mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-10-25 14:42:48 +00:00
Compare commits
192 Commits
420-locali
...
ticket/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
3aea3f502b
|
|||
|
def75cec6c
|
|||
|
41896b1dd4
|
|||
|
c5e6122d2c
|
|||
| 088b876e20 | |||
| 3400656d7c | |||
|
568c8be7fd
|
|||
|
538ecc42ea
|
|||
|
15d26d4b06
|
|||
|
d8bd9bd7cd
|
|||
|
dcdfba5ccd
|
|||
|
0204bdd38d
|
|||
|
392fd01b56
|
|||
| 35844f3b73 | |||
| 7506b918d7 | |||
| cfba291f2c | |||
|
|
04438c09d3 | ||
| 2a54d1b909 | |||
|
|
628eeac5e0 | ||
| a2263b3fa1 | |||
| 74796d0fb0 | |||
|
c19481e40a
|
|||
|
|
6eeb717b1a | ||
|
beb7c462da
|
|||
|
|
dbf363a9e8 | ||
|
64a2f7c9ed
|
|||
| f26d9739c8 | |||
|
afa5edc1d8
|
|||
|
42d6c9e672
|
|||
|
2b22d4cb7c
|
|||
|
c8e5d0eb37
|
|||
| 2bf8ad5d6c | |||
| 11698a52e3 | |||
| 70955573e8 | |||
|
|
3df4043eb9 | ||
|
06e8264dde
|
|||
| b451d2c4a3 | |||
| 4f93150874 | |||
| 0566ab0910 | |||
|
|
f4eeee1598 | ||
| 33cf16fc13 | |||
| 0a331aab37 | |||
| d43b739654 | |||
| c72432efae | |||
|
95975fae55
|
|||
|
95a7efa138
|
|||
|
45e193ff6d
|
|||
|
dfc146ff3f
|
|||
| b41fcf66a9 | |||
|
|
a8dd1b3548 | ||
|
2b99a480ac
|
|||
|
7633e587bb
|
|||
|
fc61dfdf3a
|
|||
|
f1a5b5c49e
|
|||
|
ec685dcd47
|
|||
|
631ae3eedd
|
|||
|
440a7837ac
|
|||
|
e0abf34784
|
|||
|
377ae9a9dc
|
|||
|
034dc30e30
|
|||
|
d615111a0f
|
|||
|
ffb756c712
|
|||
|
69daccb860
|
|||
|
16435423cf
|
|||
|
697b4ab436
|
|||
|
67d804e28e
|
|||
|
cf41fa9574
|
|||
|
b8b325f7d7
|
|||
|
e97bd8c4ef
|
|||
|
e28d7df533
|
|||
|
4b20b1bc01
|
|||
|
b15733076c
|
|||
|
25be5c9ea3
|
|||
|
b035020c6f
|
|||
|
128101dc46
|
|||
|
5f2711023e
|
|||
|
bdf2ed4bbd
|
|||
|
1df542603e
|
|||
|
80bcc68ce5
|
|||
|
154fc3e2f6
|
|||
|
e45af94c78
|
|||
|
166a6fde20
|
|||
|
631f047338
|
|||
|
a777588bb8
|
|||
|
ca78d112c2
|
|||
|
bcfd317d83
|
|||
|
348740f073
|
|||
|
0d74f0980f
|
|||
|
be19dc00db
|
|||
|
643028ffd6
|
|||
|
ac4e2e5bf2
|
|||
|
498572b96e
|
|||
|
d2a61ce69b
|
|||
|
a9c0567ee1
|
|||
|
76cec5b5a8
|
|||
|
efe8a67697
|
|||
|
26dfa9b028
|
|||
|
50025044d3
|
|||
|
e6202a2e34
|
|||
|
b863bd967d
|
|||
|
e65bcf7275
|
|||
|
e00ece4200
|
|||
|
640fd71402
|
|||
|
aae50ca290
|
|||
|
1fa483598b
|
|||
|
e4b6a468f8
|
|||
|
|
66c7758023 | ||
|
|
4750d2c24e | ||
|
|
ca05e3d979 | ||
|
|
a20f9b4f86 | ||
|
|
c73c1eb8d5 | ||
|
|
8778bb0731 | ||
|
|
c7d20eebc5 | ||
|
|
b9e130c159 | ||
|
|
3e8bc94af3 | ||
|
|
0c914c9f9f | ||
|
|
580a60c939 | ||
|
|
4996ac3b7c | ||
|
|
2a23bf19cb | ||
|
|
650d2596d9 | ||
|
|
2bdd5a329e | ||
|
78d1776733
|
|||
|
66dc603c85
|
|||
|
3a8154ecce
|
|||
|
c81828e04f
|
|||
|
|
ec17dd7de2 | ||
| 76c076a5f3 | |||
|
|
f0045edd6c | ||
|
|
d00b76ffcd | ||
|
|
8991f0ef3f | ||
|
|
d6f5eae0c9 | ||
|
|
821fce3dd8 | ||
|
|
1d33ae1e39 | ||
|
|
19af0feb57 | ||
|
|
1c09e9a692 | ||
|
|
d72e748388 | ||
|
|
ab850b7b70 | ||
|
|
3f9745d8cf | ||
|
|
473765366a | ||
|
|
6500c24a7f | ||
|
|
1d00457141 | ||
|
|
eb0bf56cff | ||
|
|
7b8cd90cf1 | ||
|
|
a27d92aba0 | ||
|
|
85bdfb9e21 | ||
|
|
4cffcf4de1 | ||
|
|
b2587a688f | ||
|
|
c9f0e9843b | ||
|
|
b40ad9e445 | ||
|
|
3e10e47e29 | ||
|
|
2a1963e993 | ||
| 34c171659b | |||
|
2d8b960d9e
|
|||
| 831ae03431 | |||
|
45828174d1
|
|||
|
ed45f14a45
|
|||
|
fa67835690
|
|||
|
b434d38091
|
|||
|
|
800a952532 | ||
|
9f355032a8
|
|||
|
0bc6e62d4d
|
|||
|
46fb1c04b5
|
|||
| 3b2c3d1464 | |||
|
|
0bd6038160 | ||
|
|
baab8e94ce | ||
|
e2deb55fdb
|
|||
|
|
2cdfb50058 | ||
|
39d701feb2
|
|||
|
613ee8b186
|
|||
|
56a1a488de
|
|||
| 3f789ad0f4 | |||
|
467bea7cde
|
|||
|
670b8eb82b
|
|||
|
a9760b323f
|
|||
|
71a3a1924a
|
|||
|
ecdc1e25bf
|
|||
|
dd37427be1
|
|||
|
c8467df1b1
|
|||
|
4c89a954fa
|
|||
|
7c1f3b114d
|
|||
|
36bc4dab24
|
|||
|
4b30d92282
|
|||
|
75fbec5489
|
|||
|
912fdd6349
|
|||
|
5832542978
|
|||
|
5c3585a1ed
|
|||
|
a2f1e20ddf
|
|||
|
4d67702a76
|
|||
| 18e442db29 | |||
|
|
deb3d92189 | ||
|
a59ea7db31
|
|||
|
a738b0cac9
|
6
.changes/unreleased/Feature-20240530-160003.yaml
Normal file
6
.changes/unreleased/Feature-20240530-160003.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: |
|
||||
Upgrade import of address list to the last version of compiled addresses of belgian-best-address
|
||||
time: 2024-05-30T16:00:03.440767606+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
6
.changes/unreleased/Feature-20240531-190242.yaml
Normal file
6
.changes/unreleased/Feature-20240531-190242.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: |
|
||||
Upgrade CKEditor and refactor configuration with use of typescript
|
||||
time: 2024-05-31T19:02:42.776662753+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
@@ -19,11 +19,11 @@ max_line_length = 80
|
||||
[COMMIT_EDITMSG]
|
||||
max_line_length = 0
|
||||
|
||||
[*.{js, vue, ts}]
|
||||
[*.{js,vue,ts}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
[.rst]
|
||||
ident_size = 3
|
||||
ident_style = space
|
||||
[*.rst]
|
||||
indent_size = 3
|
||||
indent_style = space
|
||||
|
||||
|
||||
13
.env
13
.env
@@ -16,9 +16,6 @@ APP_ENV=prod
|
||||
APP_SECRET=!ChangeMeInAppEnv!
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
## Wopi server for editing documents online
|
||||
EDITOR_SERVER=http://collabora:9980
|
||||
|
||||
# must be manually set in .env.local
|
||||
# ADMIN_PASSWORD=
|
||||
|
||||
@@ -92,3 +89,13 @@ REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
|
||||
###> symfony/ovh-cloud-notifier ###
|
||||
# OVHCLOUD_DSN=ovhcloud://APPLICATION_KEY:APPLICATION_SECRET@default?consumer_key=CONSUMER_KEY&service_name=SERVICE_NAME
|
||||
###< 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
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
30
.vscode/launch.json
vendored
Normal file
30
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Chill Debug",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"port": 9000,
|
||||
"pathMappings": {
|
||||
"/var/www/html": "${workspaceFolder}"
|
||||
},
|
||||
"preLaunchTask": "symfony"
|
||||
},
|
||||
{
|
||||
"name": "Yarn Encore Dev (Watch)",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "yarn encore dev --watch",
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Chill Debug + Yarn Encore Dev (Watch)",
|
||||
"configurations": ["Chill Debug", "Yarn Encore Dev (Watch)"]
|
||||
}
|
||||
]
|
||||
}
|
||||
23
.vscode/tasks.json
vendored
Normal file
23
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "symfony",
|
||||
"args": [
|
||||
"server:start",
|
||||
"--allow-http",
|
||||
"--no-tls",
|
||||
"--port=8000",
|
||||
"--allow-all-ip",
|
||||
"-d"
|
||||
],
|
||||
"label": "symfony"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"args": ["encore", "dev", "--watch"],
|
||||
"label": "webpack"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -54,7 +54,7 @@ Arborescence:
|
||||
- person
|
||||
- personvendee
|
||||
- household_edit_metadata
|
||||
- index.js
|
||||
- index.ts
|
||||
```
|
||||
|
||||
## Organisation des feuilles de styles
|
||||
|
||||
@@ -32,3 +32,9 @@ services:
|
||||
hostname: my-rabbit
|
||||
volumes:
|
||||
- ./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,7 +50,36 @@ services:
|
||||
timeout: 30s
|
||||
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:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database_data:
|
||||
###< doctrine/doctrine-bundle ###
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> symfony/mercure-bundle ###
|
||||
mercure_data:
|
||||
mercure_config:
|
||||
###< symfony/mercure-bundle ###
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"symfony/http-foundation": "^5.4",
|
||||
"symfony/intl": "^5.4",
|
||||
"symfony/mailer": "^5.4",
|
||||
"symfony/mercure-bundle": "^0.3.9",
|
||||
"symfony/messenger": "^5.4",
|
||||
"symfony/mime": "^5.4",
|
||||
"symfony/monolog-bundle": "^3.5",
|
||||
@@ -133,6 +134,7 @@
|
||||
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
|
||||
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
|
||||
"Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src",
|
||||
"Chill\\TicketBundle\\": "src/Bundle/ChillTicketBundle/src",
|
||||
"Chill\\Utils\\Rector\\": "utils/rector/src"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -35,6 +35,8 @@ return [
|
||||
Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true],
|
||||
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
|
||||
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
||||
Chill\TicketBundle\ChillTicketBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
4
config/packages/chill_ticket.yaml
Normal file
4
config/packages/chill_ticket.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
chill_ticket:
|
||||
ticket:
|
||||
person_per_ticket: one # One of "one"; "many"
|
||||
|
||||
@@ -14,6 +14,7 @@ doctrine_migrations:
|
||||
'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations'
|
||||
'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations'
|
||||
'Chill\Migrations\Report': '@ChillReportBundle/migrations'
|
||||
'Chill\Migrations\Ticket': '@ChillTicketBundle/migrations'
|
||||
all_or_nothing:
|
||||
true
|
||||
|
||||
|
||||
8
config/packages/mercure.yaml
Normal file
8
config/packages/mercure.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
mercure:
|
||||
hubs:
|
||||
default:
|
||||
url: '%env(MERCURE_URL)%'
|
||||
public_url: '%env(MERCURE_PUBLIC_URL)%'
|
||||
jwt:
|
||||
secret: '%env(MERCURE_JWT_SECRET)%'
|
||||
publish: '*'
|
||||
@@ -17,3 +17,8 @@ when@dev:
|
||||
defaults:
|
||||
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'
|
||||
|
||||
2
config/routes/chill_ticket.yaml
Normal file
2
config/routes/chill_ticket.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
chill_ticket_bundle:
|
||||
resource: '@ChillTicketBundle/config/routes.yaml'
|
||||
@@ -11,24 +11,94 @@
|
||||
Create a new bundle
|
||||
*******************
|
||||
|
||||
Create 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::
|
||||
|
||||
This part of the doc is not yet tested
|
||||
|
||||
TODO
|
||||
Create a new directory with Bundle class
|
||||
----------------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
mkdir -p src/Bundle/ChillSomeBundle/src/config
|
||||
mkdir -p src/Bundle/ChillSomeBundle/src/Controller
|
||||
|
||||
Add a bundle file
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\SomeBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class ChillSomeBundle extends Bundle {}
|
||||
|
||||
And a route file:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
chill_ticket_controller:
|
||||
resource: '@ChillTicketBundle/Controller/'
|
||||
type: annotation
|
||||
|
||||
Register the new psr-4 namespace
|
||||
--------------------------------
|
||||
|
||||
In composer.json, add the new psr4 namespace
|
||||
|
||||
.. code-block:: diff
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
+ "Chill\\SomeBundle\\": "src/Bundle/ChillSomeBundle/src",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.. rubric:: Footnotes
|
||||
Register the bundle
|
||||
-------------------
|
||||
|
||||
Register in the file :code:`config/bundles.php`:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
Vendor\Bundle\YourBundle\YourBundle::class => ['all' => true],
|
||||
|
||||
And import routes in :code:`config/routes/chill_some_bundle.yaml`:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
chill_ticket_bundle:
|
||||
resource: '@ChillSomeBundle/config/routes.yaml'
|
||||
|
||||
Add the doctrine_migration namespace
|
||||
------------------------------------
|
||||
|
||||
Add the namespace to :code:`config/packages/doctrine_migrations_chill.yaml`
|
||||
|
||||
.. code-block:: diff
|
||||
|
||||
doctrine_migrations:
|
||||
migrations_paths:
|
||||
+ 'Chill\Some\Ticket': '@ChillSomeBundle/migrations'
|
||||
|
||||
Dump autoloading
|
||||
----------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
symfony composer dump-autoload
|
||||
|
||||
.. [#f1] Be aware that we use the Affero GPL Licence, which ensure that all users must have access to derivative works done with this software.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"@hotwired/stimulus": "^3.0.0",
|
||||
"@luminateone/eslint-baseline": "^1.0.9",
|
||||
"@symfony/stimulus-bridge": "^3.2.0",
|
||||
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
|
||||
"@symfony/webpack-encore": "^4.1.0",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
@@ -79,12 +80,12 @@
|
||||
"dev": "encore dev",
|
||||
"watch": "encore dev --watch",
|
||||
"build": "encore production --progress",
|
||||
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> templates/api/specs.yaml",
|
||||
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml src/Bundle/ChillTicketBundle/chill.api.specs.yaml> templates/api/specs.yaml",
|
||||
"specs-validate": "swagger-cli validate templates/api/specs.yaml",
|
||||
"specs-create-dir": "mkdir -p templates/api",
|
||||
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
|
||||
"version": "node --version",
|
||||
"eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
||||
"eslint": "eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -58,6 +58,10 @@
|
||||
<!-- temporarily removed, the time to find a fix -->
|
||||
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="TicketBundle">
|
||||
<directory suffix="Test.php">src/Bundle/ChillTicketBundle/tests/</directory>
|
||||
</testsuite>
|
||||
<!--
|
||||
<testsuite name="ReportBundle">
|
||||
<directory suffix="Test.php">src/Bundle/ChillReportBundle/Tests/</directory>
|
||||
|
||||
20
resources/dev.Caddyfile
Normal file
20
resources/dev.Caddyfile
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
# 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>
|
||||
<concerned-groups v-if="hasPerson" />
|
||||
<social-issues-acc v-if="hasSocialIssues" />
|
||||
<location v-if="hasLocation" />
|
||||
<concerned-groups v-if="hasPerson" />
|
||||
<social-issues-acc v-if="hasSocialIssues" />
|
||||
<location v-if="hasLocation" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -10,12 +10,12 @@ import SocialIssuesAcc from "./components/SocialIssuesAcc.vue";
|
||||
import Location from "./components/Location.vue";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
SocialIssuesAcc,
|
||||
Location,
|
||||
},
|
||||
name: "App",
|
||||
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
SocialIssuesAcc,
|
||||
Location,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,46 +1,43 @@
|
||||
<template>
|
||||
<teleport to="#add-persons" v-if="isComponentVisible">
|
||||
<div class="flex-bloc concerned-groups" :class="getContext">
|
||||
<persons-bloc
|
||||
v-for="bloc in contextPersonsBlocs"
|
||||
:key="bloc.key"
|
||||
:bloc="bloc"
|
||||
:bloc-width="getBlocWidth"
|
||||
:set-persons-in-bloc="setPersonsInBloc"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
getContext === 'accompanyingCourse' &&
|
||||
suggestedEntities.length > 0
|
||||
"
|
||||
<teleport to="#add-persons" v-if="isComponentVisible">
|
||||
<div class="flex-bloc concerned-groups" :class="getContext">
|
||||
<persons-bloc
|
||||
v-for="bloc in contextPersonsBlocs"
|
||||
:key="bloc.key"
|
||||
:bloc="bloc"
|
||||
:bloc-width="getBlocWidth"
|
||||
:set-persons-in-bloc="setPersonsInBloc"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0"
|
||||
>
|
||||
<ul class="list-suggest add-items inline">
|
||||
<li
|
||||
v-for="(p, i) in suggestedEntities"
|
||||
@click="addSuggestedEntity(p)"
|
||||
:key="`suggestedEntities-${i}`"
|
||||
>
|
||||
<ul class="list-suggest add-items inline">
|
||||
<li
|
||||
v-for="(p, i) in suggestedEntities"
|
||||
@click="addSuggestedEntity(p)"
|
||||
:key="`suggestedEntities-${i}`"
|
||||
>
|
||||
<person-text v-if="p.type === 'person'" :person="p" />
|
||||
<span v-else>{{ p.text }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<person-text v-if="p.type === 'person'" :person="p" />
|
||||
<span v-else>{{ p.text }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li class="add-persons">
|
||||
<add-persons
|
||||
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||
v-bind:key="addPersons.key"
|
||||
v-bind:options="addPersonsOptions"
|
||||
@addNewPersons="addNewPersons"
|
||||
ref="addPersons"
|
||||
>
|
||||
</add-persons>
|
||||
</li>
|
||||
</ul>
|
||||
</teleport>
|
||||
<ul class="record_actions">
|
||||
<li class="add-persons">
|
||||
<add-persons
|
||||
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||
v-bind:key="addPersons.key"
|
||||
v-bind:options="addPersonsOptions"
|
||||
@addNewPersons="addNewPersons"
|
||||
ref="addPersons"
|
||||
>
|
||||
</add-persons>
|
||||
</li>
|
||||
</ul>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -49,208 +46,208 @@ import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
|
||||
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
|
||||
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
||||
import {
|
||||
ACTIVITY_BLOC_PERSONS,
|
||||
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
|
||||
ACTIVITY_BLOC_THIRDPARTY,
|
||||
ACTIVITY_BLOC_USERS,
|
||||
ACTIVITY_ADD_PERSONS,
|
||||
trans,
|
||||
ACTIVITY_BLOC_PERSONS,
|
||||
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
|
||||
ACTIVITY_BLOC_THIRDPARTY,
|
||||
ACTIVITY_BLOC_USERS,
|
||||
ACTIVITY_ADD_PERSONS,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "ConcernedGroups",
|
||||
components: {
|
||||
AddPersons,
|
||||
PersonsBloc,
|
||||
PersonText,
|
||||
name: "ConcernedGroups",
|
||||
components: {
|
||||
AddPersons,
|
||||
PersonsBloc,
|
||||
PersonText,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
trans,
|
||||
ACTIVITY_ADD_PERSONS,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
personsBlocs: [
|
||||
{
|
||||
key: "persons",
|
||||
title: trans(ACTIVITY_BLOC_PERSONS),
|
||||
persons: [],
|
||||
included: false,
|
||||
},
|
||||
{
|
||||
key: "personsAssociated",
|
||||
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
|
||||
persons: [],
|
||||
included: window.activity
|
||||
? window.activity.activityType.personsVisible !== 0
|
||||
: true,
|
||||
},
|
||||
{
|
||||
key: "personsNotAssociated",
|
||||
title: "activity.bloc_persons_not_associated",
|
||||
persons: [],
|
||||
included: window.activity
|
||||
? window.activity.activityType.personsVisible !== 0
|
||||
: true,
|
||||
},
|
||||
{
|
||||
key: "thirdparty",
|
||||
title: trans(ACTIVITY_BLOC_THIRDPARTY),
|
||||
persons: [],
|
||||
included: window.activity
|
||||
? window.activity.activityType.thirdPartiesVisible !== 0
|
||||
: true,
|
||||
},
|
||||
{
|
||||
key: "users",
|
||||
title: trans(ACTIVITY_BLOC_USERS),
|
||||
persons: [],
|
||||
included: window.activity
|
||||
? window.activity.activityType.usersVisible !== 0
|
||||
: true,
|
||||
},
|
||||
],
|
||||
addPersons: {
|
||||
key: "activity",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isComponentVisible() {
|
||||
return window.activity
|
||||
? window.activity.activityType.personsVisible !== 0 ||
|
||||
window.activity.activityType.thirdPartiesVisible !== 0 ||
|
||||
window.activity.activityType.usersVisible !== 0
|
||||
: true;
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
trans,
|
||||
ACTIVITY_ADD_PERSONS,
|
||||
};
|
||||
...mapState({
|
||||
persons: (state) => state.activity.persons,
|
||||
thirdParties: (state) => state.activity.thirdParties,
|
||||
users: (state) => state.activity.users,
|
||||
accompanyingCourse: (state) => state.activity.accompanyingPeriod,
|
||||
}),
|
||||
...mapGetters(["suggestedEntities"]),
|
||||
getContext() {
|
||||
return this.accompanyingCourse ? "accompanyingCourse" : "person";
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
personsBlocs: [
|
||||
{
|
||||
key: "persons",
|
||||
title: trans(ACTIVITY_BLOC_PERSONS),
|
||||
persons: [],
|
||||
included: false,
|
||||
},
|
||||
{
|
||||
key: "personsAssociated",
|
||||
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
|
||||
persons: [],
|
||||
included: window.activity
|
||||
? window.activity.activityType.personsVisible !== 0
|
||||
: true,
|
||||
},
|
||||
{
|
||||
key: "personsNotAssociated",
|
||||
title: "activity.bloc_persons_not_associated",
|
||||
persons: [],
|
||||
included: window.activity
|
||||
? window.activity.activityType.personsVisible !== 0
|
||||
: true,
|
||||
},
|
||||
{
|
||||
key: "thirdparty",
|
||||
title: trans(ACTIVITY_BLOC_THIRDPARTY),
|
||||
persons: [],
|
||||
included: window.activity
|
||||
? window.activity.activityType.thirdPartiesVisible !== 0
|
||||
: true,
|
||||
},
|
||||
{
|
||||
key: "users",
|
||||
title: trans(ACTIVITY_BLOC_USERS),
|
||||
persons: [],
|
||||
included: window.activity
|
||||
? window.activity.activityType.usersVisible !== 0
|
||||
: true,
|
||||
},
|
||||
],
|
||||
addPersons: {
|
||||
key: "activity",
|
||||
},
|
||||
};
|
||||
contextPersonsBlocs() {
|
||||
return this.personsBlocs.filter((bloc) => bloc.included !== false);
|
||||
},
|
||||
computed: {
|
||||
isComponentVisible() {
|
||||
return window.activity
|
||||
? window.activity.activityType.personsVisible !== 0 ||
|
||||
window.activity.activityType.thirdPartiesVisible !== 0 ||
|
||||
window.activity.activityType.usersVisible !== 0
|
||||
: true;
|
||||
},
|
||||
...mapState({
|
||||
persons: (state) => state.activity.persons,
|
||||
thirdParties: (state) => state.activity.thirdParties,
|
||||
users: (state) => state.activity.users,
|
||||
accompanyingCourse: (state) => state.activity.accompanyingPeriod,
|
||||
}),
|
||||
...mapGetters(["suggestedEntities"]),
|
||||
getContext() {
|
||||
return this.accompanyingCourse ? "accompanyingCourse" : "person";
|
||||
},
|
||||
contextPersonsBlocs() {
|
||||
return this.personsBlocs.filter((bloc) => bloc.included !== false);
|
||||
},
|
||||
addPersonsOptions() {
|
||||
let optionsType = [];
|
||||
if (window.activity) {
|
||||
if (window.activity.activityType.personsVisible !== 0) {
|
||||
optionsType.push("person");
|
||||
}
|
||||
if (window.activity.activityType.thirdPartiesVisible !== 0) {
|
||||
optionsType.push("thirdparty");
|
||||
}
|
||||
if (window.activity.activityType.usersVisible !== 0) {
|
||||
optionsType.push("user");
|
||||
}
|
||||
} else {
|
||||
optionsType = ["person", "thirdparty", "user"];
|
||||
}
|
||||
return {
|
||||
type: optionsType,
|
||||
priority: null,
|
||||
uniq: false,
|
||||
button: {
|
||||
size: "btn-sm",
|
||||
},
|
||||
};
|
||||
},
|
||||
getBlocWidth() {
|
||||
return Math.round(100 / this.contextPersonsBlocs.length) + "%";
|
||||
addPersonsOptions() {
|
||||
let optionsType = [];
|
||||
if (window.activity) {
|
||||
if (window.activity.activityType.personsVisible !== 0) {
|
||||
optionsType.push("person");
|
||||
}
|
||||
if (window.activity.activityType.thirdPartiesVisible !== 0) {
|
||||
optionsType.push("thirdparty");
|
||||
}
|
||||
if (window.activity.activityType.usersVisible !== 0) {
|
||||
optionsType.push("user");
|
||||
}
|
||||
} else {
|
||||
optionsType = ["person", "thirdparty", "user"];
|
||||
}
|
||||
return {
|
||||
type: optionsType,
|
||||
priority: null,
|
||||
uniq: false,
|
||||
button: {
|
||||
size: "btn-sm",
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.setPersonsInBloc();
|
||||
getBlocWidth() {
|
||||
return Math.round(100 / this.contextPersonsBlocs.length) + "%";
|
||||
},
|
||||
methods: {
|
||||
setPersonsInBloc() {
|
||||
let groups;
|
||||
if (this.accompanyingCourse) {
|
||||
groups = this.splitPersonsInGroups();
|
||||
}
|
||||
this.personsBlocs.forEach((bloc) => {
|
||||
if (this.accompanyingCourse) {
|
||||
switch (bloc.key) {
|
||||
case "personsAssociated":
|
||||
bloc.persons = groups.personsAssociated;
|
||||
bloc.included = true;
|
||||
break;
|
||||
case "personsNotAssociated":
|
||||
bloc.persons = groups.personsNotAssociated;
|
||||
bloc.included = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (bloc.key) {
|
||||
case "persons":
|
||||
bloc.persons = this.persons;
|
||||
bloc.included = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (bloc.key) {
|
||||
case "thirdparty":
|
||||
bloc.persons = this.thirdParties;
|
||||
break;
|
||||
case "users":
|
||||
bloc.persons = this.users;
|
||||
break;
|
||||
}
|
||||
}, groups);
|
||||
},
|
||||
splitPersonsInGroups() {
|
||||
let personsAssociated = [];
|
||||
let personsNotAssociated = this.persons;
|
||||
let participations = this.getCourseParticipations();
|
||||
this.persons.forEach((person) => {
|
||||
participations.forEach((participation) => {
|
||||
if (person.id === participation.id) {
|
||||
//console.log(person.id);
|
||||
personsAssociated.push(person);
|
||||
personsNotAssociated = personsNotAssociated.filter(
|
||||
(p) => p !== person,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
return {
|
||||
personsAssociated: personsAssociated,
|
||||
personsNotAssociated: personsNotAssociated,
|
||||
};
|
||||
},
|
||||
getCourseParticipations() {
|
||||
let participations = [];
|
||||
this.accompanyingCourse.participations.forEach((participation) => {
|
||||
if (!participation.endDate) {
|
||||
participations.push(participation.person);
|
||||
}
|
||||
});
|
||||
return participations;
|
||||
},
|
||||
addNewPersons({ selected, modal }) {
|
||||
console.log("@@@ CLICK button addNewPersons", selected);
|
||||
selected.forEach((item) => {
|
||||
this.$store.dispatch("addPersonsInvolved", item);
|
||||
}, this);
|
||||
this.$refs.addPersons.resetSearch(); // to cast child method
|
||||
modal.showModal = false;
|
||||
this.setPersonsInBloc();
|
||||
},
|
||||
addSuggestedEntity(person) {
|
||||
this.$store.dispatch("addPersonsInvolved", {
|
||||
result: person,
|
||||
type: "person",
|
||||
});
|
||||
this.setPersonsInBloc();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setPersonsInBloc();
|
||||
},
|
||||
methods: {
|
||||
setPersonsInBloc() {
|
||||
let groups;
|
||||
if (this.accompanyingCourse) {
|
||||
groups = this.splitPersonsInGroups();
|
||||
}
|
||||
this.personsBlocs.forEach((bloc) => {
|
||||
if (this.accompanyingCourse) {
|
||||
switch (bloc.key) {
|
||||
case "personsAssociated":
|
||||
bloc.persons = groups.personsAssociated;
|
||||
bloc.included = true;
|
||||
break;
|
||||
case "personsNotAssociated":
|
||||
bloc.persons = groups.personsNotAssociated;
|
||||
bloc.included = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (bloc.key) {
|
||||
case "persons":
|
||||
bloc.persons = this.persons;
|
||||
bloc.included = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (bloc.key) {
|
||||
case "thirdparty":
|
||||
bloc.persons = this.thirdParties;
|
||||
break;
|
||||
case "users":
|
||||
bloc.persons = this.users;
|
||||
break;
|
||||
}
|
||||
}, groups);
|
||||
},
|
||||
splitPersonsInGroups() {
|
||||
let personsAssociated = [];
|
||||
let personsNotAssociated = this.persons;
|
||||
let participations = this.getCourseParticipations();
|
||||
this.persons.forEach((person) => {
|
||||
participations.forEach((participation) => {
|
||||
if (person.id === participation.id) {
|
||||
//console.log(person.id);
|
||||
personsAssociated.push(person);
|
||||
personsNotAssociated = personsNotAssociated.filter(
|
||||
(p) => p !== person,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
return {
|
||||
personsAssociated: personsAssociated,
|
||||
personsNotAssociated: personsNotAssociated,
|
||||
};
|
||||
},
|
||||
getCourseParticipations() {
|
||||
let participations = [];
|
||||
this.accompanyingCourse.participations.forEach((participation) => {
|
||||
if (!participation.endDate) {
|
||||
participations.push(participation.person);
|
||||
}
|
||||
});
|
||||
return participations;
|
||||
},
|
||||
addNewPersons({ selected, modal }) {
|
||||
console.log("@@@ CLICK button addNewPersons", selected);
|
||||
selected.forEach((item) => {
|
||||
this.$store.dispatch("addPersonsInvolved", item);
|
||||
}, this);
|
||||
this.$refs.addPersons.resetSearch(); // to cast child method
|
||||
modal.showModal = false;
|
||||
this.setPersonsInBloc();
|
||||
},
|
||||
addSuggestedEntity(person) {
|
||||
this.$store.dispatch("addPersonsInvolved", {
|
||||
result: person,
|
||||
type: "person",
|
||||
});
|
||||
this.setPersonsInBloc();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
<template>
|
||||
<li>
|
||||
<span :title="person.text" @click.prevent="$emit('remove', person)">
|
||||
<span class="chill_denomination">
|
||||
<person-text :person="person" :is-cut="true" />
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span :title="person.text" @click.prevent="$emit('remove', person)">
|
||||
<span class="chill_denomination">
|
||||
<person-text :person="person" :is-cut="true" />
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
||||
|
||||
export default {
|
||||
name: "PersonBadge",
|
||||
props: ["person"],
|
||||
components: {
|
||||
PersonText,
|
||||
},
|
||||
// computed: {
|
||||
// textCutted() {
|
||||
// let more = (this.person.text.length > 15) ?'…' : '';
|
||||
// return this.person.text.slice(0,15) + more;
|
||||
// }
|
||||
// },
|
||||
emits: ["remove"],
|
||||
name: "PersonBadge",
|
||||
props: ["person"],
|
||||
components: {
|
||||
PersonText,
|
||||
},
|
||||
// computed: {
|
||||
// textCutted() {
|
||||
// let more = (this.person.text.length > 15) ?'…' : '';
|
||||
// return this.person.text.slice(0,15) + more;
|
||||
// }
|
||||
// },
|
||||
emits: ["remove"],
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
<template>
|
||||
<div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<h4>{{ $t(bloc.title) }}</h4>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-suggest remove-items">
|
||||
<person-badge
|
||||
v-for="person in bloc.persons"
|
||||
:key="person.id"
|
||||
:person="person"
|
||||
@remove="removePerson"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<h4>{{ $t(bloc.title) }}</h4>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-suggest remove-items">
|
||||
<person-badge
|
||||
v-for="person in bloc.persons"
|
||||
:key="person.id"
|
||||
:person="person"
|
||||
@remove="removePerson"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PersonBadge from "./PersonBadge.vue";
|
||||
export default {
|
||||
name: "PersonsBloc",
|
||||
components: {
|
||||
PersonBadge,
|
||||
},
|
||||
props: ["bloc", "setPersonsInBloc", "blocWidth"],
|
||||
methods: {
|
||||
removePerson(item) {
|
||||
console.log("@@ CLICK remove person: item", item);
|
||||
this.$store.dispatch("removePersonInvolved", item);
|
||||
this.setPersonsInBloc();
|
||||
},
|
||||
name: "PersonsBloc",
|
||||
components: {
|
||||
PersonBadge,
|
||||
},
|
||||
props: ["bloc", "setPersonsInBloc", "blocWidth"],
|
||||
methods: {
|
||||
removePerson(item) {
|
||||
console.log("@@ CLICK remove person: item", item);
|
||||
this.$store.dispatch("removePersonInvolved", item);
|
||||
this.setPersonsInBloc();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
<template>
|
||||
<teleport to="#location">
|
||||
<div class="mb-3 row">
|
||||
<label :class="locationClassList">
|
||||
{{ trans(ACTIVITY_LOCATION) }}
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<VueMultiselect
|
||||
name="selectLocation"
|
||||
id="selectLocation"
|
||||
label="name"
|
||||
track-by="id"
|
||||
open-direction="top"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
|
||||
:custom-label="customLabel"
|
||||
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
||||
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
|
||||
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
||||
:options="availableLocations"
|
||||
group-values="locations"
|
||||
group-label="locationGroup"
|
||||
v-model="location"
|
||||
/>
|
||||
<new-location v-bind:available-locations="availableLocations" />
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
<teleport to="#location">
|
||||
<div class="mb-3 row">
|
||||
<label :class="locationClassList">
|
||||
{{ trans(ACTIVITY_LOCATION) }}
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<VueMultiselect
|
||||
name="selectLocation"
|
||||
id="selectLocation"
|
||||
label="name"
|
||||
track-by="id"
|
||||
open-direction="top"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
|
||||
:custom-label="customLabel"
|
||||
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
||||
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
|
||||
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
||||
:options="availableLocations"
|
||||
group-values="locations"
|
||||
group-label="locationGroup"
|
||||
v-model="location"
|
||||
/>
|
||||
<new-location v-bind:available-locations="availableLocations" />
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -35,60 +35,60 @@ import VueMultiselect from "vue-multiselect";
|
||||
import NewLocation from "./Location/NewLocation.vue";
|
||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||
import {
|
||||
trans,
|
||||
ACTIVITY_LOCATION,
|
||||
ACTIVITY_CHOOSE_LOCATION,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
MULTISELECT_DESELECT_LABEL,
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
trans,
|
||||
ACTIVITY_LOCATION,
|
||||
ACTIVITY_CHOOSE_LOCATION,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
MULTISELECT_DESELECT_LABEL,
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "Location",
|
||||
components: {
|
||||
NewLocation,
|
||||
VueMultiselect,
|
||||
name: "Location",
|
||||
components: {
|
||||
NewLocation,
|
||||
VueMultiselect,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
trans,
|
||||
ACTIVITY_LOCATION,
|
||||
ACTIVITY_CHOOSE_LOCATION,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
MULTISELECT_DESELECT_LABEL,
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["activity", "availableLocations"]),
|
||||
...mapGetters(["suggestedEntities"]),
|
||||
location: {
|
||||
get() {
|
||||
return this.activity.location;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.dispatch("updateLocation", value);
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
trans,
|
||||
ACTIVITY_LOCATION,
|
||||
ACTIVITY_CHOOSE_LOCATION,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
MULTISELECT_DESELECT_LABEL,
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
labelAccompanyingCourseLocation(value) {
|
||||
return `${value.address.text} (${localizeString(value.locationType.title)})`;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["activity", "availableLocations"]),
|
||||
...mapGetters(["suggestedEntities"]),
|
||||
location: {
|
||||
get() {
|
||||
return this.activity.location;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.dispatch("updateLocation", value);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
labelAccompanyingCourseLocation(value) {
|
||||
return `${value.address.text} (${localizeString(value.locationType.title)})`;
|
||||
},
|
||||
customLabel(value) {
|
||||
return value.locationType
|
||||
? value.name
|
||||
? value.name === "__AccompanyingCourseLocation__"
|
||||
? this.labelAccompanyingCourseLocation(value)
|
||||
: `${value.name} (${localizeString(value.locationType.title)})`
|
||||
: localizeString(value.locationType.title)
|
||||
: "";
|
||||
},
|
||||
customLabel(value) {
|
||||
return value.locationType
|
||||
? value.name
|
||||
? value.name === "__AccompanyingCourseLocation__"
|
||||
? this.labelAccompanyingCourseLocation(value)
|
||||
: `${value.name} (${localizeString(value.locationType.title)})`
|
||||
: localizeString(value.locationType.title)
|
||||
: "";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,123 +1,114 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="btn btn-sm btn-create" @click="openModal">
|
||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="btn btn-sm btn-create" @click="openModal">
|
||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<teleport to="body">
|
||||
<modal
|
||||
v-if="modal.showModal"
|
||||
:modalDialogClass="modal.modalDialogClass"
|
||||
@close="modal.showModal = false"
|
||||
>
|
||||
<template #header>
|
||||
<h3 class="modal-title">
|
||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||
</h3>
|
||||
</template>
|
||||
<template #body>
|
||||
<form>
|
||||
<div class="alert alert-warning" v-if="errors.length">
|
||||
<ul>
|
||||
<li v-for="(e, i) in errors" :key="i">
|
||||
{{ e }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<teleport to="body">
|
||||
<modal
|
||||
v-if="modal.showModal"
|
||||
:modalDialogClass="modal.modalDialogClass"
|
||||
@close="modal.showModal = false"
|
||||
>
|
||||
<template #header>
|
||||
<h3 class="modal-title">
|
||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||
</h3>
|
||||
</template>
|
||||
<template #body>
|
||||
<form>
|
||||
<div class="alert alert-warning" v-if="errors.length">
|
||||
<ul>
|
||||
<li v-for="(e, i) in errors" :key="i">
|
||||
{{ e }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<select
|
||||
class="form-select form-select-lg"
|
||||
id="type"
|
||||
required
|
||||
v-model="selectType"
|
||||
>
|
||||
<option selected disabled value="">
|
||||
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
|
||||
</option>
|
||||
<option
|
||||
v-for="t in locationTypes"
|
||||
:value="t"
|
||||
:key="t.id"
|
||||
>
|
||||
{{ localizeString(t.title) }}
|
||||
</option>
|
||||
</select>
|
||||
<label>{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_TYPE)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<select
|
||||
class="form-select form-select-lg"
|
||||
id="type"
|
||||
required
|
||||
v-model="selectType"
|
||||
>
|
||||
<option selected disabled value="">
|
||||
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
|
||||
</option>
|
||||
<option v-for="t in locationTypes" :value="t" :key="t.id">
|
||||
{{ localizeString(t.title) }}
|
||||
</option>
|
||||
</select>
|
||||
<label>{{ trans(ACTIVITY_LOCATION_FIELDS_TYPE) }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="name"
|
||||
v-model="inputName"
|
||||
placeholder
|
||||
/>
|
||||
<label for="name">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_NAME)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="name"
|
||||
v-model="inputName"
|
||||
placeholder
|
||||
/>
|
||||
<label for="name">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_NAME)
|
||||
}}</label>
|
||||
</div>
|
||||
|
||||
<add-address
|
||||
:context="addAddress.context"
|
||||
:options="addAddress.options"
|
||||
:addressChangedCallback="submitNewAddress"
|
||||
v-if="showAddAddress"
|
||||
ref="addAddress"
|
||||
/>
|
||||
<add-address
|
||||
:context="addAddress.context"
|
||||
:options="addAddress.options"
|
||||
:addressChangedCallback="submitNewAddress"
|
||||
v-if="showAddAddress"
|
||||
ref="addAddress"
|
||||
/>
|
||||
|
||||
<div class="form-floating mb-3" v-if="showContactData">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="phonenumber1"
|
||||
v-model="inputPhonenumber1"
|
||||
placeholder
|
||||
/>
|
||||
<label for="phonenumber1">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="phonenumber2"
|
||||
v-model="inputPhonenumber2"
|
||||
placeholder
|
||||
/>
|
||||
<label for="phonenumber2">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="showContactData">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="email"
|
||||
v-model="inputEmail"
|
||||
placeholder
|
||||
/>
|
||||
<label for="email">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
|
||||
}}</label>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button
|
||||
class="btn btn-save"
|
||||
@click.prevent="saveNewLocation"
|
||||
>
|
||||
{{ trans(SAVE) }}
|
||||
</button>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="showContactData">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="phonenumber1"
|
||||
v-model="inputPhonenumber1"
|
||||
placeholder
|
||||
/>
|
||||
<label for="phonenumber1">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="phonenumber2"
|
||||
v-model="inputPhonenumber2"
|
||||
placeholder
|
||||
/>
|
||||
<label for="phonenumber2">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="showContactData">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="email"
|
||||
v-model="inputEmail"
|
||||
placeholder
|
||||
/>
|
||||
<label for="email">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
|
||||
}}</label>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button class="btn btn-save" @click.prevent="saveNewLocation">
|
||||
{{ trans(SAVE) }}
|
||||
</button>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -128,237 +119,236 @@ import { getLocationTypes } from "../../api";
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||
import {
|
||||
SAVE,
|
||||
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
||||
ACTIVITY_LOCATION_FIELDS_NAME,
|
||||
ACTIVITY_LOCATION_FIELDS_TYPE,
|
||||
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
||||
ACTIVITY_CREATE_NEW_LOCATION,
|
||||
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,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "NewLocation",
|
||||
components: {
|
||||
Modal,
|
||||
AddAddress,
|
||||
name: "NewLocation",
|
||||
components: {
|
||||
Modal,
|
||||
AddAddress,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
trans,
|
||||
SAVE,
|
||||
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
||||
ACTIVITY_LOCATION_FIELDS_NAME,
|
||||
ACTIVITY_LOCATION_FIELDS_TYPE,
|
||||
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
||||
ACTIVITY_CREATE_NEW_LOCATION,
|
||||
};
|
||||
},
|
||||
props: ["availableLocations"],
|
||||
data() {
|
||||
return {
|
||||
errors: [],
|
||||
selected: {
|
||||
type: null,
|
||||
name: null,
|
||||
addressId: null,
|
||||
phonenumber1: null,
|
||||
phonenumber2: null,
|
||||
email: null,
|
||||
},
|
||||
locationTypes: [],
|
||||
modal: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-scrollable modal-xl",
|
||||
},
|
||||
addAddress: {
|
||||
options: {
|
||||
button: {
|
||||
text: {
|
||||
create: "activity.create_address",
|
||||
edit: "activity.edit_address",
|
||||
},
|
||||
size: "btn-sm",
|
||||
},
|
||||
title: {
|
||||
create: "activity.create_address",
|
||||
edit: "activity.edit_address",
|
||||
},
|
||||
},
|
||||
context: {
|
||||
target: {
|
||||
//name, id
|
||||
},
|
||||
edit: false,
|
||||
addressId: null,
|
||||
defaults: window.addaddress,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["activity"]),
|
||||
selectType: {
|
||||
get() {
|
||||
return this.selected.type;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.type = value;
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
trans,
|
||||
SAVE,
|
||||
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
||||
ACTIVITY_LOCATION_FIELDS_NAME,
|
||||
ACTIVITY_LOCATION_FIELDS_TYPE,
|
||||
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
||||
ACTIVITY_CREATE_NEW_LOCATION,
|
||||
inputName: {
|
||||
get() {
|
||||
return this.selected.name;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.name = value;
|
||||
},
|
||||
},
|
||||
inputEmail: {
|
||||
get() {
|
||||
return this.selected.email;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.email = value;
|
||||
},
|
||||
},
|
||||
inputPhonenumber1: {
|
||||
get() {
|
||||
return this.selected.phonenumber1;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.phonenumber1 = value;
|
||||
},
|
||||
},
|
||||
inputPhonenumber2: {
|
||||
get() {
|
||||
return this.selected.phonenumber2;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.phonenumber2 = value;
|
||||
},
|
||||
},
|
||||
hasPhonenumber1() {
|
||||
return (
|
||||
this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== ""
|
||||
);
|
||||
},
|
||||
showAddAddress() {
|
||||
let cond = false;
|
||||
if (this.selected.type) {
|
||||
if (this.selected.type.addressRequired !== "never") {
|
||||
cond = true;
|
||||
}
|
||||
}
|
||||
return cond;
|
||||
},
|
||||
showContactData() {
|
||||
let cond = false;
|
||||
if (this.selected.type) {
|
||||
if (this.selected.type.contactData !== "never") {
|
||||
cond = true;
|
||||
}
|
||||
}
|
||||
return cond;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getLocationTypesList();
|
||||
},
|
||||
methods: {
|
||||
localizeString,
|
||||
checkForm() {
|
||||
let cond = true;
|
||||
this.errors = [];
|
||||
if (!this.selected.type) {
|
||||
this.errors.push("Type de localisation requis");
|
||||
cond = false;
|
||||
} else {
|
||||
if (
|
||||
this.selected.type.addressRequired === "required" &&
|
||||
!this.selected.addressId
|
||||
) {
|
||||
this.errors.push("Adresse requise");
|
||||
cond = false;
|
||||
}
|
||||
if (
|
||||
this.selected.type.contactData === "required" &&
|
||||
!this.selected.phonenumber1
|
||||
) {
|
||||
this.errors.push("Numéro de téléphone requis");
|
||||
cond = false;
|
||||
}
|
||||
if (
|
||||
this.selected.type.contactData === "required" &&
|
||||
!this.selected.email
|
||||
) {
|
||||
this.errors.push("Adresse email requise");
|
||||
cond = false;
|
||||
}
|
||||
}
|
||||
return cond;
|
||||
},
|
||||
getLocationTypesList() {
|
||||
getLocationTypes().then((results) => {
|
||||
this.locationTypes = results.filter(
|
||||
(t) => t.availableForUsers === true,
|
||||
);
|
||||
});
|
||||
},
|
||||
openModal() {
|
||||
this.modal.showModal = true;
|
||||
},
|
||||
saveNewLocation() {
|
||||
if (this.checkForm()) {
|
||||
let body = {
|
||||
type: "location",
|
||||
name: this.selected.name,
|
||||
locationType: {
|
||||
id: this.selected.type.id,
|
||||
type: "location-type",
|
||||
},
|
||||
phonenumber1: this.selected.phonenumber1,
|
||||
phonenumber2: this.selected.phonenumber2,
|
||||
email: this.selected.email,
|
||||
};
|
||||
},
|
||||
props: ["availableLocations"],
|
||||
data() {
|
||||
return {
|
||||
errors: [],
|
||||
selected: {
|
||||
type: null,
|
||||
name: null,
|
||||
addressId: null,
|
||||
phonenumber1: null,
|
||||
phonenumber2: null,
|
||||
email: null,
|
||||
if (this.selected.addressId) {
|
||||
body = Object.assign(body, {
|
||||
address: {
|
||||
id: this.selected.addressId,
|
||||
},
|
||||
locationTypes: [],
|
||||
modal: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-scrollable modal-xl",
|
||||
},
|
||||
addAddress: {
|
||||
options: {
|
||||
button: {
|
||||
text: {
|
||||
create: "activity.create_address",
|
||||
edit: "activity.edit_address",
|
||||
},
|
||||
size: "btn-sm",
|
||||
},
|
||||
title: {
|
||||
create: "activity.create_address",
|
||||
edit: "activity.edit_address",
|
||||
},
|
||||
},
|
||||
context: {
|
||||
target: {
|
||||
//name, id
|
||||
},
|
||||
edit: false,
|
||||
addressId: null,
|
||||
defaults: window.addaddress,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["activity"]),
|
||||
selectType: {
|
||||
get() {
|
||||
return this.selected.type;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.type = value;
|
||||
},
|
||||
},
|
||||
inputName: {
|
||||
get() {
|
||||
return this.selected.name;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.name = value;
|
||||
},
|
||||
},
|
||||
inputEmail: {
|
||||
get() {
|
||||
return this.selected.email;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.email = value;
|
||||
},
|
||||
},
|
||||
inputPhonenumber1: {
|
||||
get() {
|
||||
return this.selected.phonenumber1;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.phonenumber1 = value;
|
||||
},
|
||||
},
|
||||
inputPhonenumber2: {
|
||||
get() {
|
||||
return this.selected.phonenumber2;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.phonenumber2 = value;
|
||||
},
|
||||
},
|
||||
hasPhonenumber1() {
|
||||
return (
|
||||
this.selected.phonenumber1 !== null &&
|
||||
this.selected.phonenumber1 !== ""
|
||||
);
|
||||
},
|
||||
showAddAddress() {
|
||||
let cond = false;
|
||||
if (this.selected.type) {
|
||||
if (this.selected.type.addressRequired !== "never") {
|
||||
cond = true;
|
||||
}
|
||||
}
|
||||
return cond;
|
||||
},
|
||||
showContactData() {
|
||||
let cond = false;
|
||||
if (this.selected.type) {
|
||||
if (this.selected.type.contactData !== "never") {
|
||||
cond = true;
|
||||
}
|
||||
}
|
||||
return cond;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getLocationTypesList();
|
||||
},
|
||||
methods: {
|
||||
localizeString,
|
||||
checkForm() {
|
||||
let cond = true;
|
||||
this.errors = [];
|
||||
if (!this.selected.type) {
|
||||
this.errors.push("Type de localisation requis");
|
||||
cond = false;
|
||||
} else {
|
||||
if (
|
||||
this.selected.type.addressRequired === "required" &&
|
||||
!this.selected.addressId
|
||||
) {
|
||||
this.errors.push("Adresse requise");
|
||||
cond = false;
|
||||
}
|
||||
if (
|
||||
this.selected.type.contactData === "required" &&
|
||||
!this.selected.phonenumber1
|
||||
) {
|
||||
this.errors.push("Numéro de téléphone requis");
|
||||
cond = false;
|
||||
}
|
||||
if (
|
||||
this.selected.type.contactData === "required" &&
|
||||
!this.selected.email
|
||||
) {
|
||||
this.errors.push("Adresse email requise");
|
||||
cond = false;
|
||||
}
|
||||
}
|
||||
return cond;
|
||||
},
|
||||
getLocationTypesList() {
|
||||
getLocationTypes().then((results) => {
|
||||
this.locationTypes = results.filter(
|
||||
(t) => t.availableForUsers === true,
|
||||
);
|
||||
});
|
||||
},
|
||||
openModal() {
|
||||
this.modal.showModal = true;
|
||||
},
|
||||
saveNewLocation() {
|
||||
if (this.checkForm()) {
|
||||
let body = {
|
||||
type: "location",
|
||||
name: this.selected.name,
|
||||
locationType: {
|
||||
id: this.selected.type.id,
|
||||
type: "location-type",
|
||||
},
|
||||
phonenumber1: this.selected.phonenumber1,
|
||||
phonenumber2: this.selected.phonenumber2,
|
||||
email: this.selected.email,
|
||||
};
|
||||
if (this.selected.addressId) {
|
||||
body = Object.assign(body, {
|
||||
address: {
|
||||
id: this.selected.addressId,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
makeFetch("POST", "/api/1.0/main/location.json", body)
|
||||
.then((response) => {
|
||||
this.$store.dispatch("addAvailableLocationGroup", {
|
||||
locationGroup: "Localisations nouvellement créées",
|
||||
locations: [response],
|
||||
});
|
||||
this.$store.dispatch("updateLocation", response);
|
||||
this.modal.showModal = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.name === "ValidationException") {
|
||||
for (let v of error.violations) {
|
||||
this.errors.push(v);
|
||||
}
|
||||
} else {
|
||||
this.errors.push("An error occurred");
|
||||
}
|
||||
});
|
||||
makeFetch("POST", "/api/1.0/main/location.json", body)
|
||||
.then((response) => {
|
||||
this.$store.dispatch("addAvailableLocationGroup", {
|
||||
locationGroup: "Localisations nouvellement créées",
|
||||
locations: [response],
|
||||
});
|
||||
this.$store.dispatch("updateLocation", response);
|
||||
this.modal.showModal = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.name === "ValidationException") {
|
||||
for (let v of error.violations) {
|
||||
this.errors.push(v);
|
||||
}
|
||||
} else {
|
||||
this.errors.push("An error occurred");
|
||||
}
|
||||
},
|
||||
submitNewAddress(payload) {
|
||||
this.selected.addressId = payload.addressId;
|
||||
this.addAddress.context.addressId = payload.addressId;
|
||||
this.addAddress.context.edit = true;
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
submitNewAddress(payload) {
|
||||
this.selected.addressId = payload.addressId;
|
||||
this.addAddress.context.addressId = payload.addressId;
|
||||
this.addAddress.context.edit = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,103 +1,98 @@
|
||||
<template>
|
||||
<teleport to="#social-issues-acc">
|
||||
<div class="mb-3 row">
|
||||
<div class="col-4">
|
||||
<label :class="socialIssuesClassList">{{
|
||||
trans(ACTIVITY_SOCIAL_ISSUES)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<check-social-issue
|
||||
v-for="issue in socialIssuesList"
|
||||
:key="issue.id"
|
||||
:issue="issue"
|
||||
:selection="socialIssuesSelected"
|
||||
@updateSelected="updateIssuesSelected"
|
||||
>
|
||||
</check-social-issue>
|
||||
<teleport to="#social-issues-acc">
|
||||
<div class="mb-3 row">
|
||||
<div class="col-4">
|
||||
<label :class="socialIssuesClassList">{{
|
||||
trans(ACTIVITY_SOCIAL_ISSUES)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<check-social-issue
|
||||
v-for="issue in socialIssuesList"
|
||||
:key="issue.id"
|
||||
:issue="issue"
|
||||
:selection="socialIssuesSelected"
|
||||
@updateSelected="updateIssuesSelected"
|
||||
>
|
||||
</check-social-issue>
|
||||
|
||||
<div class="my-3">
|
||||
<VueMultiselect
|
||||
name="otherIssues"
|
||||
label="text"
|
||||
track-by="id"
|
||||
open-direction="bottom"
|
||||
:close-on-select="true"
|
||||
:preserve-search="false"
|
||||
:reset-after="true"
|
||||
:hide-selected="true"
|
||||
:taggable="false"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:allow-empty="true"
|
||||
:show-labels="false"
|
||||
:loading="issueIsLoading"
|
||||
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
|
||||
:options="socialIssuesOther"
|
||||
@select="addIssueInList"
|
||||
>
|
||||
</VueMultiselect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<VueMultiselect
|
||||
name="otherIssues"
|
||||
label="text"
|
||||
track-by="id"
|
||||
open-direction="bottom"
|
||||
:close-on-select="true"
|
||||
:preserve-search="false"
|
||||
:reset-after="true"
|
||||
:hide-selected="true"
|
||||
:taggable="false"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:allow-empty="true"
|
||||
:show-labels="false"
|
||||
:loading="issueIsLoading"
|
||||
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
|
||||
:options="socialIssuesOther"
|
||||
@select="addIssueInList"
|
||||
>
|
||||
</VueMultiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 row">
|
||||
<div class="col-4">
|
||||
<label :class="socialActionsClassList">{{
|
||||
trans(ACTIVITY_SOCIAL_ACTIONS)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div v-if="actionIsLoading === true">
|
||||
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg"></i>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 row">
|
||||
<div class="col-4">
|
||||
<label :class="socialActionsClassList">{{
|
||||
trans(ACTIVITY_SOCIAL_ACTIONS)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div v-if="actionIsLoading === true">
|
||||
<i
|
||||
class="chill-green fa fa-circle-o-notch fa-spin fa-lg"
|
||||
></i>
|
||||
</div>
|
||||
<span
|
||||
v-else-if="socialIssuesSelected.length === 0"
|
||||
class="inline-choice chill-no-data-statement mt-3"
|
||||
>
|
||||
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-else-if="socialIssuesSelected.length === 0"
|
||||
class="inline-choice chill-no-data-statement mt-3"
|
||||
>
|
||||
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
|
||||
</span>
|
||||
<template
|
||||
v-else-if="
|
||||
socialActionsList.length > 0 &&
|
||||
(socialIssuesSelected.length || socialActionsSelected.length)
|
||||
"
|
||||
>
|
||||
<div
|
||||
id="actionsList"
|
||||
v-for="group in socialActionsList"
|
||||
:key="group.issue"
|
||||
>
|
||||
<span class="badge bg-chill-l-gray text-dark">{{
|
||||
group.issue
|
||||
}}</span>
|
||||
<check-social-action
|
||||
v-for="action in group.actions"
|
||||
:key="action.id"
|
||||
:action="action"
|
||||
:selection="socialActionsSelected"
|
||||
@updateSelected="updateActionsSelected"
|
||||
>
|
||||
</check-social-action>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-else-if="
|
||||
socialActionsList.length > 0 &&
|
||||
(socialIssuesSelected.length ||
|
||||
socialActionsSelected.length)
|
||||
"
|
||||
>
|
||||
<div
|
||||
id="actionsList"
|
||||
v-for="group in socialActionsList"
|
||||
:key="group.issue"
|
||||
>
|
||||
<span class="badge bg-chill-l-gray text-dark">{{
|
||||
group.issue
|
||||
}}</span>
|
||||
<check-social-action
|
||||
v-for="action in group.actions"
|
||||
:key="action.id"
|
||||
:action="action"
|
||||
:selection="socialActionsSelected"
|
||||
@updateSelected="updateActionsSelected"
|
||||
>
|
||||
</check-social-action>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<span
|
||||
v-else-if="
|
||||
actionAreLoaded && socialActionsList.length === 0
|
||||
"
|
||||
class="inline-choice chill-no-data-statement mt-3"
|
||||
>
|
||||
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
<span
|
||||
v-else-if="actionAreLoaded && socialActionsList.length === 0"
|
||||
class="inline-choice chill-no-data-statement mt-3"
|
||||
>
|
||||
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -106,154 +101,153 @@ import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
|
||||
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
|
||||
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
|
||||
import {
|
||||
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||
ACTIVITY_SOCIAL_ACTIONS,
|
||||
ACTIVITY_SOCIAL_ISSUES,
|
||||
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
||||
trans,
|
||||
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||
ACTIVITY_SOCIAL_ACTIONS,
|
||||
ACTIVITY_SOCIAL_ISSUES,
|
||||
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "SocialIssuesAcc",
|
||||
components: {
|
||||
CheckSocialIssue,
|
||||
CheckSocialAction,
|
||||
VueMultiselect,
|
||||
name: "SocialIssuesAcc",
|
||||
components: {
|
||||
CheckSocialIssue,
|
||||
CheckSocialAction,
|
||||
VueMultiselect,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
trans,
|
||||
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||
ACTIVITY_SOCIAL_ACTIONS,
|
||||
ACTIVITY_SOCIAL_ISSUES,
|
||||
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
issueIsLoading: false,
|
||||
actionIsLoading: false,
|
||||
actionAreLoaded: false,
|
||||
socialIssuesClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialIssues").getAttribute("required") ? "required" : ""}`,
|
||||
socialActionsClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialActions").getAttribute("required") ? "required" : ""}`,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
socialIssuesList() {
|
||||
return this.$store.state.activity.accompanyingPeriod.socialIssues;
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
trans,
|
||||
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||
ACTIVITY_SOCIAL_ACTIONS,
|
||||
ACTIVITY_SOCIAL_ISSUES,
|
||||
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
||||
};
|
||||
socialIssuesSelected() {
|
||||
return this.$store.state.activity.socialIssues;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
issueIsLoading: false,
|
||||
actionIsLoading: false,
|
||||
actionAreLoaded: false,
|
||||
socialIssuesClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialIssues").getAttribute("required") ? "required" : ""}`,
|
||||
socialActionsClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialActions").getAttribute("required") ? "required" : ""}`,
|
||||
};
|
||||
socialIssuesOther() {
|
||||
return this.$store.state.socialIssuesOther;
|
||||
},
|
||||
computed: {
|
||||
socialIssuesList() {
|
||||
return this.$store.state.activity.accompanyingPeriod.socialIssues;
|
||||
},
|
||||
socialIssuesSelected() {
|
||||
return this.$store.state.activity.socialIssues;
|
||||
},
|
||||
socialIssuesOther() {
|
||||
return this.$store.state.socialIssuesOther;
|
||||
},
|
||||
socialActionsList() {
|
||||
return this.$store.getters.socialActionsListSorted;
|
||||
},
|
||||
socialActionsSelected() {
|
||||
return this.$store.state.activity.socialActions;
|
||||
},
|
||||
socialActionsList() {
|
||||
return this.$store.getters.socialActionsListSorted;
|
||||
},
|
||||
mounted() {
|
||||
/* Load 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();
|
||||
});
|
||||
socialActionsSelected() {
|
||||
return this.$store.state.activity.socialActions;
|
||||
},
|
||||
methods: {
|
||||
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
||||
},
|
||||
mounted() {
|
||||
/* Load other issues in multiselect */
|
||||
this.issueIsLoading = true;
|
||||
this.actionAreLoaded = false;
|
||||
|
||||
getSocialIssues().then((response) => {
|
||||
/* Add issues to the store */
|
||||
this.$store.commit("updateIssuesOther", response);
|
||||
|
||||
/* Add in list the issues already associated (if not yet listed) */
|
||||
this.socialIssuesSelected.forEach((issue) => {
|
||||
if (
|
||||
this.socialIssuesList.filter((i) => i.id === issue.id).length !== 1
|
||||
) {
|
||||
this.$store.commit("addIssueInList", issue);
|
||||
}
|
||||
});
|
||||
|
||||
/* Remove from multiselect the issues that are not yet in the checkbox list */
|
||||
this.socialIssuesList.forEach((issue) => {
|
||||
this.$store.commit("removeIssueInOther", issue);
|
||||
});
|
||||
|
||||
/* Filter issues */
|
||||
this.$store.commit("filterList", "issues");
|
||||
|
||||
/* Add in list the actions already associated (if not yet listed) */
|
||||
this.socialActionsSelected.forEach((action) => {
|
||||
this.$store.commit("addActionInList", action);
|
||||
});
|
||||
|
||||
/* Filter actions */
|
||||
this.$store.commit("filterList", "actions");
|
||||
|
||||
this.issueIsLoading = false;
|
||||
this.actionAreLoaded = true;
|
||||
this.updateActionsList();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
||||
remove it from multiselect, and add socialActions concerned
|
||||
*/
|
||||
addIssueInList(value) {
|
||||
//console.log('addIssueInList', value);
|
||||
this.$store.commit("addIssueInList", value);
|
||||
this.$store.commit("removeIssueInOther", value);
|
||||
this.$store.dispatch("addIssueSelected", value);
|
||||
this.updateActionsList();
|
||||
},
|
||||
/* Update value for selected issues checkboxes
|
||||
*/
|
||||
updateIssuesSelected(issues) {
|
||||
//console.log('updateIssuesSelected', issues);
|
||||
this.$store.dispatch("updateIssuesSelected", issues);
|
||||
this.updateActionsList();
|
||||
},
|
||||
/* Update value for selected actions checkboxes
|
||||
*/
|
||||
updateActionsSelected(actions) {
|
||||
//console.log('updateActionsSelected', actions);
|
||||
this.$store.dispatch("updateActionsSelected", actions);
|
||||
},
|
||||
/* Add socialActions concerned: after reset, loop on each issue selected
|
||||
addIssueInList(value) {
|
||||
//console.log('addIssueInList', value);
|
||||
this.$store.commit("addIssueInList", value);
|
||||
this.$store.commit("removeIssueInOther", value);
|
||||
this.$store.dispatch("addIssueSelected", value);
|
||||
this.updateActionsList();
|
||||
},
|
||||
/* Update value for selected issues checkboxes
|
||||
*/
|
||||
updateIssuesSelected(issues) {
|
||||
//console.log('updateIssuesSelected', issues);
|
||||
this.$store.dispatch("updateIssuesSelected", issues);
|
||||
this.updateActionsList();
|
||||
},
|
||||
/* Update value for selected actions checkboxes
|
||||
*/
|
||||
updateActionsSelected(actions) {
|
||||
//console.log('updateActionsSelected', actions);
|
||||
this.$store.dispatch("updateActionsSelected", actions);
|
||||
},
|
||||
/* Add socialActions concerned: after reset, loop on each issue selected
|
||||
to get social actions concerned
|
||||
*/
|
||||
updateActionsList() {
|
||||
this.resetActionsList();
|
||||
this.socialIssuesSelected.forEach((item) => {
|
||||
this.actionIsLoading = true;
|
||||
getSocialActionByIssue(item.id).then(
|
||||
(actions) =>
|
||||
new Promise((resolve) => {
|
||||
actions.results.forEach((action) => {
|
||||
this.$store.commit("addActionInList", action);
|
||||
}, this);
|
||||
updateActionsList() {
|
||||
this.resetActionsList();
|
||||
this.socialIssuesSelected.forEach((item) => {
|
||||
this.actionIsLoading = true;
|
||||
getSocialActionByIssue(item.id).then(
|
||||
(actions) =>
|
||||
new Promise((resolve) => {
|
||||
actions.results.forEach((action) => {
|
||||
this.$store.commit("addActionInList", action);
|
||||
}, this);
|
||||
|
||||
this.$store.commit("filterList", "actions");
|
||||
this.$store.commit("filterList", "actions");
|
||||
|
||||
this.actionIsLoading = false;
|
||||
this.actionAreLoaded = true;
|
||||
resolve();
|
||||
}),
|
||||
);
|
||||
}, this);
|
||||
},
|
||||
/* Reset socialActions List: flush list and restore selected actions
|
||||
*/
|
||||
resetActionsList() {
|
||||
this.$store.commit("resetActionsList");
|
||||
this.actionAreLoaded = false;
|
||||
this.socialActionsSelected.forEach((item) => {
|
||||
this.$store.commit("addActionInList", item);
|
||||
}, this);
|
||||
},
|
||||
this.actionIsLoading = false;
|
||||
this.actionAreLoaded = true;
|
||||
resolve();
|
||||
}),
|
||||
);
|
||||
}, this);
|
||||
},
|
||||
/* Reset socialActions List: flush list and restore selected actions
|
||||
*/
|
||||
resetActionsList() {
|
||||
this.$store.commit("resetActionsList");
|
||||
this.actionAreLoaded = false;
|
||||
this.socialActionsSelected.forEach((item) => {
|
||||
this.$store.commit("addActionInList", item);
|
||||
}, this);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -263,18 +257,18 @@ export default {
|
||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||
|
||||
span.multiselect__single {
|
||||
display: none !important;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#actionsList {
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin: 0.5rem;
|
||||
background-color: whitesmoke;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin: 0.5rem;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
span.badge {
|
||||
margin-bottom: 0.5rem;
|
||||
@include badge_social($social-issue-color);
|
||||
margin-bottom: 0.5rem;
|
||||
@include badge_social($social-issue-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
<template>
|
||||
<span class="inline-choice">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
v-model="selected"
|
||||
name="action"
|
||||
:id="action.id"
|
||||
:value="action"
|
||||
/>
|
||||
<label class="form-check-label" :for="action.id">
|
||||
<span class="badge bg-light text-dark" :title="action.text">{{
|
||||
action.text
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
<span class="inline-choice">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
v-model="selected"
|
||||
name="action"
|
||||
:id="action.id"
|
||||
:value="action"
|
||||
/>
|
||||
<label class="form-check-label" :for="action.id">
|
||||
<span class="badge bg-light text-dark" :title="action.text">{{
|
||||
action.text
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "CheckSocialAction",
|
||||
props: ["action", "selection"],
|
||||
emits: ["updateSelected"],
|
||||
computed: {
|
||||
selected: {
|
||||
set(value) {
|
||||
this.$emit("updateSelected", value);
|
||||
},
|
||||
get() {
|
||||
return this.selection;
|
||||
},
|
||||
},
|
||||
name: "CheckSocialAction",
|
||||
props: ["action", "selection"],
|
||||
emits: ["updateSelected"],
|
||||
computed: {
|
||||
selected: {
|
||||
set(value) {
|
||||
this.$emit("updateSelected", value);
|
||||
},
|
||||
get() {
|
||||
return this.selection;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -41,13 +41,13 @@ export default {
|
||||
@import "ChillPersonAssets/chill/scss/mixins";
|
||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||
span.badge {
|
||||
@include badge_social($social-action-color);
|
||||
font-size: 95%;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 1em;
|
||||
max-width: 100%; /* Adjust as needed */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@include badge_social($social-action-color);
|
||||
font-size: 95%;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 1em;
|
||||
max-width: 100%; /* Adjust as needed */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
<template>
|
||||
<span class="inline-choice">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
v-model="selected"
|
||||
name="issue"
|
||||
:id="issue.id"
|
||||
:value="issue"
|
||||
/>
|
||||
<label class="form-check-label" :for="issue.id">
|
||||
<span class="badge bg-chill-l-gray text-dark">{{
|
||||
issue.text
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
<span class="inline-choice">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
v-model="selected"
|
||||
name="issue"
|
||||
:id="issue.id"
|
||||
:value="issue"
|
||||
/>
|
||||
<label class="form-check-label" :for="issue.id">
|
||||
<span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "CheckSocialIssue",
|
||||
props: ["issue", "selection"],
|
||||
emits: ["updateSelected"],
|
||||
computed: {
|
||||
selected: {
|
||||
set(value) {
|
||||
this.$emit("updateSelected", value);
|
||||
},
|
||||
get() {
|
||||
return this.selection;
|
||||
},
|
||||
},
|
||||
name: "CheckSocialIssue",
|
||||
props: ["issue", "selection"],
|
||||
emits: ["updateSelected"],
|
||||
computed: {
|
||||
selected: {
|
||||
set(value) {
|
||||
this.$emit("updateSelected", value);
|
||||
},
|
||||
get() {
|
||||
return this.selection;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -41,9 +39,9 @@ export default {
|
||||
@import "ChillPersonAssets/chill/scss/mixins";
|
||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||
span.badge {
|
||||
@include badge_social($social-issue-color);
|
||||
font-size: 95%;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 1em;
|
||||
@include badge_social($social-issue-color);
|
||||
font-size: 95%;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,76 +1,74 @@
|
||||
import { EventInput } from "@fullcalendar/core";
|
||||
import {
|
||||
DateTime,
|
||||
Location,
|
||||
User,
|
||||
UserAssociatedInterface,
|
||||
DateTime,
|
||||
Location,
|
||||
User,
|
||||
UserAssociatedInterface,
|
||||
} from "../../../ChillMainBundle/Resources/public/types";
|
||||
import { Person } from "../../../ChillPersonBundle/Resources/public/types";
|
||||
|
||||
export interface CalendarRange {
|
||||
id: number;
|
||||
endDate: DateTime;
|
||||
startDate: DateTime;
|
||||
user: User;
|
||||
location: Location;
|
||||
createdAt: DateTime;
|
||||
createdBy: User;
|
||||
updatedAt: DateTime;
|
||||
updatedBy: User;
|
||||
id: number;
|
||||
endDate: DateTime;
|
||||
startDate: DateTime;
|
||||
user: User;
|
||||
location: Location;
|
||||
createdAt: DateTime;
|
||||
createdBy: User;
|
||||
updatedAt: DateTime;
|
||||
updatedBy: User;
|
||||
}
|
||||
|
||||
export interface CalendarRangeCreate {
|
||||
user: UserAssociatedInterface;
|
||||
startDate: DateTime;
|
||||
endDate: DateTime;
|
||||
location: Location;
|
||||
user: UserAssociatedInterface;
|
||||
startDate: DateTime;
|
||||
endDate: DateTime;
|
||||
location: Location;
|
||||
}
|
||||
|
||||
export interface CalendarRangeEdit {
|
||||
startDate?: DateTime;
|
||||
endDate?: DateTime;
|
||||
location?: Location;
|
||||
startDate?: DateTime;
|
||||
endDate?: DateTime;
|
||||
location?: Location;
|
||||
}
|
||||
|
||||
export interface Calendar {
|
||||
id: number;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface CalendarLight {
|
||||
id: number;
|
||||
endDate: DateTime;
|
||||
startDate: DateTime;
|
||||
mainUser: User;
|
||||
persons: Person[];
|
||||
status: "valid" | "moved" | "canceled";
|
||||
id: number;
|
||||
endDate: DateTime;
|
||||
startDate: DateTime;
|
||||
mainUser: User;
|
||||
persons: Person[];
|
||||
status: "valid" | "moved" | "canceled";
|
||||
}
|
||||
|
||||
export interface CalendarRemote {
|
||||
id: number;
|
||||
endDate: DateTime;
|
||||
startDate: DateTime;
|
||||
title: string;
|
||||
isAllDay: boolean;
|
||||
id: number;
|
||||
endDate: DateTime;
|
||||
startDate: DateTime;
|
||||
title: string;
|
||||
isAllDay: boolean;
|
||||
}
|
||||
|
||||
export type EventInputCalendarRange = EventInput & {
|
||||
id: string;
|
||||
userId: number;
|
||||
userLabel: string;
|
||||
calendarRangeId: number;
|
||||
locationId: number;
|
||||
locationName: string;
|
||||
start: string;
|
||||
end: string;
|
||||
is: "range";
|
||||
id: string;
|
||||
userId: number;
|
||||
userLabel: string;
|
||||
calendarRangeId: number;
|
||||
locationId: number;
|
||||
locationName: string;
|
||||
start: string;
|
||||
end: string;
|
||||
is: "range";
|
||||
};
|
||||
|
||||
export function isEventInputCalendarRange(
|
||||
toBeDetermined: EventInputCalendarRange | EventInput,
|
||||
toBeDetermined: EventInputCalendarRange | EventInput,
|
||||
): toBeDetermined is EventInputCalendarRange {
|
||||
return (
|
||||
typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
|
||||
);
|
||||
return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range";
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
@@ -1,164 +1,146 @@
|
||||
<template>
|
||||
<teleport to="#mainUser">
|
||||
<h2 class="chill-red">Utilisateur principal</h2>
|
||||
<div>
|
||||
<div>
|
||||
<div v-if="null !== this.$store.getters.getMainUser">
|
||||
<calendar-active :user="this.$store.getters.getMainUser" />
|
||||
</div>
|
||||
<pick-entity
|
||||
:multiple="false"
|
||||
:types="['user']"
|
||||
:uniqid="'main_user_calendar'"
|
||||
:picked="
|
||||
null !== this.$store.getters.getMainUser
|
||||
? [this.$store.getters.getMainUser]
|
||||
: []
|
||||
"
|
||||
:removable-if-set="false"
|
||||
:display-picked="false"
|
||||
:suggested="this.suggestedUsers"
|
||||
:label="'main_user'"
|
||||
@add-new-entity="setMainUser"
|
||||
/>
|
||||
</div>
|
||||
<teleport to="#mainUser">
|
||||
<h2 class="chill-red">Utilisateur principal</h2>
|
||||
<div>
|
||||
<div>
|
||||
<div v-if="null !== this.$store.getters.getMainUser">
|
||||
<calendar-active :user="this.$store.getters.getMainUser" />
|
||||
</div>
|
||||
</teleport>
|
||||
<pick-entity
|
||||
:multiple="false"
|
||||
:types="['user']"
|
||||
:uniqid="'main_user_calendar'"
|
||||
:picked="
|
||||
null !== this.$store.getters.getMainUser
|
||||
? [this.$store.getters.getMainUser]
|
||||
: []
|
||||
"
|
||||
:removable-if-set="false"
|
||||
:display-picked="false"
|
||||
:suggested="this.suggestedUsers"
|
||||
:label="'main_user'"
|
||||
@add-new-entity="setMainUser"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
|
||||
<concerned-groups />
|
||||
<concerned-groups />
|
||||
|
||||
<teleport to="#schedule">
|
||||
<div class="row mb-3" v-if="activity.startDate !== null">
|
||||
<label class="col-form-label col-sm-4">Date</label>
|
||||
<div class="col-sm-8">
|
||||
{{ $d(activity.startDate, "long") }} -
|
||||
{{ $d(activity.endDate, "hoursOnly") }}
|
||||
<span v-if="activity.calendarRange === null"
|
||||
>(Pas de plage de disponibilité sélectionnée)</span
|
||||
>
|
||||
<span v-else>(Une plage de disponibilité sélectionnée)</span>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
|
||||
<location />
|
||||
|
||||
<teleport to="#fullCalendar">
|
||||
<div class="calendar-actives">
|
||||
<template v-for="u in getActiveUsers" :key="u.id">
|
||||
<calendar-active
|
||||
:user="u"
|
||||
:invite="this.$store.getters.getInviteForUser(u)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
class="display-options row justify-content-between"
|
||||
style="margin-top: 1rem"
|
||||
<teleport to="#schedule">
|
||||
<div class="row mb-3" v-if="activity.startDate !== null">
|
||||
<label class="col-form-label col-sm-4">Date</label>
|
||||
<div class="col-sm-8">
|
||||
{{ $d(activity.startDate, "long") }} -
|
||||
{{ $d(activity.endDate, "hoursOnly") }}
|
||||
<span v-if="activity.calendarRange === null"
|
||||
>(Pas de plage de disponibilité sélectionnée)</span
|
||||
>
|
||||
<div class="col-sm-9 col-xs-12">
|
||||
<div class="input-group mb-3">
|
||||
<label class="input-group-text" for="slotDuration"
|
||||
>Durée des créneaux</label
|
||||
>
|
||||
<select
|
||||
v-model="slotDuration"
|
||||
id="slotDuration"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="00:05:00">5 minutes</option>
|
||||
<option value="00:10:00">10 minutes</option>
|
||||
<option value="00:15:00">15 minutes</option>
|
||||
<option value="00:30:00">30 minutes</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMinTime">De</label>
|
||||
<select
|
||||
v-model="slotMinTime"
|
||||
id="slotMinTime"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="00:00:00">0h</option>
|
||||
<option value="01:00:00">1h</option>
|
||||
<option value="02:00:00">2h</option>
|
||||
<option value="03:00:00">3h</option>
|
||||
<option value="04:00:00">4h</option>
|
||||
<option value="05:00:00">5h</option>
|
||||
<option value="06:00:00">6h</option>
|
||||
<option value="07:00:00">7h</option>
|
||||
<option value="08:00:00">8h</option>
|
||||
<option value="09:00:00">9h</option>
|
||||
<option value="10:00:00">10h</option>
|
||||
<option value="11:00:00">11h</option>
|
||||
<option value="12:00:00">12h</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMaxTime">À</label>
|
||||
<select
|
||||
v-model="slotMaxTime"
|
||||
id="slotMaxTime"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="12:00:00">12h</option>
|
||||
<option value="13:00:00">13h</option>
|
||||
<option value="14:00:00">14h</option>
|
||||
<option value="15:00:00">15h</option>
|
||||
<option value="16:00:00">16h</option>
|
||||
<option value="17:00:00">17h</option>
|
||||
<option value="18:00:00">18h</option>
|
||||
<option value="19:00:00">19h</option>
|
||||
<option value="20:00:00">20h</option>
|
||||
<option value="21:00:00">21h</option>
|
||||
<option value="22:00:00">22h</option>
|
||||
<option value="23:00:00">23h</option>
|
||||
<option value="23:59:59">24h</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3 col-xs-12">
|
||||
<div class="float-end">
|
||||
<div class="form-check input-group">
|
||||
<span class="input-group-text">
|
||||
<input
|
||||
id="showHideWE"
|
||||
class="mt-0"
|
||||
type="checkbox"
|
||||
v-model="hideWeekends"
|
||||
/>
|
||||
</span>
|
||||
<label
|
||||
for="showHideWE"
|
||||
class="form-check-label input-group-text"
|
||||
>Week-ends</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>(Une plage de disponibilité sélectionnée)</span>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
|
||||
<location />
|
||||
|
||||
<teleport to="#fullCalendar">
|
||||
<div class="calendar-actives">
|
||||
<template v-for="u in getActiveUsers" :key="u.id">
|
||||
<calendar-active
|
||||
:user="u"
|
||||
:invite="this.$store.getters.getInviteForUser(u)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
class="display-options row justify-content-between"
|
||||
style="margin-top: 1rem"
|
||||
>
|
||||
<div class="col-sm-9 col-xs-12">
|
||||
<div class="input-group mb-3">
|
||||
<label class="input-group-text" for="slotDuration"
|
||||
>Durée des créneaux</label
|
||||
>
|
||||
<select v-model="slotDuration" id="slotDuration" class="form-select">
|
||||
<option value="00:05:00">5 minutes</option>
|
||||
<option value="00:10:00">10 minutes</option>
|
||||
<option value="00:15:00">15 minutes</option>
|
||||
<option value="00:30:00">30 minutes</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMinTime">De</label>
|
||||
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
|
||||
<option value="00:00:00">0h</option>
|
||||
<option value="01:00:00">1h</option>
|
||||
<option value="02:00:00">2h</option>
|
||||
<option value="03:00:00">3h</option>
|
||||
<option value="04:00:00">4h</option>
|
||||
<option value="05:00:00">5h</option>
|
||||
<option value="06:00:00">6h</option>
|
||||
<option value="07:00:00">7h</option>
|
||||
<option value="08:00:00">8h</option>
|
||||
<option value="09:00:00">9h</option>
|
||||
<option value="10:00:00">10h</option>
|
||||
<option value="11:00:00">11h</option>
|
||||
<option value="12:00:00">12h</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMaxTime">À</label>
|
||||
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
|
||||
<option value="12:00:00">12h</option>
|
||||
<option value="13:00:00">13h</option>
|
||||
<option value="14:00:00">14h</option>
|
||||
<option value="15:00:00">15h</option>
|
||||
<option value="16:00:00">16h</option>
|
||||
<option value="17:00:00">17h</option>
|
||||
<option value="18:00:00">18h</option>
|
||||
<option value="19:00:00">19h</option>
|
||||
<option value="20:00:00">20h</option>
|
||||
<option value="21:00:00">21h</option>
|
||||
<option value="22:00:00">22h</option>
|
||||
<option value="23:00:00">23h</option>
|
||||
<option value="23:59:59">24h</option>
|
||||
</select>
|
||||
</div>
|
||||
<FullCalendar ref="fullCalendar" :options="calendarOptions">
|
||||
<template #eventContent="arg">
|
||||
<span>
|
||||
<b v-if="arg.event.extendedProps.is === 'remote'">{{
|
||||
arg.event.title
|
||||
}}</b>
|
||||
<b v-else-if="arg.event.extendedProps.is === 'range'"
|
||||
>{{ arg.timeText }}
|
||||
{{ arg.event.extendedProps.locationName }}
|
||||
<small>{{
|
||||
arg.event.extendedProps.userLabel
|
||||
}}</small></b
|
||||
>
|
||||
<b v-else-if="arg.event.extendedProps.is === 'current'"
|
||||
>{{ arg.timeText }} {{ $t("current_selected") }}
|
||||
</b>
|
||||
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
|
||||
arg.event.title
|
||||
}}</b>
|
||||
<b v-else
|
||||
>{{ arg.timeText }} {{ $t("current_selected") }}
|
||||
</b>
|
||||
</span>
|
||||
</template>
|
||||
</FullCalendar>
|
||||
</teleport>
|
||||
</div>
|
||||
<div class="col-sm-3 col-xs-12">
|
||||
<div class="float-end">
|
||||
<div class="form-check input-group">
|
||||
<span class="input-group-text">
|
||||
<input
|
||||
id="showHideWE"
|
||||
class="mt-0"
|
||||
type="checkbox"
|
||||
v-model="hideWeekends"
|
||||
/>
|
||||
</span>
|
||||
<label for="showHideWE" class="form-check-label input-group-text"
|
||||
>Week-ends</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FullCalendar ref="fullCalendar" :options="calendarOptions">
|
||||
<template #eventContent="arg">
|
||||
<span>
|
||||
<b v-if="arg.event.extendedProps.is === 'remote'">{{
|
||||
arg.event.title
|
||||
}}</b>
|
||||
<b v-else-if="arg.event.extendedProps.is === 'range'"
|
||||
>{{ arg.timeText }}
|
||||
{{ arg.event.extendedProps.locationName }}
|
||||
<small>{{ arg.event.extendedProps.userLabel }}</small></b
|
||||
>
|
||||
<b v-else-if="arg.event.extendedProps.is === 'current'"
|
||||
>{{ arg.timeText }} {{ $t("current_selected") }}
|
||||
</b>
|
||||
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
|
||||
arg.event.title
|
||||
}}</b>
|
||||
<b v-else>{{ arg.timeText }} {{ $t("current_selected") }} </b>
|
||||
</span>
|
||||
</template>
|
||||
</FullCalendar>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -175,219 +157,210 @@ import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
|
||||
import { mapGetters, mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
Location,
|
||||
FullCalendar,
|
||||
CalendarActive,
|
||||
PickEntity,
|
||||
name: "App",
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
Location,
|
||||
FullCalendar,
|
||||
CalendarActive,
|
||||
PickEntity,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorMsg: [],
|
||||
showMyCalendar: false,
|
||||
slotDuration: "00:05:00",
|
||||
slotMinTime: "09:00:00",
|
||||
slotMaxTime: "18:00:00",
|
||||
hideWeekEnds: true,
|
||||
previousUser: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["getMainUser"]),
|
||||
...mapState(["activity"]),
|
||||
events() {
|
||||
return this.$store.getters.getEventSources;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorMsg: [],
|
||||
showMyCalendar: false,
|
||||
slotDuration: "00:05:00",
|
||||
slotMinTime: "09:00:00",
|
||||
slotMaxTime: "18:00:00",
|
||||
hideWeekEnds: true,
|
||||
previousUser: [],
|
||||
};
|
||||
calendarOptions() {
|
||||
return {
|
||||
locale: frLocale,
|
||||
plugins: [
|
||||
dayGridPlugin,
|
||||
interactionPlugin,
|
||||
timeGridPlugin,
|
||||
dayGridPlugin,
|
||||
listPlugin,
|
||||
],
|
||||
initialView: "timeGridWeek",
|
||||
initialDate: this.$store.getters.getInitialDate,
|
||||
eventSources: this.events,
|
||||
selectable: true,
|
||||
slotMinTime: this.slotMinTime,
|
||||
slotMaxTime: this.slotMaxTime,
|
||||
scrollTimeReset: false,
|
||||
datesSet: this.onDatesSet,
|
||||
select: this.onDateSelect,
|
||||
eventChange: this.onEventChange,
|
||||
eventClick: this.onEventClick,
|
||||
selectMirror: true,
|
||||
editable: true,
|
||||
weekends: !this.hideWeekEnds,
|
||||
headerToolbar: {
|
||||
left: "prev,next today",
|
||||
center: "title",
|
||||
right: "timeGridWeek,timeGridDay,listWeek",
|
||||
},
|
||||
views: {
|
||||
timeGrid: {
|
||||
slotEventOverlap: false,
|
||||
slotDuration: this.slotDuration,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["getMainUser"]),
|
||||
...mapState(["activity"]),
|
||||
events() {
|
||||
return this.$store.getters.getEventSources;
|
||||
},
|
||||
calendarOptions() {
|
||||
return {
|
||||
locale: frLocale,
|
||||
plugins: [
|
||||
dayGridPlugin,
|
||||
interactionPlugin,
|
||||
timeGridPlugin,
|
||||
dayGridPlugin,
|
||||
listPlugin,
|
||||
],
|
||||
initialView: "timeGridWeek",
|
||||
initialDate: this.$store.getters.getInitialDate,
|
||||
eventSources: this.events,
|
||||
selectable: true,
|
||||
slotMinTime: this.slotMinTime,
|
||||
slotMaxTime: this.slotMaxTime,
|
||||
scrollTimeReset: false,
|
||||
datesSet: this.onDatesSet,
|
||||
select: this.onDateSelect,
|
||||
eventChange: this.onEventChange,
|
||||
eventClick: this.onEventClick,
|
||||
selectMirror: true,
|
||||
editable: true,
|
||||
weekends: !this.hideWeekEnds,
|
||||
headerToolbar: {
|
||||
left: "prev,next today",
|
||||
center: "title",
|
||||
right: "timeGridWeek,timeGridDay,listWeek",
|
||||
},
|
||||
views: {
|
||||
timeGrid: {
|
||||
slotEventOverlap: false,
|
||||
slotDuration: this.slotDuration,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
getActiveUsers() {
|
||||
const users = [];
|
||||
for (const id of this.$store.state.currentView.users.keys()) {
|
||||
users.push(this.$store.getters.getUserDataById(id).user);
|
||||
}
|
||||
return users;
|
||||
},
|
||||
suggestedUsers() {
|
||||
const suggested = [];
|
||||
|
||||
this.$data.previousUser.forEach((u) => {
|
||||
if (u.id !== this.$store.getters.getMainUser.id) {
|
||||
suggested.push(u);
|
||||
}
|
||||
});
|
||||
|
||||
return suggested;
|
||||
},
|
||||
getActiveUsers() {
|
||||
const users = [];
|
||||
for (const id of this.$store.state.currentView.users.keys()) {
|
||||
users.push(this.$store.getters.getUserDataById(id).user);
|
||||
}
|
||||
return users;
|
||||
},
|
||||
methods: {
|
||||
setMainUser({ entity }) {
|
||||
const user = entity;
|
||||
console.log("setMainUser APP", entity);
|
||||
suggestedUsers() {
|
||||
const suggested = [];
|
||||
|
||||
if (
|
||||
user.id !== this.$store.getters.getMainUser &&
|
||||
(this.$store.state.activity.calendarRange !== null ||
|
||||
this.$store.state.activity.startDate !== null ||
|
||||
this.$store.state.activity.endDate !== null)
|
||||
) {
|
||||
if (
|
||||
!window.confirm(
|
||||
this.$t("change_main_user_will_reset_event_data"),
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.$data.previousUser.forEach((u) => {
|
||||
if (u.id !== this.$store.getters.getMainUser.id) {
|
||||
suggested.push(u);
|
||||
}
|
||||
});
|
||||
|
||||
// add the previous user, if any, in the previous user list (in use for suggestion)
|
||||
if (null !== this.$store.getters.getMainUser) {
|
||||
const suggestedUids = new Set(
|
||||
this.$data.previousUser.map((u) => u.id),
|
||||
);
|
||||
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
|
||||
this.$data.previousUser.push(
|
||||
this.$store.getters.getMainUser,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.$store.dispatch("setMainUser", user);
|
||||
this.$store.commit("showUserOnCalendar", {
|
||||
user,
|
||||
ranges: true,
|
||||
remotes: true,
|
||||
});
|
||||
},
|
||||
removeMainUser(user) {
|
||||
console.log("removeMainUser APP", user);
|
||||
|
||||
window.alert(this.$t("main_user_is_mandatory"));
|
||||
return;
|
||||
},
|
||||
onDatesSet(event) {
|
||||
console.log("onDatesSet", event);
|
||||
this.$store.dispatch("setCurrentDatesView", {
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
});
|
||||
},
|
||||
onDateSelect(payload) {
|
||||
console.log("onDateSelect", payload);
|
||||
|
||||
// show an alert if changing mainUser
|
||||
if (
|
||||
(this.$store.getters.getMainUser !== null &&
|
||||
this.$store.state.me.id !==
|
||||
this.$store.getters.getMainUser.id) ||
|
||||
this.$store.getters.getMainUser === null
|
||||
) {
|
||||
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
|
||||
return;
|
||||
} else {
|
||||
this.$store.commit("showUserOnCalendar", {
|
||||
user: this.$store.state.me,
|
||||
remotes: true,
|
||||
ranges: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.$store.dispatch("setEventTimes", {
|
||||
start: payload.start,
|
||||
end: payload.end,
|
||||
});
|
||||
},
|
||||
onEventChange(payload) {
|
||||
console.log("onEventChange", payload);
|
||||
if (this.$store.state.activity.calendarRange !== null) {
|
||||
throw new Error(
|
||||
"not allowed to edit a calendar associated with a calendar range",
|
||||
);
|
||||
}
|
||||
this.$store.dispatch("setEventTimes", {
|
||||
start: payload.event.start,
|
||||
end: payload.event.end,
|
||||
});
|
||||
},
|
||||
onEventClick(payload) {
|
||||
if (payload.event.extendedProps.is !== "range") {
|
||||
// do nothing when clicking on remote
|
||||
return;
|
||||
}
|
||||
|
||||
// show an alert if changing mainUser
|
||||
if (
|
||||
this.$store.getters.getMainUser !== null &&
|
||||
payload.event.extendedProps.userId !==
|
||||
this.$store.getters.getMainUser.id
|
||||
) {
|
||||
if (
|
||||
!window.confirm(
|
||||
this.$t("this_calendar_range_will_change_main_user"),
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.$store.dispatch("associateCalendarToRange", {
|
||||
range: payload.event,
|
||||
});
|
||||
},
|
||||
return suggested;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setMainUser({ entity }) {
|
||||
const user = entity;
|
||||
console.log("setMainUser APP", entity);
|
||||
|
||||
if (
|
||||
user.id !== this.$store.getters.getMainUser &&
|
||||
(this.$store.state.activity.calendarRange !== null ||
|
||||
this.$store.state.activity.startDate !== null ||
|
||||
this.$store.state.activity.endDate !== null)
|
||||
) {
|
||||
if (
|
||||
!window.confirm(this.$t("change_main_user_will_reset_event_data"))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// add the previous user, if any, in the previous user list (in use for suggestion)
|
||||
if (null !== this.$store.getters.getMainUser) {
|
||||
const suggestedUids = new Set(this.$data.previousUser.map((u) => u.id));
|
||||
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
|
||||
this.$data.previousUser.push(this.$store.getters.getMainUser);
|
||||
}
|
||||
}
|
||||
|
||||
this.$store.dispatch("setMainUser", user);
|
||||
this.$store.commit("showUserOnCalendar", {
|
||||
user,
|
||||
ranges: true,
|
||||
remotes: true,
|
||||
});
|
||||
},
|
||||
removeMainUser(user) {
|
||||
console.log("removeMainUser APP", user);
|
||||
|
||||
window.alert(this.$t("main_user_is_mandatory"));
|
||||
return;
|
||||
},
|
||||
onDatesSet(event) {
|
||||
console.log("onDatesSet", event);
|
||||
this.$store.dispatch("setCurrentDatesView", {
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
});
|
||||
},
|
||||
onDateSelect(payload) {
|
||||
console.log("onDateSelect", payload);
|
||||
|
||||
// show an alert if changing mainUser
|
||||
if (
|
||||
(this.$store.getters.getMainUser !== null &&
|
||||
this.$store.state.me.id !== this.$store.getters.getMainUser.id) ||
|
||||
this.$store.getters.getMainUser === null
|
||||
) {
|
||||
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
|
||||
return;
|
||||
} else {
|
||||
this.$store.commit("showUserOnCalendar", {
|
||||
user: this.$store.state.me,
|
||||
remotes: true,
|
||||
ranges: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.$store.dispatch("setEventTimes", {
|
||||
start: payload.start,
|
||||
end: payload.end,
|
||||
});
|
||||
},
|
||||
onEventChange(payload) {
|
||||
console.log("onEventChange", payload);
|
||||
if (this.$store.state.activity.calendarRange !== null) {
|
||||
throw new Error(
|
||||
"not allowed to edit a calendar associated with a calendar range",
|
||||
);
|
||||
}
|
||||
this.$store.dispatch("setEventTimes", {
|
||||
start: payload.event.start,
|
||||
end: payload.event.end,
|
||||
});
|
||||
},
|
||||
onEventClick(payload) {
|
||||
if (payload.event.extendedProps.is !== "range") {
|
||||
// do nothing when clicking on remote
|
||||
return;
|
||||
}
|
||||
|
||||
// show an alert if changing mainUser
|
||||
if (
|
||||
this.$store.getters.getMainUser !== null &&
|
||||
payload.event.extendedProps.userId !==
|
||||
this.$store.getters.getMainUser.id
|
||||
) {
|
||||
if (
|
||||
!window.confirm(this.$t("this_calendar_range_will_change_main_user"))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.$store.dispatch("associateCalendarToRange", {
|
||||
range: payload.event,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.calendar-actives {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.display-options {
|
||||
margin-top: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* for events which are range */
|
||||
.fc-event.isrange {
|
||||
border-width: 3px;
|
||||
border-width: 3px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,119 +1,105 @@
|
||||
<template>
|
||||
<div :style="style" class="calendar-active">
|
||||
<span class="badge-user">
|
||||
{{ user.text }}
|
||||
<template v-if="invite !== null">
|
||||
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
|
||||
<i
|
||||
v-else-if="invite.status === 'declined'"
|
||||
class="fa fa-times"
|
||||
/>
|
||||
<i
|
||||
v-else-if="invite.status === 'pending'"
|
||||
class="fa fa-question-o"
|
||||
/>
|
||||
<i
|
||||
v-else-if="invite.status === 'tentative'"
|
||||
class="fa fa-question"
|
||||
/>
|
||||
<span v-else="">{{ invite.status }}</span>
|
||||
</template>
|
||||
</span>
|
||||
<span class="form-check-inline form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="flexSwitchCheckDefault"
|
||||
v-model="rangeShow"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="flexSwitchCheckDefault"
|
||||
title="Disponibilités"
|
||||
><i class="fa fa-calendar-check-o"
|
||||
/></label>
|
||||
</span>
|
||||
<span class="form-check-inline form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="flexSwitchCheckDefault"
|
||||
v-model="remoteShow"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="flexSwitchCheckDefault"
|
||||
title="Agenda"
|
||||
><i class="fa fa-calendar"
|
||||
/></label>
|
||||
</span>
|
||||
</div>
|
||||
<div :style="style" class="calendar-active">
|
||||
<span class="badge-user">
|
||||
{{ user.text }}
|
||||
<template v-if="invite !== null">
|
||||
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
|
||||
<i v-else-if="invite.status === 'declined'" class="fa fa-times" />
|
||||
<i v-else-if="invite.status === 'pending'" class="fa fa-question-o" />
|
||||
<i v-else-if="invite.status === 'tentative'" class="fa fa-question" />
|
||||
<span v-else="">{{ invite.status }}</span>
|
||||
</template>
|
||||
</span>
|
||||
<span class="form-check-inline form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="flexSwitchCheckDefault"
|
||||
v-model="rangeShow"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="flexSwitchCheckDefault"
|
||||
title="Disponibilités"
|
||||
><i class="fa fa-calendar-check-o"
|
||||
/></label>
|
||||
</span>
|
||||
<span class="form-check-inline form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="flexSwitchCheckDefault"
|
||||
v-model="remoteShow"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="flexSwitchCheckDefault"
|
||||
title="Agenda"
|
||||
><i class="fa fa-calendar"
|
||||
/></label>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "CalendarActive",
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
invite: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
name: "CalendarActive",
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
backgroundColor: this.$store.getters.getUserData(this.user)
|
||||
.mainColor,
|
||||
};
|
||||
},
|
||||
rangeShow: {
|
||||
set(value) {
|
||||
this.$store.commit("showUserOnCalendar", {
|
||||
user: this.user,
|
||||
ranges: value,
|
||||
});
|
||||
},
|
||||
get() {
|
||||
return this.$store.getters.isRangeShownOnCalendarForUser(
|
||||
this.user,
|
||||
);
|
||||
},
|
||||
},
|
||||
remoteShow: {
|
||||
set(value) {
|
||||
this.$store.commit("showUserOnCalendar", {
|
||||
user: this.user,
|
||||
remotes: value,
|
||||
});
|
||||
},
|
||||
get() {
|
||||
return this.$store.getters.isRemoteShownOnCalendarForUser(
|
||||
this.user,
|
||||
);
|
||||
},
|
||||
},
|
||||
invite: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
backgroundColor: this.$store.getters.getUserData(this.user).mainColor,
|
||||
};
|
||||
},
|
||||
rangeShow: {
|
||||
set(value) {
|
||||
this.$store.commit("showUserOnCalendar", {
|
||||
user: this.user,
|
||||
ranges: value,
|
||||
});
|
||||
},
|
||||
get() {
|
||||
return this.$store.getters.isRangeShownOnCalendarForUser(this.user);
|
||||
},
|
||||
},
|
||||
remoteShow: {
|
||||
set(value) {
|
||||
this.$store.commit("showUserOnCalendar", {
|
||||
user: this.user,
|
||||
remotes: value,
|
||||
});
|
||||
},
|
||||
get() {
|
||||
return this.$store.getters.isRemoteShownOnCalendarForUser(this.user);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.calendar-active {
|
||||
margin: 0 0.25rem 0.25rem 0;
|
||||
padding: 0.5rem;
|
||||
margin: 0 0.25rem 0.25rem 0;
|
||||
padding: 0.5rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
color: var(--bs-blue);
|
||||
color: var(--bs-blue);
|
||||
|
||||
& > .badge-user {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
& > .badge-user {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,37 +14,37 @@ export { whoami } from "../../../../../ChillMainBundle/Resources/public/lib/api/
|
||||
* @return Promise
|
||||
*/
|
||||
export const fetchCalendarRangeForUser = (
|
||||
user: User,
|
||||
start: Date,
|
||||
end: Date,
|
||||
user: User,
|
||||
start: Date,
|
||||
end: Date,
|
||||
): Promise<CalendarRange[]> => {
|
||||
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
|
||||
const dateFrom = datetimeToISO(start);
|
||||
const dateTo = datetimeToISO(end);
|
||||
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
|
||||
const dateFrom = datetimeToISO(start);
|
||||
const dateTo = datetimeToISO(end);
|
||||
|
||||
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
|
||||
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
|
||||
};
|
||||
|
||||
export const fetchCalendarRemoteForUser = (
|
||||
user: User,
|
||||
start: Date,
|
||||
end: Date,
|
||||
user: User,
|
||||
start: Date,
|
||||
end: Date,
|
||||
): Promise<CalendarRemote[]> => {
|
||||
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
|
||||
const dateFrom = datetimeToISO(start);
|
||||
const dateTo = datetimeToISO(end);
|
||||
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
|
||||
const dateFrom = datetimeToISO(start);
|
||||
const dateTo = datetimeToISO(end);
|
||||
|
||||
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
|
||||
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
|
||||
};
|
||||
|
||||
export const fetchCalendarLocalForUser = (
|
||||
user: User,
|
||||
start: Date,
|
||||
end: Date,
|
||||
user: User,
|
||||
start: Date,
|
||||
end: Date,
|
||||
): Promise<CalendarLight[]> => {
|
||||
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
|
||||
const dateFrom = datetimeToISO(start);
|
||||
const dateTo = datetimeToISO(end);
|
||||
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
|
||||
const dateFrom = datetimeToISO(start);
|
||||
const dateTo = datetimeToISO(end);
|
||||
|
||||
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
|
||||
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
|
||||
};
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
const COLORS = [
|
||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||
"#8dd3c7",
|
||||
"#ffffb3",
|
||||
"#bebada",
|
||||
"#fb8072",
|
||||
"#80b1d3",
|
||||
"#fdb462",
|
||||
"#b3de69",
|
||||
"#fccde5",
|
||||
"#d9d9d9",
|
||||
"#bc80bd",
|
||||
"#ccebc5",
|
||||
"#ffed6f",
|
||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||
"#8dd3c7",
|
||||
"#ffffb3",
|
||||
"#bebada",
|
||||
"#fb8072",
|
||||
"#80b1d3",
|
||||
"#fdb462",
|
||||
"#b3de69",
|
||||
"#fccde5",
|
||||
"#d9d9d9",
|
||||
"#bc80bd",
|
||||
"#ccebc5",
|
||||
"#ffed6f",
|
||||
];
|
||||
|
||||
export { COLORS };
|
||||
|
||||
@@ -1,117 +1,117 @@
|
||||
import { COLORS } from "../const";
|
||||
import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||
import {
|
||||
DateTime,
|
||||
User,
|
||||
DateTime,
|
||||
User,
|
||||
} from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||
import { CalendarLight, CalendarRange, CalendarRemote } from "../../../types";
|
||||
import type { EventInputCalendarRange } from "../../../types";
|
||||
import { EventInput } from "@fullcalendar/core";
|
||||
|
||||
export interface UserData {
|
||||
user: User;
|
||||
calendarRanges: CalendarRange[];
|
||||
calendarRangesLoaded: {}[];
|
||||
remotes: CalendarRemote[];
|
||||
remotesLoaded: {}[];
|
||||
locals: CalendarRemote[];
|
||||
localsLoaded: {}[];
|
||||
mainColor: string;
|
||||
user: User;
|
||||
calendarRanges: CalendarRange[];
|
||||
calendarRangesLoaded: {}[];
|
||||
remotes: CalendarRemote[];
|
||||
remotesLoaded: {}[];
|
||||
locals: CalendarRemote[];
|
||||
localsLoaded: {}[];
|
||||
mainColor: string;
|
||||
}
|
||||
|
||||
export const addIdToValue = (string: string, id: number): string => {
|
||||
const array = string ? string.split(",") : [];
|
||||
array.push(id.toString());
|
||||
const str = array.join();
|
||||
return str;
|
||||
const array = string ? string.split(",") : [];
|
||||
array.push(id.toString());
|
||||
const str = array.join();
|
||||
return str;
|
||||
};
|
||||
|
||||
export const removeIdFromValue = (string: string, id: number) => {
|
||||
let array = string.split(",");
|
||||
array = array.filter((el) => el !== id.toString());
|
||||
const str = array.join();
|
||||
return str;
|
||||
let array = string.split(",");
|
||||
array = array.filter((el) => el !== id.toString());
|
||||
const str = array.join();
|
||||
return str;
|
||||
};
|
||||
|
||||
/*
|
||||
* Assign missing keys for the ConcernedGroups component
|
||||
*/
|
||||
export const mapEntity = (entity: EventInput): EventInput => {
|
||||
const calendar = { ...entity };
|
||||
Object.assign(calendar, { thirdParties: entity.professionals });
|
||||
const calendar = { ...entity };
|
||||
Object.assign(calendar, { thirdParties: entity.professionals });
|
||||
|
||||
if (entity.startDate !== null) {
|
||||
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
|
||||
}
|
||||
if (entity.endDate !== null) {
|
||||
calendar.endDate = ISOToDatetime(entity.endDate.datetime);
|
||||
}
|
||||
if (entity.startDate !== null) {
|
||||
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
|
||||
}
|
||||
if (entity.endDate !== null) {
|
||||
calendar.endDate = ISOToDatetime(entity.endDate.datetime);
|
||||
}
|
||||
|
||||
if (entity.calendarRange !== null) {
|
||||
calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
|
||||
calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
|
||||
}
|
||||
if (entity.calendarRange !== null) {
|
||||
calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
|
||||
calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
|
||||
}
|
||||
|
||||
return calendar;
|
||||
return calendar;
|
||||
};
|
||||
|
||||
export const createUserData = (user: User, colorIndex: number): UserData => {
|
||||
const colorId = colorIndex % COLORS.length;
|
||||
const colorId = colorIndex % COLORS.length;
|
||||
|
||||
return {
|
||||
user: user,
|
||||
calendarRanges: [],
|
||||
calendarRangesLoaded: [],
|
||||
remotes: [],
|
||||
remotesLoaded: [],
|
||||
locals: [],
|
||||
localsLoaded: [],
|
||||
mainColor: COLORS[colorId],
|
||||
};
|
||||
return {
|
||||
user: user,
|
||||
calendarRanges: [],
|
||||
calendarRangesLoaded: [],
|
||||
remotes: [],
|
||||
remotesLoaded: [],
|
||||
locals: [],
|
||||
localsLoaded: [],
|
||||
mainColor: COLORS[colorId],
|
||||
};
|
||||
};
|
||||
|
||||
// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app
|
||||
export const calendarRangeToFullCalendarEvent = (
|
||||
entity: CalendarRange,
|
||||
entity: CalendarRange,
|
||||
): EventInputCalendarRange => {
|
||||
return {
|
||||
id: `range_${entity.id}`,
|
||||
title: "(" + entity.user.text + ")",
|
||||
start: entity.startDate.datetime8601,
|
||||
end: entity.endDate.datetime8601,
|
||||
allDay: false,
|
||||
userId: entity.user.id,
|
||||
userLabel: entity.user.label,
|
||||
calendarRangeId: entity.id,
|
||||
locationId: entity.location.id,
|
||||
locationName: entity.location.name,
|
||||
is: "range",
|
||||
};
|
||||
return {
|
||||
id: `range_${entity.id}`,
|
||||
title: "(" + entity.user.text + ")",
|
||||
start: entity.startDate.datetime8601,
|
||||
end: entity.endDate.datetime8601,
|
||||
allDay: false,
|
||||
userId: entity.user.id,
|
||||
userLabel: entity.user.label,
|
||||
calendarRangeId: entity.id,
|
||||
locationId: entity.location.id,
|
||||
locationName: entity.location.name,
|
||||
is: "range",
|
||||
};
|
||||
};
|
||||
|
||||
export const remoteToFullCalendarEvent = (
|
||||
entity: CalendarRemote,
|
||||
entity: CalendarRemote,
|
||||
): EventInput & { id: string } => {
|
||||
return {
|
||||
id: `range_${entity.id}`,
|
||||
title: entity.title,
|
||||
start: entity.startDate.datetime8601,
|
||||
end: entity.endDate.datetime8601,
|
||||
allDay: entity.isAllDay,
|
||||
is: "remote",
|
||||
};
|
||||
return {
|
||||
id: `range_${entity.id}`,
|
||||
title: entity.title,
|
||||
start: entity.startDate.datetime8601,
|
||||
end: entity.endDate.datetime8601,
|
||||
allDay: entity.isAllDay,
|
||||
is: "remote",
|
||||
};
|
||||
};
|
||||
|
||||
export const localsToFullCalendarEvent = (
|
||||
entity: CalendarLight,
|
||||
entity: CalendarLight,
|
||||
): EventInput & { id: string; originId: number } => {
|
||||
return {
|
||||
id: `local_${entity.id}`,
|
||||
title: entity.persons.map((p) => p.text).join(", "),
|
||||
originId: entity.id,
|
||||
start: entity.startDate.datetime8601,
|
||||
end: entity.endDate.datetime8601,
|
||||
allDay: false,
|
||||
is: "local",
|
||||
};
|
||||
return {
|
||||
id: `local_${entity.id}`,
|
||||
title: entity.persons.map((p) => p.text).join(", "),
|
||||
originId: entity.id,
|
||||
start: entity.startDate.datetime8601,
|
||||
end: entity.endDate.datetime8601,
|
||||
allDay: false,
|
||||
is: "local",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,58 +1,50 @@
|
||||
<template>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
id="btnGroupDrop1"
|
||||
type="button"
|
||||
class="btn btn-misc dropdown-toggle"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
id="btnGroupDrop1"
|
||||
type="button"
|
||||
class="btn btn-misc dropdown-toggle"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<template v-if="status === Statuses.PENDING">
|
||||
<span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
|
||||
</template>
|
||||
<template v-else-if="status === Statuses.ACCEPTED">
|
||||
<span class="fa fa-check"></span> {{ $t("Accepted") }}
|
||||
</template>
|
||||
<template v-else-if="status === Statuses.DECLINED">
|
||||
<span class="fa fa-times"></span> {{ $t("Declined") }}
|
||||
</template>
|
||||
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
|
||||
<span class="fa fa-question"></span> {{ $t("Tentative") }}
|
||||
</template>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
||||
<li v-if="status !== Statuses.ACCEPTED">
|
||||
<a class="dropdown-item" @click="changeStatus(Statuses.ACCEPTED)"
|
||||
><i class="fa fa-check" aria-hidden="true"></i> {{ $t("Accept") }}</a
|
||||
>
|
||||
<template v-if="status === Statuses.PENDING">
|
||||
<span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
|
||||
</template>
|
||||
<template v-else-if="status === Statuses.ACCEPTED">
|
||||
<span class="fa fa-check"></span> {{ $t("Accepted") }}
|
||||
</template>
|
||||
<template v-else-if="status === Statuses.DECLINED">
|
||||
<span class="fa fa-times"></span> {{ $t("Declined") }}
|
||||
</template>
|
||||
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
|
||||
<span class="fa fa-question"></span> {{ $t("Tentative") }}
|
||||
</template>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
||||
<li v-if="status !== Statuses.ACCEPTED">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
@click="changeStatus(Statuses.ACCEPTED)"
|
||||
><i class="fa fa-check" aria-hidden="true"></i>
|
||||
{{ $t("Accept") }}</a
|
||||
>
|
||||
</li>
|
||||
<li v-if="status !== Statuses.DECLINED">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
@click="changeStatus(Statuses.DECLINED)"
|
||||
><i class="fa fa-times" aria-hidden="true"></i>
|
||||
{{ $t("Decline") }}</a
|
||||
>
|
||||
</li>
|
||||
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
|
||||
><i class="fa fa-question"></i>
|
||||
{{ $t("Tentatively_accept") }}</a
|
||||
>
|
||||
</li>
|
||||
<li v-if="status !== Statuses.PENDING">
|
||||
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
|
||||
><i class="fa fa-hourglass-o"></i>
|
||||
{{ $t("Set_pending") }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="status !== Statuses.DECLINED">
|
||||
<a class="dropdown-item" @click="changeStatus(Statuses.DECLINED)"
|
||||
><i class="fa fa-times" aria-hidden="true"></i> {{ $t("Decline") }}</a
|
||||
>
|
||||
</li>
|
||||
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
|
||||
><i class="fa fa-question"></i> {{ $t("Tentatively_accept") }}</a
|
||||
>
|
||||
</li>
|
||||
<li v-if="status !== Statuses.PENDING">
|
||||
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
|
||||
><i class="fa fa-hourglass-o"></i> {{ $t("Set_pending") }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -64,69 +56,67 @@ const PENDING = "pending";
|
||||
const TENTATIVELY_ACCEPTED = "tentative";
|
||||
|
||||
const i18n = {
|
||||
messages: {
|
||||
fr: {
|
||||
Give_an_answer: "Répondre",
|
||||
Accepted: "Accepté",
|
||||
Declined: "Refusé",
|
||||
Tentative: "Accepté provisoirement",
|
||||
Accept: "Accepter",
|
||||
Decline: "Refuser",
|
||||
Tentatively_accept: "Accepter provisoirement",
|
||||
Set_pending: "Ne pas répondre",
|
||||
},
|
||||
messages: {
|
||||
fr: {
|
||||
Give_an_answer: "Répondre",
|
||||
Accepted: "Accepté",
|
||||
Declined: "Refusé",
|
||||
Tentative: "Accepté provisoirement",
|
||||
Accept: "Accepter",
|
||||
Decline: "Refuser",
|
||||
Tentatively_accept: "Accepter provisoirement",
|
||||
Set_pending: "Ne pas répondre",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: "Answer",
|
||||
i18n,
|
||||
props: {
|
||||
calendarId: { type: Number, required: true },
|
||||
status: {
|
||||
type: String as PropType<
|
||||
"accepted" | "declined" | "pending" | "tentative"
|
||||
>,
|
||||
required: true,
|
||||
},
|
||||
name: "Answer",
|
||||
i18n,
|
||||
props: {
|
||||
calendarId: { type: Number, required: true },
|
||||
status: {
|
||||
type: String as PropType<
|
||||
"accepted" | "declined" | "pending" | "tentative"
|
||||
>,
|
||||
required: true,
|
||||
},
|
||||
emits: {
|
||||
statusChanged(
|
||||
payload: "accepted" | "declined" | "pending" | "tentative",
|
||||
) {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
statusChanged(payload: "accepted" | "declined" | "pending" | "tentative") {
|
||||
return true;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Statuses: {
|
||||
ACCEPTED,
|
||||
DECLINED,
|
||||
PENDING,
|
||||
TENTATIVELY_ACCEPTED,
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeStatus: function (
|
||||
newStatus: "accepted" | "declined" | "pending" | "tentative",
|
||||
) {
|
||||
console.log("changeStatus", newStatus);
|
||||
const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
|
||||
window
|
||||
.fetch(url, {
|
||||
method: "POST",
|
||||
})
|
||||
.then((r: Response) => {
|
||||
if (!r.ok) {
|
||||
console.error("could not confirm answer", newStatus);
|
||||
return;
|
||||
}
|
||||
console.log("answer sent", newStatus);
|
||||
this.$emit("statusChanged", newStatus);
|
||||
});
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Statuses: {
|
||||
ACCEPTED,
|
||||
DECLINED,
|
||||
PENDING,
|
||||
TENTATIVELY_ACCEPTED,
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeStatus: function (
|
||||
newStatus: "accepted" | "declined" | "pending" | "tentative",
|
||||
) {
|
||||
console.log("changeStatus", newStatus);
|
||||
const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
|
||||
window
|
||||
.fetch(url, {
|
||||
method: "POST",
|
||||
})
|
||||
.then((r: Response) => {
|
||||
if (!r.ok) {
|
||||
console.error("could not confirm answer", newStatus);
|
||||
return;
|
||||
}
|
||||
console.log("answer sent", newStatus);
|
||||
this.$emit("statusChanged", newStatus);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,225 +1,177 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<label class="form-label">{{ $t("created_availabilities") }}</label>
|
||||
<vue-multiselect
|
||||
v-model="pickedLocation"
|
||||
:options="locations"
|
||||
:label="'name'"
|
||||
:track-by="'id'"
|
||||
:selectLabel="'Presser \'Entrée\' pour choisir'"
|
||||
:selectedLabel="'Choisir'"
|
||||
:deselectLabel="'Presser \'Entrée\' pour enlever'"
|
||||
:placeholder="'Choisir'"
|
||||
></vue-multiselect>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<label class="form-label">{{ $t("created_availabilities") }}</label>
|
||||
<vue-multiselect
|
||||
v-model="pickedLocation"
|
||||
:options="locations"
|
||||
:label="'name'"
|
||||
:track-by="'id'"
|
||||
:selectLabel="'Presser \'Entrée\' pour choisir'"
|
||||
:selectedLabel="'Choisir'"
|
||||
:deselectLabel="'Presser \'Entrée\' pour enlever'"
|
||||
:placeholder="'Choisir'"
|
||||
></vue-multiselect>
|
||||
</div>
|
||||
<div
|
||||
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
|
||||
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>
|
||||
<FullCalendar :options="calendarOptions" ref="calendarRef">
|
||||
<template v-slot:eventContent="{ event }: { event: EventApi }">
|
||||
<span :class="eventClasses">
|
||||
<b v-if="event.extendedProps.is === 'remote'">{{
|
||||
event.title
|
||||
}}</b>
|
||||
<b v-else-if="event.extendedProps.is === 'range'"
|
||||
>{{ formatDate(event.startStr) }} -
|
||||
{{ event.extendedProps.locationName }}</b
|
||||
>
|
||||
<b v-else-if="event.extendedProps.is === 'local'">{{
|
||||
event.title
|
||||
}}</b>
|
||||
<b v-else>no 'is'</b>
|
||||
<a
|
||||
v-if="event.extendedProps.is === 'range'"
|
||||
class="fa fa-fw fa-times delete"
|
||||
@click.prevent="onClickDelete(event)"
|
||||
>
|
||||
</a>
|
||||
</span>
|
||||
<div class="col-xs-12 col-sm-3">
|
||||
<div class="float-end">
|
||||
<div class="form-check input-group">
|
||||
<span class="input-group-text">
|
||||
<input
|
||||
id="showHideWE"
|
||||
class="mt-0"
|
||||
type="checkbox"
|
||||
v-model="showWeekends"
|
||||
/>
|
||||
</span>
|
||||
<label for="showHideWE" class="form-check-label input-group-text"
|
||||
>Week-ends</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FullCalendar :options="calendarOptions" ref="calendarRef">
|
||||
<template v-slot:eventContent="{ event }: { event: EventApi }">
|
||||
<span :class="eventClasses">
|
||||
<b v-if="event.extendedProps.is === 'remote'">{{ event.title }}</b>
|
||||
<b v-else-if="event.extendedProps.is === 'range'"
|
||||
>{{ formatDate(event.startStr) }} -
|
||||
{{ event.extendedProps.locationName }}</b
|
||||
>
|
||||
<b v-else-if="event.extendedProps.is === 'local'">{{ event.title }}</b>
|
||||
<b v-else>no 'is'</b>
|
||||
<a
|
||||
v-if="event.extendedProps.is === 'range'"
|
||||
class="fa fa-fw fa-times delete"
|
||||
@click.prevent="onClickDelete(event)"
|
||||
>
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
</FullCalendar>
|
||||
|
||||
<div id="copy-widget">
|
||||
<div class="container mt-2 mb-2">
|
||||
<div class="row justify-content-between align-items-center mb-4">
|
||||
<div class="col-xs-12 col-sm-3 col-md-2">
|
||||
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-9 col-md-2">
|
||||
<select v-model="dayOrWeek" id="dayOrWeek" class="form-select">
|
||||
<option value="day">{{ $t("from_day_to_day") }}</option>
|
||||
<option value="week">
|
||||
{{ $t("from_week_to_week") }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<template v-if="dayOrWeek === 'day'">
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<input class="form-control" type="date" v-model="copyFrom" />
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<input class="form-control" type="date" v-model="copyTo" />
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||
<button class="btn btn-action float-end" @click="copyDay">
|
||||
{{ $t("copy_range") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</FullCalendar>
|
||||
|
||||
<div id="copy-widget">
|
||||
<div class="container mt-2 mb-2">
|
||||
<div class="row justify-content-between align-items-center mb-4">
|
||||
<div class="col-xs-12 col-sm-3 col-md-2">
|
||||
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-9 col-md-2">
|
||||
<select
|
||||
v-model="dayOrWeek"
|
||||
id="dayOrWeek"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="day">{{ $t("from_day_to_day") }}</option>
|
||||
<option value="week">
|
||||
{{ $t("from_week_to_week") }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<template v-if="dayOrWeek === 'day'">
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<input
|
||||
class="form-control"
|
||||
type="date"
|
||||
v-model="copyFrom"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<input
|
||||
class="form-control"
|
||||
type="date"
|
||||
v-model="copyTo"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||
<button
|
||||
class="btn btn-action float-end"
|
||||
@click="copyDay"
|
||||
>
|
||||
{{ $t("copy_range") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template 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>
|
||||
<template v-else>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<select
|
||||
v-model="copyFromWeek"
|
||||
id="copyFromWeek"
|
||||
class="form-select"
|
||||
>
|
||||
<option v-for="w in lastWeeks" :value="w.value" :key="w.value">
|
||||
{{ w.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<select v-model="copyToWeek" id="copyToWeek" class="form-select">
|
||||
<option v-for="w in nextWeeks" :value="w.value" :key="w.value">
|
||||
{{ w.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||
<button class="btn btn-action float-end" @click="copyWeek">
|
||||
{{ $t("copy_range") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- not directly seen, but include in a modal -->
|
||||
<edit-location ref="editLocation"></edit-location>
|
||||
<!-- not directly seen, but include in a modal -->
|
||||
<edit-location ref="editLocation"></edit-location>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
CalendarOptions,
|
||||
DatesSetArg,
|
||||
EventInput,
|
||||
CalendarOptions,
|
||||
DatesSetArg,
|
||||
EventInput,
|
||||
} from "@fullcalendar/core";
|
||||
import { computed, ref, onMounted } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
@@ -227,14 +179,14 @@ import { key } from "./store";
|
||||
import FullCalendar from "@fullcalendar/vue3";
|
||||
import frLocale from "@fullcalendar/core/locales/fr";
|
||||
import interactionPlugin, {
|
||||
EventResizeDoneArg,
|
||||
EventResizeDoneArg,
|
||||
} from "@fullcalendar/interaction";
|
||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||
import {
|
||||
EventApi,
|
||||
DateSelectArg,
|
||||
EventDropArg,
|
||||
EventClickArg,
|
||||
EventApi,
|
||||
DateSelectArg,
|
||||
EventDropArg,
|
||||
EventClickArg,
|
||||
} from "@fullcalendar/core";
|
||||
import { dateToISO, ISOToDate } from "ChillMainAssets/chill/js/date";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
@@ -255,96 +207,96 @@ const copyFromWeek = ref<string | null>(null);
|
||||
const copyToWeek = ref<string | null>(null);
|
||||
|
||||
interface Weeks {
|
||||
value: string | null;
|
||||
text: string;
|
||||
value: string | null;
|
||||
text: string;
|
||||
}
|
||||
|
||||
const getMonday = (week: number): Date => {
|
||||
const lastMonday = new Date();
|
||||
lastMonday.setDate(
|
||||
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
|
||||
);
|
||||
return lastMonday;
|
||||
const lastMonday = new Date();
|
||||
lastMonday.setDate(
|
||||
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
|
||||
);
|
||||
return lastMonday;
|
||||
};
|
||||
|
||||
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
};
|
||||
|
||||
const lastWeeks = computed((): Weeks[] =>
|
||||
Array.from(Array(30).keys()).map((w) => {
|
||||
const lastMonday = getMonday(15 - w);
|
||||
return {
|
||||
value: dateToISO(lastMonday),
|
||||
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||
};
|
||||
}),
|
||||
Array.from(Array(30).keys()).map((w) => {
|
||||
const lastMonday = getMonday(15 - w);
|
||||
return {
|
||||
value: dateToISO(lastMonday),
|
||||
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const nextWeeks = computed((): Weeks[] =>
|
||||
Array.from(Array(52).keys()).map((w) => {
|
||||
const nextMonday = getMonday(w + 1);
|
||||
return {
|
||||
value: dateToISO(nextMonday),
|
||||
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||
};
|
||||
}),
|
||||
Array.from(Array(52).keys()).map((w) => {
|
||||
const nextMonday = getMonday(w + 1);
|
||||
return {
|
||||
value: dateToISO(nextMonday),
|
||||
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const formatDate = (datetime: string) => {
|
||||
console.log(typeof datetime);
|
||||
return ISOToDate(datetime);
|
||||
console.log(typeof datetime);
|
||||
return ISOToDate(datetime);
|
||||
};
|
||||
|
||||
const baseOptions = ref<CalendarOptions>({
|
||||
locale: frLocale,
|
||||
plugins: [interactionPlugin, timeGridPlugin],
|
||||
initialView: "timeGridWeek",
|
||||
initialDate: new Date(),
|
||||
scrollTimeReset: false,
|
||||
selectable: true,
|
||||
// when the dates are changes in the fullcalendar view OR when new events are added
|
||||
datesSet: onDatesSet,
|
||||
// when a date is selected
|
||||
select: onDateSelect,
|
||||
// when a event is resized
|
||||
eventResize: onEventDropOrResize,
|
||||
// when an event is moved
|
||||
eventDrop: onEventDropOrResize,
|
||||
// when an event si clicked
|
||||
eventClick: onEventClick,
|
||||
selectMirror: false,
|
||||
editable: true,
|
||||
headerToolbar: {
|
||||
left: "prev,next today",
|
||||
center: "title",
|
||||
right: "timeGridWeek,timeGridDay",
|
||||
},
|
||||
locale: frLocale,
|
||||
plugins: [interactionPlugin, timeGridPlugin],
|
||||
initialView: "timeGridWeek",
|
||||
initialDate: new Date(),
|
||||
scrollTimeReset: false,
|
||||
selectable: true,
|
||||
// when the dates are changes in the fullcalendar view OR when new events are added
|
||||
datesSet: onDatesSet,
|
||||
// when a date is selected
|
||||
select: onDateSelect,
|
||||
// when a event is resized
|
||||
eventResize: onEventDropOrResize,
|
||||
// when an event is moved
|
||||
eventDrop: onEventDropOrResize,
|
||||
// when an event si clicked
|
||||
eventClick: onEventClick,
|
||||
selectMirror: false,
|
||||
editable: true,
|
||||
headerToolbar: {
|
||||
left: "prev,next today",
|
||||
center: "title",
|
||||
right: "timeGridWeek,timeGridDay",
|
||||
},
|
||||
});
|
||||
|
||||
const ranges = computed<EventInput[]>(() => {
|
||||
return store.state.calendarRanges.ranges;
|
||||
return store.state.calendarRanges.ranges;
|
||||
});
|
||||
|
||||
const locations = computed<Location[]>(() => {
|
||||
return store.state.locations.locations;
|
||||
return store.state.locations.locations;
|
||||
});
|
||||
|
||||
const pickedLocation = computed<Location | null>({
|
||||
get(): Location | null {
|
||||
return (
|
||||
store.state.locations.locationPicked ||
|
||||
store.state.locations.currentLocation
|
||||
);
|
||||
},
|
||||
set(newLocation: Location | null): void {
|
||||
store.commit("locations/setLocationPicked", newLocation, {
|
||||
root: true,
|
||||
});
|
||||
},
|
||||
get(): Location | null {
|
||||
return (
|
||||
store.state.locations.locationPicked ||
|
||||
store.state.locations.currentLocation
|
||||
);
|
||||
},
|
||||
set(newLocation: Location | null): void {
|
||||
store.commit("locations/setLocationPicked", newLocation, {
|
||||
root: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -373,116 +325,116 @@ const sources = computed<EventSourceInput[]>(() => {
|
||||
*/
|
||||
|
||||
const calendarOptions = computed((): CalendarOptions => {
|
||||
return {
|
||||
...baseOptions.value,
|
||||
weekends: showWeekends.value,
|
||||
slotDuration: slotDuration.value,
|
||||
events: ranges.value,
|
||||
slotMinTime: slotMinTime.value,
|
||||
slotMaxTime: slotMaxTime.value,
|
||||
};
|
||||
return {
|
||||
...baseOptions.value,
|
||||
weekends: showWeekends.value,
|
||||
slotDuration: slotDuration.value,
|
||||
events: ranges.value,
|
||||
slotMinTime: slotMinTime.value,
|
||||
slotMaxTime: slotMaxTime.value,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* launched when the calendar range date change
|
||||
*/
|
||||
function onDatesSet(event: DatesSetArg): void {
|
||||
store.dispatch("fullCalendar/setCurrentDatesView", {
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
});
|
||||
store.dispatch("fullCalendar/setCurrentDatesView", {
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
});
|
||||
}
|
||||
|
||||
function onDateSelect(event: DateSelectArg): void {
|
||||
if (null === pickedLocation.value) {
|
||||
window.alert(
|
||||
"Indiquez une localisation avant de créer une période de disponibilité.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (null === pickedLocation.value) {
|
||||
window.alert(
|
||||
"Indiquez une localisation avant de créer une période de disponibilité.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
store.dispatch("calendarRanges/createRange", {
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
location: pickedLocation.value,
|
||||
});
|
||||
store.dispatch("calendarRanges/createRange", {
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
location: pickedLocation.value,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When a calendar range is deleted
|
||||
*/
|
||||
function onClickDelete(event: EventApi): void {
|
||||
if (event.extendedProps.is !== "range") {
|
||||
return;
|
||||
}
|
||||
if (event.extendedProps.is !== "range") {
|
||||
return;
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
"calendarRanges/deleteRange",
|
||||
event.extendedProps.calendarRangeId,
|
||||
);
|
||||
store.dispatch(
|
||||
"calendarRanges/deleteRange",
|
||||
event.extendedProps.calendarRangeId,
|
||||
);
|
||||
}
|
||||
|
||||
function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) {
|
||||
if (payload.event.extendedProps.is !== "range") {
|
||||
return;
|
||||
}
|
||||
if (payload.event.extendedProps.is !== "range") {
|
||||
return;
|
||||
}
|
||||
|
||||
store.dispatch("calendarRanges/patchRangeTime", {
|
||||
calendarRangeId: payload.event.extendedProps.calendarRangeId,
|
||||
start: payload.event.start,
|
||||
end: payload.event.end,
|
||||
});
|
||||
store.dispatch("calendarRanges/patchRangeTime", {
|
||||
calendarRangeId: payload.event.extendedProps.calendarRangeId,
|
||||
start: payload.event.start,
|
||||
end: payload.event.end,
|
||||
});
|
||||
}
|
||||
|
||||
function onEventClick(payload: EventClickArg): void {
|
||||
// @ts-ignore TS does not recognize the target. But it does exists.
|
||||
if (payload.jsEvent.target.classList.contains("delete")) {
|
||||
return;
|
||||
}
|
||||
if (payload.event.extendedProps.is !== "range") {
|
||||
return;
|
||||
}
|
||||
// @ts-ignore TS does not recognize the target. But it does exists.
|
||||
if (payload.jsEvent.target.classList.contains("delete")) {
|
||||
return;
|
||||
}
|
||||
if (payload.event.extendedProps.is !== "range") {
|
||||
return;
|
||||
}
|
||||
|
||||
editLocation.value?.startEdit(payload.event);
|
||||
editLocation.value?.startEdit(payload.event);
|
||||
}
|
||||
|
||||
function copyDay() {
|
||||
if (null === copyFrom.value || null === copyTo.value) {
|
||||
return;
|
||||
}
|
||||
store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
|
||||
from: ISOToDate(copyFrom.value),
|
||||
to: ISOToDate(copyTo.value),
|
||||
});
|
||||
if (null === copyFrom.value || null === copyTo.value) {
|
||||
return;
|
||||
}
|
||||
store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
|
||||
from: ISOToDate(copyFrom.value),
|
||||
to: ISOToDate(copyTo.value),
|
||||
});
|
||||
}
|
||||
|
||||
function copyWeek() {
|
||||
if (null === copyFromWeek.value || null === copyToWeek.value) {
|
||||
return;
|
||||
}
|
||||
store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
|
||||
fromMonday: ISOToDate(copyFromWeek.value),
|
||||
toMonday: ISOToDate(copyToWeek.value),
|
||||
});
|
||||
if (null === copyFromWeek.value || null === copyToWeek.value) {
|
||||
return;
|
||||
}
|
||||
store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
|
||||
fromMonday: ISOToDate(copyFromWeek.value),
|
||||
toMonday: ISOToDate(copyToWeek.value),
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
copyFromWeek.value = dateToISO(getMonday(0));
|
||||
copyToWeek.value = dateToISO(getMonday(1));
|
||||
copyFromWeek.value = dateToISO(getMonday(0));
|
||||
copyToWeek.value = dateToISO(getMonday(1));
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#copy-widget {
|
||||
position: sticky;
|
||||
bottom: 0px;
|
||||
background-color: white;
|
||||
z-index: 9999999999;
|
||||
padding: 0.25rem 0 0.25rem;
|
||||
position: sticky;
|
||||
bottom: 0px;
|
||||
background-color: white;
|
||||
z-index: 9999999999;
|
||||
padding: 0.25rem 0 0.25rem;
|
||||
}
|
||||
div.copy-chevron {
|
||||
text-align: center;
|
||||
font-size: x-large;
|
||||
width: 2rem;
|
||||
text-align: center;
|
||||
font-size: x-large;
|
||||
width: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<template>
|
||||
<component :is="Teleport" to="body">
|
||||
<modal v-if="showModal" @close="closeModal">
|
||||
<template v-slot:header>
|
||||
<h3>{{ "Modifier le lieu" }}</h3>
|
||||
</template>
|
||||
<component :is="Teleport" to="body">
|
||||
<modal v-if="showModal" @close="closeModal">
|
||||
<template v-slot:header>
|
||||
<h3>{{ "Modifier le lieu" }}</h3>
|
||||
</template>
|
||||
|
||||
<template v-slot:body>
|
||||
<div></div>
|
||||
<label>Localisation</label>
|
||||
<vue-multiselect
|
||||
v-model="location"
|
||||
:options="locations"
|
||||
:label="'name'"
|
||||
:track-by="'id'"
|
||||
></vue-multiselect>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<div></div>
|
||||
<label>Localisation</label>
|
||||
<vue-multiselect
|
||||
v-model="location"
|
||||
:options="locations"
|
||||
:label="'name'"
|
||||
:track-by="'id'"
|
||||
></vue-multiselect>
|
||||
</template>
|
||||
|
||||
<template v-slot:footer>
|
||||
<button class="btn btn-save" @click="saveAndClose">
|
||||
{{ "Enregistrer" }}
|
||||
</button>
|
||||
</template>
|
||||
</modal>
|
||||
</component>
|
||||
<template v-slot:footer>
|
||||
<button class="btn btn-save" @click="saveAndClose">
|
||||
{{ "Enregistrer" }}
|
||||
</button>
|
||||
</template>
|
||||
</modal>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -39,7 +39,7 @@ import VueMultiselect from "vue-multiselect";
|
||||
import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
|
||||
|
||||
const Teleport = teleport_ as new () => {
|
||||
$props: VNodeProps & TeleportProps;
|
||||
$props: VNodeProps & TeleportProps;
|
||||
};
|
||||
|
||||
const store = useStore(key);
|
||||
@@ -50,37 +50,37 @@ const showModal = ref(false);
|
||||
//const tele = ref<InstanceType<typeof Teleport> | null>(null);
|
||||
|
||||
const locations = computed<Location[]>(() => {
|
||||
return store.state.locations.locations;
|
||||
return store.state.locations.locations;
|
||||
});
|
||||
|
||||
const startEdit = function (event: EventApi): void {
|
||||
console.log("startEditing", event);
|
||||
calendarRangeId.value = event.extendedProps.calendarRangeId;
|
||||
location.value =
|
||||
store.getters["locations/getLocationById"](
|
||||
event.extendedProps.locationId,
|
||||
) || null;
|
||||
console.log("startEditing", event);
|
||||
calendarRangeId.value = event.extendedProps.calendarRangeId;
|
||||
location.value =
|
||||
store.getters["locations/getLocationById"](
|
||||
event.extendedProps.locationId,
|
||||
) || null;
|
||||
|
||||
console.log("new location value", location.value);
|
||||
console.log("calendar range id", calendarRangeId.value);
|
||||
showModal.value = true;
|
||||
console.log("new location value", location.value);
|
||||
console.log("calendar range id", calendarRangeId.value);
|
||||
showModal.value = true;
|
||||
};
|
||||
|
||||
const saveAndClose = function (e: Event): void {
|
||||
console.log("saveEditAndClose", e);
|
||||
console.log("saveEditAndClose", e);
|
||||
|
||||
store
|
||||
.dispatch("calendarRanges/patchRangeLocation", {
|
||||
location: location.value,
|
||||
calendarRangeId: calendarRangeId.value,
|
||||
})
|
||||
.then((_) => {
|
||||
showModal.value = false;
|
||||
});
|
||||
store
|
||||
.dispatch("calendarRanges/patchRangeLocation", {
|
||||
location: location.value,
|
||||
calendarRangeId: calendarRangeId.value,
|
||||
})
|
||||
.then((_) => {
|
||||
showModal.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const closeModal = function (_: any): void {
|
||||
showModal.value = false;
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
defineExpose({ startEdit });
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
const appMessages = {
|
||||
fr: {
|
||||
created_availabilities: "Lieu des plages de disponibilités créées",
|
||||
edit_your_calendar_range: "Planifiez vos plages de disponibilités",
|
||||
show_my_calendar: "Afficher mon calendrier",
|
||||
show_weekends: "Afficher les week-ends",
|
||||
copy_range: "Copier",
|
||||
copy_range_from_to: "Copier les plages",
|
||||
from_day_to_day: "d'un jour à l'autre",
|
||||
from_week_to_week: "d'une semaine à l'autre",
|
||||
copy_range_how_to:
|
||||
"Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
|
||||
new_range_to_save: "Nouvelles plages à enregistrer",
|
||||
update_range_to_save: "Plages à modifier",
|
||||
delete_range_to_save: "Plages à supprimer",
|
||||
by: "Par",
|
||||
main_user_concerned: "Utilisateur concerné",
|
||||
dateFrom: "De",
|
||||
dateTo: "à",
|
||||
day: "Jour",
|
||||
week: "Semaine",
|
||||
month: "Mois",
|
||||
today: "Aujourd'hui",
|
||||
},
|
||||
fr: {
|
||||
created_availabilities: "Lieu des plages de disponibilités créées",
|
||||
edit_your_calendar_range: "Planifiez vos plages de disponibilités",
|
||||
show_my_calendar: "Afficher mon calendrier",
|
||||
show_weekends: "Afficher les week-ends",
|
||||
copy_range: "Copier",
|
||||
copy_range_from_to: "Copier les plages",
|
||||
from_day_to_day: "d'un jour à l'autre",
|
||||
from_week_to_week: "d'une semaine à l'autre",
|
||||
copy_range_how_to:
|
||||
"Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
|
||||
new_range_to_save: "Nouvelles plages à enregistrer",
|
||||
update_range_to_save: "Plages à modifier",
|
||||
delete_range_to_save: "Plages à supprimer",
|
||||
by: "Par",
|
||||
main_user_concerned: "Utilisateur concerné",
|
||||
dateFrom: "De",
|
||||
dateTo: "à",
|
||||
day: "Jour",
|
||||
week: "Semaine",
|
||||
month: "Mois",
|
||||
today: "Aujourd'hui",
|
||||
},
|
||||
};
|
||||
|
||||
export { appMessages };
|
||||
|
||||
@@ -7,13 +7,13 @@ import App2 from "./App2.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
futureStore().then((store) => {
|
||||
const i18n = _createI18n(appMessages, false);
|
||||
const i18n = _createI18n(appMessages, false);
|
||||
|
||||
const app = createApp({
|
||||
template: `<app></app>`,
|
||||
})
|
||||
.use(store, key)
|
||||
.use(i18n)
|
||||
.component("app", App2)
|
||||
.mount("#myCalendar");
|
||||
const app = createApp({
|
||||
template: `<app></app>`,
|
||||
})
|
||||
.use(store, key)
|
||||
.use(i18n)
|
||||
.component("app", App2)
|
||||
.mount("#myCalendar");
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import me, { MeState } from "./modules/me";
|
||||
import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
|
||||
import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
|
||||
import calendarRemotes, {
|
||||
CalendarRemotesState,
|
||||
CalendarRemotesState,
|
||||
} from "./modules/calendarRemotes";
|
||||
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||
import { User } from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||
@@ -15,42 +15,40 @@ import calendarLocals, { CalendarLocalsState } from "./modules/calendarLocals";
|
||||
const debug = process.env.NODE_ENV !== "production";
|
||||
|
||||
export interface State {
|
||||
calendarRanges: CalendarRangesState;
|
||||
calendarRemotes: CalendarRemotesState;
|
||||
calendarLocals: CalendarLocalsState;
|
||||
fullCalendar: FullCalendarState;
|
||||
me: MeState;
|
||||
locations: LocationState;
|
||||
calendarRanges: CalendarRangesState;
|
||||
calendarRemotes: CalendarRemotesState;
|
||||
calendarLocals: CalendarLocalsState;
|
||||
fullCalendar: FullCalendarState;
|
||||
me: MeState;
|
||||
locations: LocationState;
|
||||
}
|
||||
|
||||
export const key: InjectionKey<Store<State>> = Symbol();
|
||||
|
||||
const futureStore = function (): Promise<Store<State>> {
|
||||
return whoami().then((user: User) => {
|
||||
const store = createStore<State>({
|
||||
strict: debug,
|
||||
modules: {
|
||||
me,
|
||||
fullCalendar,
|
||||
calendarRanges,
|
||||
calendarRemotes,
|
||||
calendarLocals,
|
||||
locations,
|
||||
},
|
||||
mutations: {},
|
||||
});
|
||||
|
||||
store.commit("me/setWhoAmi", user, { root: true });
|
||||
store
|
||||
.dispatch("locations/getLocations", null, { root: true })
|
||||
.then((_) => {
|
||||
return store.dispatch("locations/getCurrentLocation", null, {
|
||||
root: true,
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.resolve(store);
|
||||
return whoami().then((user: User) => {
|
||||
const store = createStore<State>({
|
||||
strict: debug,
|
||||
modules: {
|
||||
me,
|
||||
fullCalendar,
|
||||
calendarRanges,
|
||||
calendarRemotes,
|
||||
calendarLocals,
|
||||
locations,
|
||||
},
|
||||
mutations: {},
|
||||
});
|
||||
|
||||
store.commit("me/setWhoAmi", user, { root: true });
|
||||
store.dispatch("locations/getLocations", null, { root: true }).then((_) => {
|
||||
return store.dispatch("locations/getCurrentLocation", null, {
|
||||
root: true,
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.resolve(store);
|
||||
});
|
||||
};
|
||||
|
||||
export default futureStore;
|
||||
|
||||
@@ -8,109 +8,99 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
|
||||
import { COLORS } from "../../../Calendar/const";
|
||||
|
||||
export interface CalendarLocalsState {
|
||||
locals: EventInput[];
|
||||
localsLoaded: { start: number; end: number }[];
|
||||
localsIndex: Set<string>;
|
||||
key: number;
|
||||
locals: EventInput[];
|
||||
localsLoaded: { start: number; end: number }[];
|
||||
localsIndex: Set<string>;
|
||||
key: number;
|
||||
}
|
||||
|
||||
type Context = ActionContext<CalendarLocalsState, State>;
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: (): CalendarLocalsState => ({
|
||||
locals: [],
|
||||
localsLoaded: [],
|
||||
localsIndex: new Set<string>(),
|
||||
key: 0,
|
||||
}),
|
||||
getters: {
|
||||
isLocalsLoaded:
|
||||
(state: CalendarLocalsState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.localsLoaded) {
|
||||
if (
|
||||
start.getTime() === range.start &&
|
||||
end.getTime() === range.end
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
namespaced: true,
|
||||
state: (): CalendarLocalsState => ({
|
||||
locals: [],
|
||||
localsLoaded: [],
|
||||
localsIndex: new Set<string>(),
|
||||
key: 0,
|
||||
}),
|
||||
getters: {
|
||||
isLocalsLoaded:
|
||||
(state: CalendarLocalsState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.localsLoaded) {
|
||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
return false;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
|
||||
console.log("addLocals", ranges);
|
||||
|
||||
const toAdd = ranges
|
||||
.map((cr) => localsToFullCalendarEvent(cr))
|
||||
.filter((r) => !state.localsIndex.has(r.id));
|
||||
|
||||
toAdd.forEach((r) => {
|
||||
state.localsIndex.add(r.id);
|
||||
state.locals.push(r);
|
||||
});
|
||||
state.key = state.key + toAdd.length;
|
||||
},
|
||||
mutations: {
|
||||
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
|
||||
console.log("addLocals", ranges);
|
||||
|
||||
const toAdd = ranges
|
||||
.map((cr) => localsToFullCalendarEvent(cr))
|
||||
.filter((r) => !state.localsIndex.has(r.id));
|
||||
|
||||
toAdd.forEach((r) => {
|
||||
state.localsIndex.add(r.id);
|
||||
state.locals.push(r);
|
||||
});
|
||||
state.key = state.key + toAdd.length;
|
||||
},
|
||||
addLoaded(
|
||||
state: CalendarLocalsState,
|
||||
payload: { start: Date; end: Date },
|
||||
) {
|
||||
state.localsLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
});
|
||||
},
|
||||
addLoaded(state: CalendarLocalsState, payload: { start: Date; end: Date }) {
|
||||
state.localsLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
});
|
||||
},
|
||||
actions: {
|
||||
fetchLocals(
|
||||
ctx: Context,
|
||||
payload: { start: Date; end: Date },
|
||||
): Promise<null> {
|
||||
const start = payload.start;
|
||||
const end = payload.end;
|
||||
},
|
||||
actions: {
|
||||
fetchLocals(
|
||||
ctx: Context,
|
||||
payload: { start: Date; end: Date },
|
||||
): Promise<null> {
|
||||
const start = payload.start;
|
||||
const end = payload.end;
|
||||
|
||||
if (ctx.rootGetters["me/getMe"] === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
if (ctx.rootGetters["me/getMe"] === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (ctx.getters.isLocalsLoaded({ start, end })) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
if (ctx.getters.isLocalsLoaded({ start, end })) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
|
||||
ctx.commit("addLoaded", {
|
||||
start: start,
|
||||
end: end,
|
||||
});
|
||||
ctx.commit("addLoaded", {
|
||||
start: start,
|
||||
end: end,
|
||||
});
|
||||
|
||||
return fetchCalendarLocalForUser(
|
||||
ctx.rootGetters["me/getMe"],
|
||||
start,
|
||||
end,
|
||||
)
|
||||
.then((remotes: CalendarLight[]) => {
|
||||
// to be add when reactivity problem will be solve ?
|
||||
//ctx.commit('addRemotes', remotes);
|
||||
const inputs = remotes
|
||||
.map((cr) => localsToFullCalendarEvent(cr))
|
||||
.map((cr) => ({
|
||||
...cr,
|
||||
backgroundColor: COLORS[0],
|
||||
textColor: "black",
|
||||
editable: false,
|
||||
}));
|
||||
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||
root: true,
|
||||
});
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((e: TransportExceptionInterface) => {
|
||||
console.error(e);
|
||||
return fetchCalendarLocalForUser(ctx.rootGetters["me/getMe"], start, end)
|
||||
.then((remotes: CalendarLight[]) => {
|
||||
// to be add when reactivity problem will be solve ?
|
||||
//ctx.commit('addRemotes', remotes);
|
||||
const inputs = remotes
|
||||
.map((cr) => localsToFullCalendarEvent(cr))
|
||||
.map((cr) => ({
|
||||
...cr,
|
||||
backgroundColor: COLORS[0],
|
||||
textColor: "black",
|
||||
editable: false,
|
||||
}));
|
||||
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||
root: true,
|
||||
});
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((e: TransportExceptionInterface) => {
|
||||
console.error(e);
|
||||
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
},
|
||||
} as Module<CalendarLocalsState, State>;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { State } from "./../index";
|
||||
import { ActionContext, Module } from "vuex";
|
||||
import {
|
||||
CalendarRange,
|
||||
CalendarRangeCreate,
|
||||
CalendarRangeEdit,
|
||||
isEventInputCalendarRange,
|
||||
CalendarRange,
|
||||
CalendarRangeCreate,
|
||||
CalendarRangeEdit,
|
||||
isEventInputCalendarRange,
|
||||
} from "../../../../types";
|
||||
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
import { fetchCalendarRangeForUser } from "../../../Calendar/api";
|
||||
@@ -12,369 +12,332 @@ import { calendarRangeToFullCalendarEvent } from "../../../Calendar/store/utils"
|
||||
import { EventInput } from "@fullcalendar/core";
|
||||
import { makeFetch } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
import {
|
||||
datetimeToISO,
|
||||
dateToISO,
|
||||
ISOToDatetime,
|
||||
datetimeToISO,
|
||||
dateToISO,
|
||||
ISOToDatetime,
|
||||
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||
import type { EventInputCalendarRange } from "../../../../types";
|
||||
|
||||
export interface CalendarRangesState {
|
||||
ranges: (EventInput | EventInputCalendarRange)[];
|
||||
rangesLoaded: { start: number; end: number }[];
|
||||
rangesIndex: Set<string>;
|
||||
key: number;
|
||||
ranges: (EventInput | EventInputCalendarRange)[];
|
||||
rangesLoaded: { start: number; end: number }[];
|
||||
rangesIndex: Set<string>;
|
||||
key: number;
|
||||
}
|
||||
|
||||
type Context = ActionContext<CalendarRangesState, State>;
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: (): CalendarRangesState => ({
|
||||
ranges: [],
|
||||
rangesLoaded: [],
|
||||
rangesIndex: new Set<string>(),
|
||||
key: 0,
|
||||
}),
|
||||
getters: {
|
||||
isRangeLoaded:
|
||||
(state: CalendarRangesState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.rangesLoaded) {
|
||||
if (
|
||||
start.getTime() === range.start &&
|
||||
end.getTime() === range.end
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
namespaced: true,
|
||||
state: (): CalendarRangesState => ({
|
||||
ranges: [],
|
||||
rangesLoaded: [],
|
||||
rangesIndex: new Set<string>(),
|
||||
key: 0,
|
||||
}),
|
||||
getters: {
|
||||
isRangeLoaded:
|
||||
(state: CalendarRangesState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.rangesLoaded) {
|
||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
getRangesOnDate:
|
||||
(state: CalendarRangesState) =>
|
||||
(date: Date): EventInputCalendarRange[] => {
|
||||
const founds = [];
|
||||
const dateStr = dateToISO(date) as string;
|
||||
return false;
|
||||
},
|
||||
getRangesOnDate:
|
||||
(state: CalendarRangesState) =>
|
||||
(date: Date): EventInputCalendarRange[] => {
|
||||
const founds = [];
|
||||
const dateStr = dateToISO(date) as string;
|
||||
|
||||
for (const range of state.ranges) {
|
||||
if (
|
||||
isEventInputCalendarRange(range) &&
|
||||
range.start.startsWith(dateStr)
|
||||
) {
|
||||
founds.push(range);
|
||||
}
|
||||
}
|
||||
for (const range of state.ranges) {
|
||||
if (
|
||||
isEventInputCalendarRange(range) &&
|
||||
range.start.startsWith(dateStr)
|
||||
) {
|
||||
founds.push(range);
|
||||
}
|
||||
}
|
||||
|
||||
return founds;
|
||||
},
|
||||
getRangesOnWeek:
|
||||
(state: CalendarRangesState) =>
|
||||
(mondayDate: Date): EventInputCalendarRange[] => {
|
||||
const founds = [];
|
||||
for (const d of Array.from(Array(7).keys())) {
|
||||
const dateOfWeek = new Date(mondayDate);
|
||||
dateOfWeek.setDate(mondayDate.getDate() + d);
|
||||
const dateStr = dateToISO(dateOfWeek) as string;
|
||||
for (const range of state.ranges) {
|
||||
if (
|
||||
isEventInputCalendarRange(range) &&
|
||||
range.start.startsWith(dateStr)
|
||||
) {
|
||||
founds.push(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
return founds;
|
||||
},
|
||||
getRangesOnWeek:
|
||||
(state: CalendarRangesState) =>
|
||||
(mondayDate: Date): EventInputCalendarRange[] => {
|
||||
const founds = [];
|
||||
for (const d of Array.from(Array(7).keys())) {
|
||||
const dateOfWeek = new Date(mondayDate);
|
||||
dateOfWeek.setDate(mondayDate.getDate() + d);
|
||||
const dateStr = dateToISO(dateOfWeek) as string;
|
||||
for (const range of state.ranges) {
|
||||
if (
|
||||
isEventInputCalendarRange(range) &&
|
||||
range.start.startsWith(dateStr)
|
||||
) {
|
||||
founds.push(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return founds;
|
||||
},
|
||||
return founds;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
|
||||
const toAdd = ranges
|
||||
.map((cr) => calendarRangeToFullCalendarEvent(cr))
|
||||
.map((cr) => ({
|
||||
...cr,
|
||||
backgroundColor: "white",
|
||||
borderColor: "#3788d8",
|
||||
textColor: "black",
|
||||
}))
|
||||
.filter((r) => !state.rangesIndex.has(r.id));
|
||||
|
||||
toAdd.forEach((r) => {
|
||||
state.rangesIndex.add(r.id);
|
||||
state.ranges.push(r);
|
||||
});
|
||||
state.key = state.key + toAdd.length;
|
||||
},
|
||||
mutations: {
|
||||
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
|
||||
const toAdd = ranges
|
||||
.map((cr) => calendarRangeToFullCalendarEvent(cr))
|
||||
.map((cr) => ({
|
||||
...cr,
|
||||
backgroundColor: "white",
|
||||
borderColor: "#3788d8",
|
||||
textColor: "black",
|
||||
}))
|
||||
.filter((r) => !state.rangesIndex.has(r.id));
|
||||
addExternals(
|
||||
state: CalendarRangesState,
|
||||
externalEvents: (EventInput & { id: string })[],
|
||||
) {
|
||||
const toAdd = externalEvents.filter((r) => !state.rangesIndex.has(r.id));
|
||||
|
||||
toAdd.forEach((r) => {
|
||||
state.rangesIndex.add(r.id);
|
||||
state.ranges.push(r);
|
||||
});
|
||||
state.key = state.key + toAdd.length;
|
||||
},
|
||||
addExternals(
|
||||
state: CalendarRangesState,
|
||||
externalEvents: (EventInput & { id: string })[],
|
||||
) {
|
||||
const toAdd = externalEvents.filter(
|
||||
(r) => !state.rangesIndex.has(r.id),
|
||||
);
|
||||
|
||||
toAdd.forEach((r) => {
|
||||
state.rangesIndex.add(r.id);
|
||||
state.ranges.push(r);
|
||||
});
|
||||
state.key = state.key + toAdd.length;
|
||||
},
|
||||
addLoaded(
|
||||
state: CalendarRangesState,
|
||||
payload: { start: Date; end: Date },
|
||||
) {
|
||||
state.rangesLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
});
|
||||
},
|
||||
addRange(state: CalendarRangesState, payload: CalendarRange) {
|
||||
const asEvent = calendarRangeToFullCalendarEvent(payload);
|
||||
state.ranges.push({
|
||||
...asEvent,
|
||||
backgroundColor: "white",
|
||||
borderColor: "#3788d8",
|
||||
textColor: "black",
|
||||
});
|
||||
state.rangesIndex.add(asEvent.id);
|
||||
state.key = state.key + 1;
|
||||
},
|
||||
removeRange(state: CalendarRangesState, calendarRangeId: number) {
|
||||
const found = state.ranges.find(
|
||||
(r) =>
|
||||
r.calendarRangeId === calendarRangeId && r.is === "range",
|
||||
);
|
||||
|
||||
if (found !== undefined) {
|
||||
state.ranges = state.ranges.filter(
|
||||
(r) =>
|
||||
!(
|
||||
r.calendarRangeId === calendarRangeId &&
|
||||
r.is === "range"
|
||||
),
|
||||
);
|
||||
|
||||
if (typeof found.id === "string") {
|
||||
// should always be true
|
||||
state.rangesIndex.delete(found.id);
|
||||
}
|
||||
|
||||
state.key = state.key + 1;
|
||||
}
|
||||
},
|
||||
updateRange(state: CalendarRangesState, range: CalendarRange) {
|
||||
const found = state.ranges.find(
|
||||
(r) => r.calendarRangeId === range.id && r.is === "range",
|
||||
);
|
||||
const newEvent = calendarRangeToFullCalendarEvent(range);
|
||||
|
||||
if (found !== undefined) {
|
||||
found.start = newEvent.start;
|
||||
found.end = newEvent.end;
|
||||
found.locationId = range.location.id;
|
||||
found.locationName = range.location.name;
|
||||
}
|
||||
|
||||
state.key = state.key + 1;
|
||||
},
|
||||
toAdd.forEach((r) => {
|
||||
state.rangesIndex.add(r.id);
|
||||
state.ranges.push(r);
|
||||
});
|
||||
state.key = state.key + toAdd.length;
|
||||
},
|
||||
actions: {
|
||||
fetchRanges(
|
||||
ctx: Context,
|
||||
payload: { start: Date; end: Date },
|
||||
): Promise<null> {
|
||||
const start = payload.start;
|
||||
const end = payload.end;
|
||||
|
||||
if (ctx.rootGetters["me/getMe"] === null) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
|
||||
if (ctx.getters.isRangeLoaded({ start, end })) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
|
||||
ctx.commit("addLoaded", {
|
||||
start: start,
|
||||
end: end,
|
||||
});
|
||||
|
||||
return fetchCalendarRangeForUser(
|
||||
ctx.rootGetters["me/getMe"],
|
||||
start,
|
||||
end,
|
||||
).then((ranges: CalendarRange[]) => {
|
||||
ctx.commit("addRanges", ranges);
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
createRange(
|
||||
ctx: Context,
|
||||
{
|
||||
start,
|
||||
end,
|
||||
location,
|
||||
}: { start: Date; end: Date; location: Location },
|
||||
): Promise<null> {
|
||||
const url = `/api/1.0/calendar/calendar-range.json?`;
|
||||
|
||||
if (ctx.rootState.me.me === null) {
|
||||
throw new Error("user is currently null");
|
||||
}
|
||||
|
||||
const body = {
|
||||
user: {
|
||||
id: ctx.rootState.me.me.id,
|
||||
type: "user",
|
||||
},
|
||||
startDate: {
|
||||
datetime: datetimeToISO(start),
|
||||
},
|
||||
endDate: {
|
||||
datetime: datetimeToISO(end),
|
||||
},
|
||||
location: {
|
||||
id: location.id,
|
||||
type: "location",
|
||||
},
|
||||
} as CalendarRangeCreate;
|
||||
|
||||
return makeFetch<CalendarRangeCreate, CalendarRange>(
|
||||
"POST",
|
||||
url,
|
||||
body,
|
||||
)
|
||||
.then((newRange) => {
|
||||
ctx.commit("addRange", newRange);
|
||||
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
deleteRange(ctx: Context, calendarRangeId: number) {
|
||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||
|
||||
makeFetch<undefined, never>("DELETE", url).then(() => {
|
||||
ctx.commit("removeRange", calendarRangeId);
|
||||
});
|
||||
},
|
||||
patchRangeTime(
|
||||
ctx,
|
||||
{
|
||||
calendarRangeId,
|
||||
start,
|
||||
end,
|
||||
}: { calendarRangeId: number; start: Date; end: Date },
|
||||
): Promise<null> {
|
||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||
const body = {
|
||||
startDate: {
|
||||
datetime: datetimeToISO(start),
|
||||
},
|
||||
endDate: {
|
||||
datetime: datetimeToISO(end),
|
||||
},
|
||||
} as CalendarRangeEdit;
|
||||
|
||||
return makeFetch<CalendarRangeEdit, CalendarRange>(
|
||||
"PATCH",
|
||||
url,
|
||||
body,
|
||||
)
|
||||
.then((range) => {
|
||||
ctx.commit("updateRange", range);
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
patchRangeLocation(
|
||||
ctx,
|
||||
{
|
||||
location,
|
||||
calendarRangeId,
|
||||
}: { location: Location; calendarRangeId: number },
|
||||
): Promise<null> {
|
||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||
const body = {
|
||||
location: {
|
||||
id: location.id,
|
||||
type: "location",
|
||||
},
|
||||
} as CalendarRangeEdit;
|
||||
|
||||
return makeFetch<CalendarRangeEdit, CalendarRange>(
|
||||
"PATCH",
|
||||
url,
|
||||
body,
|
||||
)
|
||||
.then((range) => {
|
||||
ctx.commit("updateRange", range);
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
copyFromDayToAnotherDay(
|
||||
ctx,
|
||||
{ from, to }: { from: Date; to: Date },
|
||||
): Promise<null> {
|
||||
const rangesToCopy: EventInputCalendarRange[] =
|
||||
ctx.getters["getRangesOnDate"](from);
|
||||
const promises = [];
|
||||
|
||||
for (const r of rangesToCopy) {
|
||||
const start = new Date(ISOToDatetime(r.start) as Date);
|
||||
start.setFullYear(
|
||||
to.getFullYear(),
|
||||
to.getMonth(),
|
||||
to.getDate(),
|
||||
);
|
||||
const end = new Date(ISOToDatetime(r.end) as Date);
|
||||
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
||||
const location = ctx.rootGetters["locations/getLocationById"](
|
||||
r.locationId,
|
||||
);
|
||||
|
||||
promises.push(
|
||||
ctx.dispatch("createRange", { start, end, location }),
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||
},
|
||||
copyFromWeekToAnotherWeek(
|
||||
ctx: Context,
|
||||
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
|
||||
): Promise<null> {
|
||||
const rangesToCopy: EventInputCalendarRange[] =
|
||||
ctx.getters["getRangesOnWeek"](fromMonday);
|
||||
const promises = [];
|
||||
const diffTime = toMonday.getTime() - fromMonday.getTime();
|
||||
for (const r of rangesToCopy) {
|
||||
const start = new Date(ISOToDatetime(r.start) as Date);
|
||||
const end = new Date(ISOToDatetime(r.end) as Date);
|
||||
start.setTime(start.getTime() + diffTime);
|
||||
end.setTime(end.getTime() + diffTime);
|
||||
const location = ctx.rootGetters["locations/getLocationById"](
|
||||
r.locationId,
|
||||
);
|
||||
|
||||
promises.push(
|
||||
ctx.dispatch("createRange", { start, end, location }),
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||
},
|
||||
addLoaded(state: CalendarRangesState, payload: { start: Date; end: Date }) {
|
||||
state.rangesLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
});
|
||||
},
|
||||
addRange(state: CalendarRangesState, payload: CalendarRange) {
|
||||
const asEvent = calendarRangeToFullCalendarEvent(payload);
|
||||
state.ranges.push({
|
||||
...asEvent,
|
||||
backgroundColor: "white",
|
||||
borderColor: "#3788d8",
|
||||
textColor: "black",
|
||||
});
|
||||
state.rangesIndex.add(asEvent.id);
|
||||
state.key = state.key + 1;
|
||||
},
|
||||
removeRange(state: CalendarRangesState, calendarRangeId: number) {
|
||||
const found = state.ranges.find(
|
||||
(r) => r.calendarRangeId === calendarRangeId && r.is === "range",
|
||||
);
|
||||
|
||||
if (found !== undefined) {
|
||||
state.ranges = state.ranges.filter(
|
||||
(r) => !(r.calendarRangeId === calendarRangeId && r.is === "range"),
|
||||
);
|
||||
|
||||
if (typeof found.id === "string") {
|
||||
// should always be true
|
||||
state.rangesIndex.delete(found.id);
|
||||
}
|
||||
|
||||
state.key = state.key + 1;
|
||||
}
|
||||
},
|
||||
updateRange(state: CalendarRangesState, range: CalendarRange) {
|
||||
const found = state.ranges.find(
|
||||
(r) => r.calendarRangeId === range.id && r.is === "range",
|
||||
);
|
||||
const newEvent = calendarRangeToFullCalendarEvent(range);
|
||||
|
||||
if (found !== undefined) {
|
||||
found.start = newEvent.start;
|
||||
found.end = newEvent.end;
|
||||
found.locationId = range.location.id;
|
||||
found.locationName = range.location.name;
|
||||
}
|
||||
|
||||
state.key = state.key + 1;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
fetchRanges(
|
||||
ctx: Context,
|
||||
payload: { start: Date; end: Date },
|
||||
): Promise<null> {
|
||||
const start = payload.start;
|
||||
const end = payload.end;
|
||||
|
||||
if (ctx.rootGetters["me/getMe"] === null) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
|
||||
if (ctx.getters.isRangeLoaded({ start, end })) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
|
||||
ctx.commit("addLoaded", {
|
||||
start: start,
|
||||
end: end,
|
||||
});
|
||||
|
||||
return fetchCalendarRangeForUser(
|
||||
ctx.rootGetters["me/getMe"],
|
||||
start,
|
||||
end,
|
||||
).then((ranges: CalendarRange[]) => {
|
||||
ctx.commit("addRanges", ranges);
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
createRange(
|
||||
ctx: Context,
|
||||
{ start, end, location }: { start: Date; end: Date; location: Location },
|
||||
): Promise<null> {
|
||||
const url = `/api/1.0/calendar/calendar-range.json?`;
|
||||
|
||||
if (ctx.rootState.me.me === null) {
|
||||
throw new Error("user is currently null");
|
||||
}
|
||||
|
||||
const body = {
|
||||
user: {
|
||||
id: ctx.rootState.me.me.id,
|
||||
type: "user",
|
||||
},
|
||||
startDate: {
|
||||
datetime: datetimeToISO(start),
|
||||
},
|
||||
endDate: {
|
||||
datetime: datetimeToISO(end),
|
||||
},
|
||||
location: {
|
||||
id: location.id,
|
||||
type: "location",
|
||||
},
|
||||
} as CalendarRangeCreate;
|
||||
|
||||
return makeFetch<CalendarRangeCreate, CalendarRange>("POST", url, body)
|
||||
.then((newRange) => {
|
||||
ctx.commit("addRange", newRange);
|
||||
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
deleteRange(ctx: Context, calendarRangeId: number) {
|
||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||
|
||||
makeFetch<undefined, never>("DELETE", url).then(() => {
|
||||
ctx.commit("removeRange", calendarRangeId);
|
||||
});
|
||||
},
|
||||
patchRangeTime(
|
||||
ctx,
|
||||
{
|
||||
calendarRangeId,
|
||||
start,
|
||||
end,
|
||||
}: { calendarRangeId: number; start: Date; end: Date },
|
||||
): Promise<null> {
|
||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||
const body = {
|
||||
startDate: {
|
||||
datetime: datetimeToISO(start),
|
||||
},
|
||||
endDate: {
|
||||
datetime: datetimeToISO(end),
|
||||
},
|
||||
} as CalendarRangeEdit;
|
||||
|
||||
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
|
||||
.then((range) => {
|
||||
ctx.commit("updateRange", range);
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
patchRangeLocation(
|
||||
ctx,
|
||||
{
|
||||
location,
|
||||
calendarRangeId,
|
||||
}: { location: Location; calendarRangeId: number },
|
||||
): Promise<null> {
|
||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||
const body = {
|
||||
location: {
|
||||
id: location.id,
|
||||
type: "location",
|
||||
},
|
||||
} as CalendarRangeEdit;
|
||||
|
||||
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
|
||||
.then((range) => {
|
||||
ctx.commit("updateRange", range);
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
copyFromDayToAnotherDay(
|
||||
ctx,
|
||||
{ from, to }: { from: Date; to: Date },
|
||||
): Promise<null> {
|
||||
const rangesToCopy: EventInputCalendarRange[] =
|
||||
ctx.getters["getRangesOnDate"](from);
|
||||
const promises = [];
|
||||
|
||||
for (const r of rangesToCopy) {
|
||||
const start = new Date(ISOToDatetime(r.start) as Date);
|
||||
start.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
||||
const end = new Date(ISOToDatetime(r.end) as Date);
|
||||
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
||||
const location = ctx.rootGetters["locations/getLocationById"](
|
||||
r.locationId,
|
||||
);
|
||||
|
||||
promises.push(ctx.dispatch("createRange", { start, end, location }));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||
},
|
||||
copyFromWeekToAnotherWeek(
|
||||
ctx: Context,
|
||||
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
|
||||
): Promise<null> {
|
||||
const rangesToCopy: EventInputCalendarRange[] =
|
||||
ctx.getters["getRangesOnWeek"](fromMonday);
|
||||
const promises = [];
|
||||
const diffTime = toMonday.getTime() - fromMonday.getTime();
|
||||
for (const r of rangesToCopy) {
|
||||
const start = new Date(ISOToDatetime(r.start) as Date);
|
||||
const end = new Date(ISOToDatetime(r.end) as Date);
|
||||
start.setTime(start.getTime() + diffTime);
|
||||
end.setTime(end.getTime() + diffTime);
|
||||
const location = ctx.rootGetters["locations/getLocationById"](
|
||||
r.locationId,
|
||||
);
|
||||
|
||||
promises.push(ctx.dispatch("createRange", { start, end, location }));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||
},
|
||||
},
|
||||
} as Module<CalendarRangesState, State>;
|
||||
|
||||
@@ -8,109 +8,102 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
|
||||
import { COLORS } from "../../../Calendar/const";
|
||||
|
||||
export interface CalendarRemotesState {
|
||||
remotes: EventInput[];
|
||||
remotesLoaded: { start: number; end: number }[];
|
||||
remotesIndex: Set<string>;
|
||||
key: number;
|
||||
remotes: EventInput[];
|
||||
remotesLoaded: { start: number; end: number }[];
|
||||
remotesIndex: Set<string>;
|
||||
key: number;
|
||||
}
|
||||
|
||||
type Context = ActionContext<CalendarRemotesState, State>;
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: (): CalendarRemotesState => ({
|
||||
remotes: [],
|
||||
remotesLoaded: [],
|
||||
remotesIndex: new Set<string>(),
|
||||
key: 0,
|
||||
}),
|
||||
getters: {
|
||||
isRemotesLoaded:
|
||||
(state: CalendarRemotesState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.remotesLoaded) {
|
||||
if (
|
||||
start.getTime() === range.start &&
|
||||
end.getTime() === range.end
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
namespaced: true,
|
||||
state: (): CalendarRemotesState => ({
|
||||
remotes: [],
|
||||
remotesLoaded: [],
|
||||
remotesIndex: new Set<string>(),
|
||||
key: 0,
|
||||
}),
|
||||
getters: {
|
||||
isRemotesLoaded:
|
||||
(state: CalendarRemotesState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.remotesLoaded) {
|
||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
return false;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
|
||||
console.log("addRemotes", ranges);
|
||||
|
||||
const toAdd = ranges
|
||||
.map((cr) => remoteToFullCalendarEvent(cr))
|
||||
.filter((r) => !state.remotesIndex.has(r.id));
|
||||
|
||||
toAdd.forEach((r) => {
|
||||
state.remotesIndex.add(r.id);
|
||||
state.remotes.push(r);
|
||||
});
|
||||
state.key = state.key + toAdd.length;
|
||||
},
|
||||
mutations: {
|
||||
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
|
||||
console.log("addRemotes", ranges);
|
||||
|
||||
const toAdd = ranges
|
||||
.map((cr) => remoteToFullCalendarEvent(cr))
|
||||
.filter((r) => !state.remotesIndex.has(r.id));
|
||||
|
||||
toAdd.forEach((r) => {
|
||||
state.remotesIndex.add(r.id);
|
||||
state.remotes.push(r);
|
||||
});
|
||||
state.key = state.key + toAdd.length;
|
||||
},
|
||||
addLoaded(
|
||||
state: CalendarRemotesState,
|
||||
payload: { start: Date; end: Date },
|
||||
) {
|
||||
state.remotesLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
});
|
||||
},
|
||||
addLoaded(
|
||||
state: CalendarRemotesState,
|
||||
payload: { start: Date; end: Date },
|
||||
) {
|
||||
state.remotesLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
});
|
||||
},
|
||||
actions: {
|
||||
fetchRemotes(
|
||||
ctx: Context,
|
||||
payload: { start: Date; end: Date },
|
||||
): Promise<null> {
|
||||
const start = payload.start;
|
||||
const end = payload.end;
|
||||
},
|
||||
actions: {
|
||||
fetchRemotes(
|
||||
ctx: Context,
|
||||
payload: { start: Date; end: Date },
|
||||
): Promise<null> {
|
||||
const start = payload.start;
|
||||
const end = payload.end;
|
||||
|
||||
if (ctx.rootGetters["me/getMe"] === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
if (ctx.rootGetters["me/getMe"] === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (ctx.getters.isRemotesLoaded({ start, end })) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
if (ctx.getters.isRemotesLoaded({ start, end })) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
|
||||
ctx.commit("addLoaded", {
|
||||
start: start,
|
||||
end: end,
|
||||
});
|
||||
ctx.commit("addLoaded", {
|
||||
start: start,
|
||||
end: end,
|
||||
});
|
||||
|
||||
return fetchCalendarRemoteForUser(
|
||||
ctx.rootGetters["me/getMe"],
|
||||
start,
|
||||
end,
|
||||
)
|
||||
.then((remotes: CalendarRemote[]) => {
|
||||
// to be add when reactivity problem will be solve ?
|
||||
//ctx.commit('addRemotes', remotes);
|
||||
const inputs = remotes
|
||||
.map((cr) => remoteToFullCalendarEvent(cr))
|
||||
.map((cr) => ({
|
||||
...cr,
|
||||
backgroundColor: COLORS[0],
|
||||
textColor: "black",
|
||||
editable: false,
|
||||
}));
|
||||
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||
root: true,
|
||||
});
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((e: TransportExceptionInterface) => {
|
||||
console.error(e);
|
||||
return fetchCalendarRemoteForUser(ctx.rootGetters["me/getMe"], start, end)
|
||||
.then((remotes: CalendarRemote[]) => {
|
||||
// to be add when reactivity problem will be solve ?
|
||||
//ctx.commit('addRemotes', remotes);
|
||||
const inputs = remotes
|
||||
.map((cr) => remoteToFullCalendarEvent(cr))
|
||||
.map((cr) => ({
|
||||
...cr,
|
||||
backgroundColor: COLORS[0],
|
||||
textColor: "black",
|
||||
editable: false,
|
||||
}));
|
||||
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||
root: true,
|
||||
});
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((e: TransportExceptionInterface) => {
|
||||
console.error(e);
|
||||
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
},
|
||||
} as Module<CalendarRemotesState, State>;
|
||||
|
||||
@@ -2,77 +2,77 @@ import { State } from "./../index";
|
||||
import { ActionContext } from "vuex";
|
||||
|
||||
export interface FullCalendarState {
|
||||
currentView: {
|
||||
start: Date | null;
|
||||
end: Date | null;
|
||||
};
|
||||
key: number;
|
||||
currentView: {
|
||||
start: Date | null;
|
||||
end: Date | null;
|
||||
};
|
||||
key: number;
|
||||
}
|
||||
|
||||
type Context = ActionContext<FullCalendarState, State>;
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: (): FullCalendarState => ({
|
||||
currentView: {
|
||||
start: null,
|
||||
end: null,
|
||||
},
|
||||
key: 0,
|
||||
}),
|
||||
mutations: {
|
||||
setCurrentDatesView: function (
|
||||
state: FullCalendarState,
|
||||
payload: { start: Date; end: Date },
|
||||
): void {
|
||||
state.currentView.start = payload.start;
|
||||
state.currentView.end = payload.end;
|
||||
},
|
||||
increaseKey: function (state: FullCalendarState): void {
|
||||
state.key = state.key + 1;
|
||||
},
|
||||
namespaced: true,
|
||||
state: (): FullCalendarState => ({
|
||||
currentView: {
|
||||
start: null,
|
||||
end: null,
|
||||
},
|
||||
actions: {
|
||||
setCurrentDatesView(
|
||||
ctx: Context,
|
||||
{ start, end }: { start: Date | null; end: Date | null },
|
||||
): Promise<null> {
|
||||
console.log("dispatch setCurrentDatesView", { start, end });
|
||||
|
||||
if (
|
||||
ctx.state.currentView.start !== start ||
|
||||
ctx.state.currentView.end !== end
|
||||
) {
|
||||
ctx.commit("setCurrentDatesView", { start, end });
|
||||
}
|
||||
|
||||
if (start !== null && end !== null) {
|
||||
return Promise.all([
|
||||
ctx
|
||||
.dispatch(
|
||||
"calendarRanges/fetchRanges",
|
||||
{ start, end },
|
||||
{ root: true },
|
||||
)
|
||||
.then((_) => Promise.resolve(null)),
|
||||
ctx
|
||||
.dispatch(
|
||||
"calendarRemotes/fetchRemotes",
|
||||
{ start, end },
|
||||
{ root: true },
|
||||
)
|
||||
.then((_) => Promise.resolve(null)),
|
||||
ctx
|
||||
.dispatch(
|
||||
"calendarLocals/fetchLocals",
|
||||
{ start, end },
|
||||
{ root: true },
|
||||
)
|
||||
.then((_) => Promise.resolve(null)),
|
||||
]).then((_) => Promise.resolve(null));
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
},
|
||||
key: 0,
|
||||
}),
|
||||
mutations: {
|
||||
setCurrentDatesView: function (
|
||||
state: FullCalendarState,
|
||||
payload: { start: Date; end: Date },
|
||||
): void {
|
||||
state.currentView.start = payload.start;
|
||||
state.currentView.end = payload.end;
|
||||
},
|
||||
increaseKey: function (state: FullCalendarState): void {
|
||||
state.key = state.key + 1;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setCurrentDatesView(
|
||||
ctx: Context,
|
||||
{ start, end }: { start: Date | null; end: Date | null },
|
||||
): Promise<null> {
|
||||
console.log("dispatch setCurrentDatesView", { start, end });
|
||||
|
||||
if (
|
||||
ctx.state.currentView.start !== start ||
|
||||
ctx.state.currentView.end !== end
|
||||
) {
|
||||
ctx.commit("setCurrentDatesView", { start, end });
|
||||
}
|
||||
|
||||
if (start !== null && end !== null) {
|
||||
return Promise.all([
|
||||
ctx
|
||||
.dispatch(
|
||||
"calendarRanges/fetchRanges",
|
||||
{ start, end },
|
||||
{ root: true },
|
||||
)
|
||||
.then((_) => Promise.resolve(null)),
|
||||
ctx
|
||||
.dispatch(
|
||||
"calendarRemotes/fetchRemotes",
|
||||
{ start, end },
|
||||
{ root: true },
|
||||
)
|
||||
.then((_) => Promise.resolve(null)),
|
||||
ctx
|
||||
.dispatch(
|
||||
"calendarLocals/fetchLocals",
|
||||
{ start, end },
|
||||
{ root: true },
|
||||
)
|
||||
.then((_) => Promise.resolve(null)),
|
||||
]).then((_) => Promise.resolve(null));
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,61 +5,61 @@ import { getLocations } from "../../../../../../../ChillMainBundle/Resources/pub
|
||||
import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||
|
||||
export interface LocationState {
|
||||
locations: Location[];
|
||||
locationPicked: Location | null;
|
||||
currentLocation: Location | null;
|
||||
locations: Location[];
|
||||
locationPicked: Location | null;
|
||||
currentLocation: Location | null;
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: (): LocationState => {
|
||||
return {
|
||||
locations: [],
|
||||
locationPicked: null,
|
||||
currentLocation: null,
|
||||
};
|
||||
namespaced: true,
|
||||
state: (): LocationState => {
|
||||
return {
|
||||
locations: [],
|
||||
locationPicked: null,
|
||||
currentLocation: null,
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
getLocationById:
|
||||
(state) =>
|
||||
(id: number): Location | undefined => {
|
||||
return state.locations.find((l) => l.id === id);
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setLocations(state, locations): void {
|
||||
state.locations = locations;
|
||||
},
|
||||
getters: {
|
||||
getLocationById:
|
||||
(state) =>
|
||||
(id: number): Location | undefined => {
|
||||
return state.locations.find((l) => l.id === id);
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setLocations(state, locations): void {
|
||||
state.locations = locations;
|
||||
},
|
||||
setLocationPicked(state, location: Location | null): void {
|
||||
if (null === location) {
|
||||
state.locationPicked = null;
|
||||
return;
|
||||
}
|
||||
setLocationPicked(state, location: Location | null): void {
|
||||
if (null === location) {
|
||||
state.locationPicked = null;
|
||||
return;
|
||||
}
|
||||
|
||||
state.locationPicked =
|
||||
state.locations.find((l) => l.id === location.id) || null;
|
||||
},
|
||||
setCurrentLocation(state, location: Location | null): void {
|
||||
if (null === location) {
|
||||
state.currentLocation = null;
|
||||
return;
|
||||
}
|
||||
state.locationPicked =
|
||||
state.locations.find((l) => l.id === location.id) || null;
|
||||
},
|
||||
setCurrentLocation(state, location: Location | null): void {
|
||||
if (null === location) {
|
||||
state.currentLocation = null;
|
||||
return;
|
||||
}
|
||||
|
||||
state.currentLocation =
|
||||
state.locations.find((l) => l.id === location.id) || null;
|
||||
},
|
||||
state.currentLocation =
|
||||
state.locations.find((l) => l.id === location.id) || null;
|
||||
},
|
||||
actions: {
|
||||
getLocations(ctx): Promise<void> {
|
||||
return getLocations().then((locations) => {
|
||||
ctx.commit("setLocations", locations);
|
||||
return Promise.resolve();
|
||||
});
|
||||
},
|
||||
getCurrentLocation(ctx): Promise<void> {
|
||||
return whereami().then((location) => {
|
||||
ctx.commit("setCurrentLocation", location);
|
||||
});
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
getLocations(ctx): Promise<void> {
|
||||
return getLocations().then((locations) => {
|
||||
ctx.commit("setLocations", locations);
|
||||
return Promise.resolve();
|
||||
});
|
||||
},
|
||||
getCurrentLocation(ctx): Promise<void> {
|
||||
return whereami().then((location) => {
|
||||
ctx.commit("setCurrentLocation", location);
|
||||
});
|
||||
},
|
||||
},
|
||||
} as Module<LocationState, State>;
|
||||
|
||||
@@ -3,24 +3,24 @@ import { User } from "../../../../../../../ChillMainBundle/Resources/public/type
|
||||
import { ActionContext } from "vuex";
|
||||
|
||||
export interface MeState {
|
||||
me: User | null;
|
||||
me: User | null;
|
||||
}
|
||||
|
||||
type Context = ActionContext<MeState, State>;
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: (): MeState => ({
|
||||
me: null,
|
||||
}),
|
||||
getters: {
|
||||
getMe: function (state: MeState): User | null {
|
||||
return state.me;
|
||||
},
|
||||
namespaced: true,
|
||||
state: (): MeState => ({
|
||||
me: null,
|
||||
}),
|
||||
getters: {
|
||||
getMe: function (state: MeState): User | null {
|
||||
return state.me;
|
||||
},
|
||||
mutations: {
|
||||
setWhoAmi(state: MeState, me: User) {
|
||||
state.me = me;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setWhoAmi(state: MeState, me: User) {
|
||||
state.me = me;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="chill-red">
|
||||
{{ $t("choose_your_calendar_user") }}
|
||||
</h2>
|
||||
<VueMultiselect
|
||||
name="field"
|
||||
id="calendarUserSelector"
|
||||
v-model="value"
|
||||
track-by="id"
|
||||
label="value"
|
||||
:custom-label="transName"
|
||||
:placeholder="$t('select_user')"
|
||||
:multiple="true"
|
||||
:close-on-select="false"
|
||||
:allow-empty="true"
|
||||
:model-value="value"
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('multiselect.deselect_label')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
@select="selectUsers"
|
||||
@remove="unSelectUsers"
|
||||
@close="coloriseSelectedValues"
|
||||
:options="options"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="myCalendar"
|
||||
class="form-check-input"
|
||||
v-model="showMyCalendarWidget"
|
||||
/>
|
||||
<label class="form-check-label" for="myCalendar">{{
|
||||
$t("show_my_calendar")
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="weekends"
|
||||
class="form-check-input"
|
||||
@click="toggleWeekends"
|
||||
/>
|
||||
<label class="form-check-label" for="weekends">{{
|
||||
$t("show_weekends")
|
||||
}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="chill-red">
|
||||
{{ $t("choose_your_calendar_user") }}
|
||||
</h2>
|
||||
<VueMultiselect
|
||||
name="field"
|
||||
id="calendarUserSelector"
|
||||
v-model="value"
|
||||
track-by="id"
|
||||
label="value"
|
||||
:custom-label="transName"
|
||||
:placeholder="$t('select_user')"
|
||||
:multiple="true"
|
||||
:close-on-select="false"
|
||||
:allow-empty="true"
|
||||
:model-value="value"
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('multiselect.deselect_label')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
@select="selectUsers"
|
||||
@remove="unSelectUsers"
|
||||
@close="coloriseSelectedValues"
|
||||
:options="options"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="myCalendar"
|
||||
class="form-check-input"
|
||||
v-model="showMyCalendarWidget"
|
||||
/>
|
||||
<label class="form-check-label" for="myCalendar">{{
|
||||
$t("show_my_calendar")
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="weekends"
|
||||
class="form-check-input"
|
||||
@click="toggleWeekends"
|
||||
/>
|
||||
<label class="form-check-label" for="weekends">{{
|
||||
$t("show_weekends")
|
||||
}}</label>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { fetchCalendarRanges, fetchCalendar } from "../../_api/api";
|
||||
@@ -53,206 +53,183 @@ import VueMultiselect from "vue-multiselect";
|
||||
import { whoami } from "ChillPersonAssets/vuejs/AccompanyingCourse/api";
|
||||
|
||||
const COLORS = [
|
||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||
"#8dd3c7",
|
||||
"#ffffb3",
|
||||
"#bebada",
|
||||
"#fb8072",
|
||||
"#80b1d3",
|
||||
"#fdb462",
|
||||
"#b3de69",
|
||||
"#fccde5",
|
||||
"#d9d9d9",
|
||||
"#bc80bd",
|
||||
"#ccebc5",
|
||||
"#ffed6f",
|
||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||
"#8dd3c7",
|
||||
"#ffffb3",
|
||||
"#bebada",
|
||||
"#fb8072",
|
||||
"#80b1d3",
|
||||
"#fdb462",
|
||||
"#b3de69",
|
||||
"#fccde5",
|
||||
"#d9d9d9",
|
||||
"#bc80bd",
|
||||
"#ccebc5",
|
||||
"#ffed6f",
|
||||
];
|
||||
|
||||
export default {
|
||||
name: "CalendarUserSelector",
|
||||
components: { VueMultiselect },
|
||||
props: [
|
||||
"users",
|
||||
"updateEventsSource",
|
||||
"calendarEvents",
|
||||
"showMyCalendar",
|
||||
"toggleMyCalendar",
|
||||
"toggleWeekends",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
errorMsg: [],
|
||||
value: [],
|
||||
options: [],
|
||||
};
|
||||
name: "CalendarUserSelector",
|
||||
components: { VueMultiselect },
|
||||
props: [
|
||||
"users",
|
||||
"updateEventsSource",
|
||||
"calendarEvents",
|
||||
"showMyCalendar",
|
||||
"toggleMyCalendar",
|
||||
"toggleWeekends",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
errorMsg: [],
|
||||
value: [],
|
||||
options: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showMyCalendarWidget: {
|
||||
set(value) {
|
||||
this.toggleMyCalendar(value);
|
||||
this.updateEventsSource();
|
||||
},
|
||||
get() {
|
||||
return this.showMyCalendar;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showMyCalendarWidget: {
|
||||
set(value) {
|
||||
this.toggleMyCalendar(value);
|
||||
this.updateEventsSource();
|
||||
},
|
||||
get() {
|
||||
return this.showMyCalendar;
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.fetchData();
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.fetchData();
|
||||
},
|
||||
fetchData() {
|
||||
fetchCalendarRanges()
|
||||
.then(
|
||||
(calendarRanges) =>
|
||||
new Promise((resolve, reject) => {
|
||||
let results = calendarRanges.results;
|
||||
fetchData() {
|
||||
fetchCalendarRanges()
|
||||
.then(
|
||||
(calendarRanges) =>
|
||||
new Promise((resolve, reject) => {
|
||||
let results = calendarRanges.results;
|
||||
|
||||
let users = [];
|
||||
let users = [];
|
||||
|
||||
results.forEach((i) => {
|
||||
if (!users.some((j) => i.user.id === j.id)) {
|
||||
let ratio = Math.floor(
|
||||
users.length / COLORS.length,
|
||||
);
|
||||
let colorIndex =
|
||||
users.length - ratio * COLORS.length;
|
||||
users.push({
|
||||
id: i.user.id,
|
||||
username: i.user.username,
|
||||
color: COLORS[colorIndex],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let calendarEvents = [];
|
||||
users.forEach((u) => {
|
||||
let arr = results
|
||||
.filter((i) => i.user.id === u.id)
|
||||
.map((i) => ({
|
||||
start: i.startDate.datetime,
|
||||
end: i.endDate.datetime,
|
||||
calendarRangeId: i.id,
|
||||
sourceColor: u.color,
|
||||
//display: 'background' // can be an option for the disponibility
|
||||
}));
|
||||
calendarEvents.push({
|
||||
events: arr,
|
||||
color: u.color,
|
||||
textColor: "#444444",
|
||||
editable: false,
|
||||
id: u.id,
|
||||
});
|
||||
});
|
||||
|
||||
this.users.loaded = users;
|
||||
this.options = users;
|
||||
|
||||
this.calendarEvents.loaded = calendarEvents;
|
||||
whoami().then(
|
||||
(me) =>
|
||||
new Promise((resolve, reject) => {
|
||||
this.users.logged = me;
|
||||
let currentUser = users.find(
|
||||
(u) => u.id === me.id,
|
||||
);
|
||||
this.value = currentUser;
|
||||
|
||||
fetchCalendar(currentUser.id).then(
|
||||
(calendar) =>
|
||||
new Promise(
|
||||
(resolve, reject) => {
|
||||
let results =
|
||||
calendar.results;
|
||||
let events =
|
||||
results.map(
|
||||
(i) => ({
|
||||
start: i
|
||||
.startDate
|
||||
.datetime,
|
||||
end: i
|
||||
.endDate
|
||||
.datetime,
|
||||
}),
|
||||
);
|
||||
let calendarEventsCurrentUser =
|
||||
{
|
||||
events: events,
|
||||
color: "darkblue",
|
||||
id: 1000,
|
||||
editable: false,
|
||||
};
|
||||
this.calendarEvents.user =
|
||||
calendarEventsCurrentUser;
|
||||
|
||||
this.selectUsers(
|
||||
currentUser,
|
||||
);
|
||||
|
||||
resolve();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
resolve();
|
||||
}),
|
||||
);
|
||||
|
||||
resolve();
|
||||
}),
|
||||
)
|
||||
.catch((error) => {
|
||||
this.errorMsg.push(error.message);
|
||||
});
|
||||
},
|
||||
transName(value) {
|
||||
return `${value.username}`;
|
||||
},
|
||||
coloriseSelectedValues() {
|
||||
let tags = document.querySelectorAll(
|
||||
"div.multiselect__tags-wrap",
|
||||
)[0];
|
||||
|
||||
if (tags.hasChildNodes()) {
|
||||
let children = tags.childNodes;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let child = children[i];
|
||||
if (child.nodeType === Node.ELEMENT_NODE) {
|
||||
this.users.selected.forEach((u) => {
|
||||
if (child.hasChildNodes()) {
|
||||
if (child.firstChild.innerText == u.username) {
|
||||
child.style.background = u.color;
|
||||
child.firstChild.style.color = "#444444";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
results.forEach((i) => {
|
||||
if (!users.some((j) => i.user.id === j.id)) {
|
||||
let ratio = Math.floor(users.length / COLORS.length);
|
||||
let colorIndex = users.length - ratio * COLORS.length;
|
||||
users.push({
|
||||
id: i.user.id,
|
||||
username: i.user.username,
|
||||
color: COLORS[colorIndex],
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
selectEvents() {
|
||||
let selectedUsersId = this.users.selected.map((a) => a.id);
|
||||
this.calendarEvents.selected = this.calendarEvents.loaded.filter(
|
||||
(a) => selectedUsersId.includes(a.id),
|
||||
);
|
||||
},
|
||||
selectUsers(value) {
|
||||
this.users.selected.push(value);
|
||||
this.coloriseSelectedValues();
|
||||
this.selectEvents();
|
||||
this.updateEventsSource();
|
||||
},
|
||||
unSelectUsers(value) {
|
||||
this.users.selected = this.users.selected.filter(
|
||||
(a) => a.id != value.id,
|
||||
);
|
||||
this.selectEvents();
|
||||
this.updateEventsSource();
|
||||
},
|
||||
});
|
||||
|
||||
let calendarEvents = [];
|
||||
users.forEach((u) => {
|
||||
let arr = results
|
||||
.filter((i) => i.user.id === u.id)
|
||||
.map((i) => ({
|
||||
start: i.startDate.datetime,
|
||||
end: i.endDate.datetime,
|
||||
calendarRangeId: i.id,
|
||||
sourceColor: u.color,
|
||||
//display: 'background' // can be an option for the disponibility
|
||||
}));
|
||||
calendarEvents.push({
|
||||
events: arr,
|
||||
color: u.color,
|
||||
textColor: "#444444",
|
||||
editable: false,
|
||||
id: u.id,
|
||||
});
|
||||
});
|
||||
|
||||
this.users.loaded = users;
|
||||
this.options = users;
|
||||
|
||||
this.calendarEvents.loaded = calendarEvents;
|
||||
whoami().then(
|
||||
(me) =>
|
||||
new Promise((resolve, reject) => {
|
||||
this.users.logged = me;
|
||||
let currentUser = users.find((u) => u.id === me.id);
|
||||
this.value = currentUser;
|
||||
|
||||
fetchCalendar(currentUser.id).then(
|
||||
(calendar) =>
|
||||
new Promise((resolve, reject) => {
|
||||
let results = calendar.results;
|
||||
let events = results.map((i) => ({
|
||||
start: i.startDate.datetime,
|
||||
end: i.endDate.datetime,
|
||||
}));
|
||||
let calendarEventsCurrentUser = {
|
||||
events: events,
|
||||
color: "darkblue",
|
||||
id: 1000,
|
||||
editable: false,
|
||||
};
|
||||
this.calendarEvents.user = calendarEventsCurrentUser;
|
||||
|
||||
this.selectUsers(currentUser);
|
||||
|
||||
resolve();
|
||||
}),
|
||||
);
|
||||
|
||||
resolve();
|
||||
}),
|
||||
);
|
||||
|
||||
resolve();
|
||||
}),
|
||||
)
|
||||
.catch((error) => {
|
||||
this.errorMsg.push(error.message);
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
transName(value) {
|
||||
return `${value.username}`;
|
||||
},
|
||||
coloriseSelectedValues() {
|
||||
let tags = document.querySelectorAll("div.multiselect__tags-wrap")[0];
|
||||
|
||||
if (tags.hasChildNodes()) {
|
||||
let children = tags.childNodes;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let child = children[i];
|
||||
if (child.nodeType === Node.ELEMENT_NODE) {
|
||||
this.users.selected.forEach((u) => {
|
||||
if (child.hasChildNodes()) {
|
||||
if (child.firstChild.innerText == u.username) {
|
||||
child.style.background = u.color;
|
||||
child.firstChild.style.color = "#444444";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
selectEvents() {
|
||||
let selectedUsersId = this.users.selected.map((a) => a.id);
|
||||
this.calendarEvents.selected = this.calendarEvents.loaded.filter((a) =>
|
||||
selectedUsersId.includes(a.id),
|
||||
);
|
||||
},
|
||||
selectUsers(value) {
|
||||
this.users.selected.push(value);
|
||||
this.coloriseSelectedValues();
|
||||
this.selectEvents();
|
||||
this.updateEventsSource();
|
||||
},
|
||||
unSelectUsers(value) {
|
||||
this.users.selected = this.users.selected.filter((a) => a.id != value.id);
|
||||
this.selectEvents();
|
||||
this.updateEventsSource();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,59 +1,54 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="templates.length > 0">
|
||||
<slot name="title">
|
||||
<h2>{{ $t("generate_document") }}</h2>
|
||||
</slot>
|
||||
<div>
|
||||
<template v-if="templates.length > 0">
|
||||
<slot name="title">
|
||||
<h2>{{ $t("generate_document") }}</h2>
|
||||
</slot>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<slot name="label">
|
||||
<label>{{ $t("select_a_template") }}</label>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="input-group mb-3">
|
||||
<select class="form-select" v-model="template">
|
||||
<option disabled selected value="">
|
||||
{{ $t("choose_a_template") }}
|
||||
</option>
|
||||
<template v-for="t in templates" :key="t.id">
|
||||
<option :value="t.id">
|
||||
{{
|
||||
localizeString(t.name) ||
|
||||
"Aucun nom défini"
|
||||
}}
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
<a
|
||||
v-if="canGenerate"
|
||||
class="btn btn-update btn-sm change-icon"
|
||||
:href="buildUrlGenerate"
|
||||
@click.prevent="
|
||||
clickGenerate($event, buildUrlGenerate)
|
||||
"
|
||||
><i class="fa fa-fw fa-cog"
|
||||
/></a>
|
||||
<a
|
||||
v-else
|
||||
class="btn btn-update btn-sm change-icon"
|
||||
href="#"
|
||||
disabled
|
||||
><i class="fa fa-fw fa-cog"
|
||||
/></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="hasDescription">
|
||||
<div class="col-md-8 align-self-end">
|
||||
<p>{{ getDescription }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<slot name="label">
|
||||
<label>{{ $t("select_a_template") }}</label>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="input-group mb-3">
|
||||
<select class="form-select" v-model="template">
|
||||
<option disabled selected value="">
|
||||
{{ $t("choose_a_template") }}
|
||||
</option>
|
||||
<template v-for="t in templates" :key="t.id">
|
||||
<option :value="t.id">
|
||||
{{ localizeString(t.name) || "Aucun nom défini" }}
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
<a
|
||||
v-if="canGenerate"
|
||||
class="btn btn-update btn-sm change-icon"
|
||||
:href="buildUrlGenerate"
|
||||
@click.prevent="clickGenerate($event, buildUrlGenerate)"
|
||||
><i class="fa fa-fw fa-cog"
|
||||
/></a>
|
||||
<a
|
||||
v-else
|
||||
class="btn btn-update btn-sm change-icon"
|
||||
href="#"
|
||||
disabled
|
||||
><i class="fa fa-fw fa-cog"
|
||||
/></a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="hasDescription">
|
||||
<div class="col-md-8 align-self-end">
|
||||
<p>{{ getDescription }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -61,83 +56,83 @@ import { buildLink } from "ChillDocGeneratorAssets/lib/document-generator";
|
||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||
|
||||
export default {
|
||||
name: "PickTemplate",
|
||||
props: {
|
||||
entityId: [String, Number],
|
||||
entityClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
templates: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
preventDefaultMoveToGenerate: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
name: "PickTemplate",
|
||||
props: {
|
||||
entityId: [String, Number],
|
||||
entityClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
emits: ["goToGenerateDocument"],
|
||||
data() {
|
||||
return {
|
||||
template: null,
|
||||
};
|
||||
templates: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
canGenerate() {
|
||||
return this.template != null;
|
||||
},
|
||||
hasDescription() {
|
||||
if (this.template == null) {
|
||||
return false;
|
||||
}
|
||||
preventDefaultMoveToGenerate: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["goToGenerateDocument"],
|
||||
data() {
|
||||
return {
|
||||
template: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canGenerate() {
|
||||
return this.template != null;
|
||||
},
|
||||
hasDescription() {
|
||||
if (this.template == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
getDescription() {
|
||||
if (null === this.template) {
|
||||
return "";
|
||||
}
|
||||
let desc = this.templates.find((t) => t.id === this.template);
|
||||
if (null === desc) {
|
||||
return "";
|
||||
}
|
||||
return desc.description || "";
|
||||
},
|
||||
buildUrlGenerate() {
|
||||
if (null === this.template) {
|
||||
return "#";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
getDescription() {
|
||||
if (null === this.template) {
|
||||
return "";
|
||||
}
|
||||
let desc = this.templates.find((t) => t.id === this.template);
|
||||
if (null === desc) {
|
||||
return "";
|
||||
}
|
||||
return desc.description || "";
|
||||
},
|
||||
buildUrlGenerate() {
|
||||
if (null === this.template) {
|
||||
return "#";
|
||||
}
|
||||
|
||||
return buildLink(this.template, this.entityId, this.entityClass);
|
||||
},
|
||||
return buildLink(this.template, this.entityId, this.entityClass);
|
||||
},
|
||||
methods: {
|
||||
localizeString(str) {
|
||||
return localizeString(str);
|
||||
},
|
||||
clickGenerate(event, link) {
|
||||
if (!this.preventDefaultMoveToGenerate) {
|
||||
window.location.assign(link);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
localizeString(str) {
|
||||
return localizeString(str);
|
||||
},
|
||||
clickGenerate(event, link) {
|
||||
if (!this.preventDefaultMoveToGenerate) {
|
||||
window.location.assign(link);
|
||||
}
|
||||
|
||||
this.$emit("goToGenerateDocument", {
|
||||
event,
|
||||
link,
|
||||
template: this.template,
|
||||
});
|
||||
},
|
||||
this.$emit("goToGenerateDocument", {
|
||||
event,
|
||||
link,
|
||||
template: this.template,
|
||||
});
|
||||
},
|
||||
i18n: {
|
||||
messages: {
|
||||
fr: {
|
||||
generate_document: "Générer un document",
|
||||
select_a_template: "Choisir un modèle",
|
||||
choose_a_template: "Choisir",
|
||||
},
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
messages: {
|
||||
fr: {
|
||||
generate_document: "Générer un document",
|
||||
select_a_template: "Choisir un modèle",
|
||||
choose_a_template: "Choisir",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,20 +6,20 @@ const algo = "AES-CBC";
|
||||
const URL_POST = "/asyncupload/temp_url/generate/post";
|
||||
|
||||
const keyDefinition = {
|
||||
name: algo,
|
||||
length: 256,
|
||||
name: algo,
|
||||
length: 256,
|
||||
};
|
||||
|
||||
const createFilename = (): string => {
|
||||
let text = "";
|
||||
const possible =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let text = "";
|
||||
const possible =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
for (let i = 0; i < 7; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
|
||||
return text;
|
||||
return text;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -30,59 +30,59 @@ const createFilename = (): string => {
|
||||
* @returns {Promise<StoredObject>} A Promise that resolves to the newly created StoredObject.
|
||||
*/
|
||||
export const fetchNewStoredObject = async (): Promise<StoredObject> => {
|
||||
return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null);
|
||||
return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null);
|
||||
};
|
||||
|
||||
export const uploadVersion = async (
|
||||
uploadFile: ArrayBuffer,
|
||||
storedObject: StoredObject,
|
||||
uploadFile: ArrayBuffer,
|
||||
storedObject: StoredObject,
|
||||
): Promise<string> => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("expires_delay", "180");
|
||||
params.append("submit_delay", "180");
|
||||
const asyncData: PostStoreObjectSignature = await makeFetch(
|
||||
"GET",
|
||||
`/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
|
||||
"?" +
|
||||
params.toString(),
|
||||
);
|
||||
const suffix = createFilename();
|
||||
const filename = asyncData.prefix + suffix;
|
||||
const formData = new FormData();
|
||||
formData.append("redirect", asyncData.redirect);
|
||||
formData.append("max_file_size", asyncData.max_file_size.toString());
|
||||
formData.append("max_file_count", asyncData.max_file_count.toString());
|
||||
formData.append("expires", asyncData.expires.toString());
|
||||
formData.append("signature", asyncData.signature);
|
||||
formData.append(filename, new Blob([uploadFile]), suffix);
|
||||
const params = new URLSearchParams();
|
||||
params.append("expires_delay", "180");
|
||||
params.append("submit_delay", "180");
|
||||
const asyncData: PostStoreObjectSignature = await makeFetch(
|
||||
"GET",
|
||||
`/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
|
||||
"?" +
|
||||
params.toString(),
|
||||
);
|
||||
const suffix = createFilename();
|
||||
const filename = asyncData.prefix + suffix;
|
||||
const formData = new FormData();
|
||||
formData.append("redirect", asyncData.redirect);
|
||||
formData.append("max_file_size", asyncData.max_file_size.toString());
|
||||
formData.append("max_file_count", asyncData.max_file_count.toString());
|
||||
formData.append("expires", asyncData.expires.toString());
|
||||
formData.append("signature", asyncData.signature);
|
||||
formData.append(filename, new Blob([uploadFile]), suffix);
|
||||
|
||||
const response = await window.fetch(asyncData.url, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
const response = await window.fetch(asyncData.url, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Error while sending file to store", response);
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
if (!response.ok) {
|
||||
console.error("Error while sending file to store", response);
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
return Promise.resolve(filename);
|
||||
return Promise.resolve(filename);
|
||||
};
|
||||
|
||||
export const encryptFile = async (
|
||||
originalFile: ArrayBuffer,
|
||||
originalFile: ArrayBuffer,
|
||||
): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => {
|
||||
const iv = crypto.getRandomValues(new Uint8Array(16));
|
||||
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
]);
|
||||
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
|
||||
const encrypted = await window.crypto.subtle.encrypt(
|
||||
{ name: algo, iv: iv },
|
||||
key,
|
||||
originalFile,
|
||||
);
|
||||
const iv = crypto.getRandomValues(new Uint8Array(16));
|
||||
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
]);
|
||||
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
|
||||
const encrypted = await window.crypto.subtle.encrypt(
|
||||
{ name: algo, iv: iv },
|
||||
key,
|
||||
originalFile,
|
||||
);
|
||||
|
||||
return Promise.resolve([encrypted, iv, exportedKey]);
|
||||
return Promise.resolve([encrypted, iv, exportedKey]);
|
||||
};
|
||||
|
||||
@@ -2,9 +2,9 @@ import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
|
||||
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
||||
|
||||
export function fetch_generic_docs_by_accompanying_period(
|
||||
periodId: number,
|
||||
periodId: number,
|
||||
): Promise<GenericDocForAccompanyingPeriod[]> {
|
||||
return fetchResults(
|
||||
`/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
|
||||
);
|
||||
return fetchResults(
|
||||
`/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,117 +6,116 @@ import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vue
|
||||
const i18n = _createI18n({});
|
||||
|
||||
const startApp = (
|
||||
divElement: HTMLDivElement,
|
||||
collectionEntry: null | HTMLLIElement,
|
||||
divElement: HTMLDivElement,
|
||||
collectionEntry: null | HTMLLIElement,
|
||||
): void => {
|
||||
console.log("app started", divElement);
|
||||
console.log("app started", divElement);
|
||||
|
||||
const inputTitle = collectionEntry?.querySelector("input[type='text']");
|
||||
const inputTitle = collectionEntry?.querySelector("input[type='text']");
|
||||
|
||||
const input_stored_object: HTMLInputElement | null =
|
||||
divElement.querySelector("input[data-stored-object]");
|
||||
if (null === input_stored_object) {
|
||||
throw new Error("input to stored object not found");
|
||||
}
|
||||
const input_stored_object: HTMLInputElement | null = divElement.querySelector(
|
||||
"input[data-stored-object]",
|
||||
);
|
||||
if (null === input_stored_object) {
|
||||
throw new Error("input to stored object not found");
|
||||
}
|
||||
|
||||
let existingDoc: StoredObject | null = null;
|
||||
if (input_stored_object.value !== "") {
|
||||
existingDoc = JSON.parse(input_stored_object.value);
|
||||
}
|
||||
const app_container = document.createElement("div");
|
||||
divElement.appendChild(app_container);
|
||||
let existingDoc: StoredObject | null = null;
|
||||
if (input_stored_object.value !== "") {
|
||||
existingDoc = JSON.parse(input_stored_object.value);
|
||||
}
|
||||
const app_container = document.createElement("div");
|
||||
divElement.appendChild(app_container);
|
||||
|
||||
const app = createApp({
|
||||
template:
|
||||
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
|
||||
data() {
|
||||
return {
|
||||
existingDoc: existingDoc,
|
||||
inputTitle: inputTitle,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
DropFileWidget,
|
||||
},
|
||||
methods: {
|
||||
addDocument: function ({
|
||||
stored_object,
|
||||
stored_object_version,
|
||||
file_name,
|
||||
}: {
|
||||
stored_object: StoredObject;
|
||||
stored_object_version: StoredObjectVersion;
|
||||
file_name: string;
|
||||
}): void {
|
||||
stored_object.title = file_name;
|
||||
console.log("object added", stored_object);
|
||||
console.log("version added", stored_object_version);
|
||||
this.$data.existingDoc = stored_object;
|
||||
this.$data.existingDoc.currentVersion = stored_object_version;
|
||||
input_stored_object.value = JSON.stringify(
|
||||
this.$data.existingDoc,
|
||||
);
|
||||
if (this.$data.inputTitle) {
|
||||
if (!this.$data.inputTitle?.value) {
|
||||
this.$data.inputTitle.value = file_name;
|
||||
}
|
||||
}
|
||||
},
|
||||
removeDocument: function (object: StoredObject): void {
|
||||
console.log("catch remove document", object);
|
||||
input_stored_object.value = "";
|
||||
this.$data.existingDoc = undefined;
|
||||
console.log("collectionEntry", collectionEntry);
|
||||
const app = createApp({
|
||||
template:
|
||||
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
|
||||
data() {
|
||||
return {
|
||||
existingDoc: existingDoc,
|
||||
inputTitle: inputTitle,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
DropFileWidget,
|
||||
},
|
||||
methods: {
|
||||
addDocument: function ({
|
||||
stored_object,
|
||||
stored_object_version,
|
||||
file_name,
|
||||
}: {
|
||||
stored_object: StoredObject;
|
||||
stored_object_version: StoredObjectVersion;
|
||||
file_name: string;
|
||||
}): void {
|
||||
stored_object.title = file_name;
|
||||
console.log("object added", stored_object);
|
||||
console.log("version added", stored_object_version);
|
||||
this.$data.existingDoc = stored_object;
|
||||
this.$data.existingDoc.currentVersion = stored_object_version;
|
||||
input_stored_object.value = JSON.stringify(this.$data.existingDoc);
|
||||
if (this.$data.inputTitle) {
|
||||
if (!this.$data.inputTitle?.value) {
|
||||
this.$data.inputTitle.value = file_name;
|
||||
}
|
||||
}
|
||||
},
|
||||
removeDocument: function (object: StoredObject): void {
|
||||
console.log("catch remove document", object);
|
||||
input_stored_object.value = "";
|
||||
this.$data.existingDoc = undefined;
|
||||
console.log("collectionEntry", collectionEntry);
|
||||
|
||||
if (null !== collectionEntry) {
|
||||
console.log("will remove collection");
|
||||
collectionEntry.remove();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
if (null !== collectionEntry) {
|
||||
console.log("will remove collection");
|
||||
collectionEntry.remove();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.use(i18n).mount(app_container);
|
||||
app.use(i18n).mount(app_container);
|
||||
};
|
||||
window.addEventListener("collection-add-entry", ((
|
||||
e: CustomEvent<CollectionEventPayload>,
|
||||
e: CustomEvent<CollectionEventPayload>,
|
||||
) => {
|
||||
const detail = e.detail;
|
||||
const divElement: null | HTMLDivElement = detail.entry.querySelector(
|
||||
"div[data-stored-object]",
|
||||
);
|
||||
const detail = e.detail;
|
||||
const divElement: null | HTMLDivElement = detail.entry.querySelector(
|
||||
"div[data-stored-object]",
|
||||
);
|
||||
|
||||
if (null === divElement) {
|
||||
throw new Error("div[data-stored-object] not found");
|
||||
}
|
||||
if (null === divElement) {
|
||||
throw new Error("div[data-stored-object] not found");
|
||||
}
|
||||
|
||||
startApp(divElement, detail.entry);
|
||||
startApp(divElement, detail.entry);
|
||||
}) as EventListener);
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
|
||||
"div[data-stored-object]",
|
||||
);
|
||||
const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
|
||||
"div[data-stored-object]",
|
||||
);
|
||||
|
||||
upload_inputs.forEach((input: HTMLDivElement): void => {
|
||||
// test for a parent to check if this is a collection entry
|
||||
let collectionEntry: null | HTMLLIElement = null;
|
||||
const parent = input.parentElement;
|
||||
console.log("parent", parent);
|
||||
if (null !== parent) {
|
||||
const grandParent = parent.parentElement;
|
||||
console.log("grandParent", grandParent);
|
||||
if (null !== grandParent) {
|
||||
if (
|
||||
grandParent.tagName.toLowerCase() === "li" &&
|
||||
grandParent.classList.contains("entry")
|
||||
) {
|
||||
collectionEntry = grandParent as HTMLLIElement;
|
||||
}
|
||||
}
|
||||
upload_inputs.forEach((input: HTMLDivElement): void => {
|
||||
// test for a parent to check if this is a collection entry
|
||||
let collectionEntry: null | HTMLLIElement = null;
|
||||
const parent = input.parentElement;
|
||||
console.log("parent", parent);
|
||||
if (null !== parent) {
|
||||
const grandParent = parent.parentElement;
|
||||
console.log("grandParent", grandParent);
|
||||
if (null !== grandParent) {
|
||||
if (
|
||||
grandParent.tagName.toLowerCase() === "li" &&
|
||||
grandParent.classList.contains("entry")
|
||||
) {
|
||||
collectionEntry = grandParent as HTMLLIElement;
|
||||
}
|
||||
startApp(input, collectionEntry);
|
||||
});
|
||||
}
|
||||
}
|
||||
startApp(input, collectionEntry);
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
|
||||
@@ -9,26 +9,26 @@ import ToastPlugin from "vue-toast-notification";
|
||||
const i18n = _createI18n({});
|
||||
|
||||
window.addEventListener("DOMContentLoaded", function (e) {
|
||||
document
|
||||
.querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
|
||||
.forEach((el) => {
|
||||
const storedObject = JSON.parse(
|
||||
el.dataset.storedObject as string,
|
||||
) as StoredObject;
|
||||
const title = el.dataset.title as string;
|
||||
const app = createApp({
|
||||
components: { DownloadButton },
|
||||
data() {
|
||||
return {
|
||||
storedObject,
|
||||
title,
|
||||
classes: { btn: true, "btn-outline-primary": true },
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
|
||||
});
|
||||
document
|
||||
.querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
|
||||
.forEach((el) => {
|
||||
const storedObject = JSON.parse(
|
||||
el.dataset.storedObject as string,
|
||||
) as StoredObject;
|
||||
const title = el.dataset.title as string;
|
||||
const app = createApp({
|
||||
components: { DownloadButton },
|
||||
data() {
|
||||
return {
|
||||
storedObject,
|
||||
title,
|
||||
classes: { btn: true, "btn-outline-primary": true },
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
|
||||
});
|
||||
|
||||
app.use(i18n).use(ToastPlugin).mount(el);
|
||||
});
|
||||
app.use(i18n).use(ToastPlugin).mount(el);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,66 +8,66 @@ import ToastPlugin from "vue-toast-notification";
|
||||
const i18n = _createI18n({});
|
||||
|
||||
window.addEventListener("DOMContentLoaded", function (e) {
|
||||
document
|
||||
.querySelectorAll<HTMLDivElement>("div[data-download-buttons]")
|
||||
.forEach((el) => {
|
||||
const app = createApp({
|
||||
components: { DocumentActionButtonsGroup },
|
||||
data() {
|
||||
const datasets = el.dataset as {
|
||||
filename: string;
|
||||
canEdit: string;
|
||||
storedObject: string;
|
||||
buttonSmall: string;
|
||||
davLink: string;
|
||||
davLinkExpiration: string;
|
||||
};
|
||||
document
|
||||
.querySelectorAll<HTMLDivElement>("div[data-download-buttons]")
|
||||
.forEach((el) => {
|
||||
const app = createApp({
|
||||
components: { DocumentActionButtonsGroup },
|
||||
data() {
|
||||
const datasets = el.dataset as {
|
||||
filename: string;
|
||||
canEdit: string;
|
||||
storedObject: string;
|
||||
buttonSmall: string;
|
||||
davLink: string;
|
||||
davLinkExpiration: string;
|
||||
};
|
||||
|
||||
const storedObject = JSON.parse(
|
||||
datasets.storedObject,
|
||||
) as StoredObject,
|
||||
filename = datasets.filename,
|
||||
canEdit = datasets.canEdit === "1",
|
||||
small = datasets.buttonSmall === "1",
|
||||
davLink =
|
||||
"davLink" in datasets && datasets.davLink !== ""
|
||||
? datasets.davLink
|
||||
: null,
|
||||
davLinkExpiration =
|
||||
"davLinkExpiration" in datasets
|
||||
? Number.parseInt(datasets.davLinkExpiration)
|
||||
: null;
|
||||
return {
|
||||
storedObject,
|
||||
filename,
|
||||
canEdit,
|
||||
small,
|
||||
davLink,
|
||||
davLinkExpiration,
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small" :dav-link="davLink" :dav-link-expiration="davLinkExpiration" @on-stored-object-status-change="onStoredObjectStatusChange"></document-action-buttons-group>',
|
||||
methods: {
|
||||
onStoredObjectStatusChange: function (
|
||||
newStatus: StoredObjectStatusChange,
|
||||
): void {
|
||||
this.$data.storedObject.status = newStatus.status;
|
||||
this.$data.storedObject.filename = newStatus.filename;
|
||||
this.$data.storedObject.type = newStatus.type;
|
||||
const storedObject = JSON.parse(
|
||||
datasets.storedObject,
|
||||
) as StoredObject,
|
||||
filename = datasets.filename,
|
||||
canEdit = datasets.canEdit === "1",
|
||||
small = datasets.buttonSmall === "1",
|
||||
davLink =
|
||||
"davLink" in datasets && datasets.davLink !== ""
|
||||
? datasets.davLink
|
||||
: null,
|
||||
davLinkExpiration =
|
||||
"davLinkExpiration" in datasets
|
||||
? Number.parseInt(datasets.davLinkExpiration)
|
||||
: null;
|
||||
return {
|
||||
storedObject,
|
||||
filename,
|
||||
canEdit,
|
||||
small,
|
||||
davLink,
|
||||
davLinkExpiration,
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small" :dav-link="davLink" :dav-link-expiration="davLinkExpiration" @on-stored-object-status-change="onStoredObjectStatusChange"></document-action-buttons-group>',
|
||||
methods: {
|
||||
onStoredObjectStatusChange: function (
|
||||
newStatus: StoredObjectStatusChange,
|
||||
): void {
|
||||
this.$data.storedObject.status = newStatus.status;
|
||||
this.$data.storedObject.filename = newStatus.filename;
|
||||
this.$data.storedObject.type = newStatus.type;
|
||||
|
||||
// remove eventual div which inform pending status
|
||||
document
|
||||
.querySelectorAll(
|
||||
`[data-docgen-is-pending="${this.$data.storedObject.id}"]`,
|
||||
)
|
||||
.forEach(function (el) {
|
||||
el.remove();
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
// remove eventual div which inform pending status
|
||||
document
|
||||
.querySelectorAll(
|
||||
`[data-docgen-is-pending="${this.$data.storedObject.id}"]`,
|
||||
)
|
||||
.forEach(function (el) {
|
||||
el.remove();
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.use(i18n).use(ToastPlugin).mount(el);
|
||||
});
|
||||
app.use(i18n).use(ToastPlugin).mount(el);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { DateTime } from "ChillMainAssets/types";
|
||||
import { StoredObject } from "ChillDocStoreAssets/types/index";
|
||||
|
||||
export interface GenericDocMetadata {
|
||||
isPresent: boolean;
|
||||
isPresent: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -15,57 +15,57 @@ export interface EmptyMetadata extends GenericDocMetadata {}
|
||||
* Minimal Metadata for a GenericDoc with a normalizer
|
||||
*/
|
||||
export interface BaseMetadata extends GenericDocMetadata {
|
||||
title: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic doc is a document attached to a Person or an AccompanyingPeriod.
|
||||
*/
|
||||
export interface GenericDoc {
|
||||
type: "doc_store_generic_doc";
|
||||
uniqueKey: string;
|
||||
key: string;
|
||||
identifiers: object;
|
||||
context: "person" | "accompanying-period";
|
||||
doc_date: DateTime;
|
||||
metadata: GenericDocMetadata;
|
||||
storedObject: StoredObject | null;
|
||||
type: "doc_store_generic_doc";
|
||||
uniqueKey: string;
|
||||
key: string;
|
||||
identifiers: object;
|
||||
context: "person" | "accompanying-period";
|
||||
doc_date: DateTime;
|
||||
metadata: GenericDocMetadata;
|
||||
storedObject: StoredObject | null;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingPeriod extends GenericDoc {
|
||||
context: "accompanying-period";
|
||||
context: "accompanying-period";
|
||||
}
|
||||
|
||||
interface BaseMetadataWithHtml extends BaseMetadata {
|
||||
html: string;
|
||||
html: string;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCourseDocument
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_course_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_course_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCourseActivityDocument
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_course_activity_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_course_activity_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCourseCalendarDocument
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_course_calendar_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_course_calendar_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCoursePersonDocument
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "person_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "person_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_period_work_evaluation_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_period_work_evaluation_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
}
|
||||
|
||||
@@ -4,73 +4,73 @@ import { SignedUrlGet } from "ChillDocStoreAssets/vuejs/StoredObjectButton/helpe
|
||||
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
|
||||
|
||||
export interface StoredObject {
|
||||
id: number;
|
||||
title: string | null;
|
||||
uuid: string;
|
||||
prefix: string;
|
||||
status: StoredObjectStatus;
|
||||
currentVersion:
|
||||
| null
|
||||
| StoredObjectVersionCreated
|
||||
| StoredObjectVersionPersisted;
|
||||
totalVersions: number;
|
||||
datas: object;
|
||||
/** @deprecated */
|
||||
creationDate: DateTime;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
_permissions: {
|
||||
canEdit: boolean;
|
||||
canSee: boolean;
|
||||
};
|
||||
_links?: {
|
||||
dav_link?: {
|
||||
href: string;
|
||||
expiration: number;
|
||||
};
|
||||
downloadLink?: SignedUrlGet;
|
||||
id: number;
|
||||
title: string | null;
|
||||
uuid: string;
|
||||
prefix: string;
|
||||
status: StoredObjectStatus;
|
||||
currentVersion:
|
||||
| null
|
||||
| StoredObjectVersionCreated
|
||||
| StoredObjectVersionPersisted;
|
||||
totalVersions: number;
|
||||
datas: object;
|
||||
/** @deprecated */
|
||||
creationDate: DateTime;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
_permissions: {
|
||||
canEdit: boolean;
|
||||
canSee: boolean;
|
||||
};
|
||||
_links?: {
|
||||
dav_link?: {
|
||||
href: string;
|
||||
expiration: number;
|
||||
};
|
||||
downloadLink?: SignedUrlGet;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StoredObjectVersion {
|
||||
/**
|
||||
* filename of the object in the object storage
|
||||
*/
|
||||
filename: string;
|
||||
iv: number[];
|
||||
keyInfos: JsonWebKey;
|
||||
type: string;
|
||||
/**
|
||||
* filename of the object in the object storage
|
||||
*/
|
||||
filename: string;
|
||||
iv: number[];
|
||||
keyInfos: JsonWebKey;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface StoredObjectVersionCreated extends StoredObjectVersion {
|
||||
persisted: false;
|
||||
persisted: false;
|
||||
}
|
||||
|
||||
export interface StoredObjectVersionPersisted
|
||||
extends StoredObjectVersionCreated {
|
||||
version: number;
|
||||
id: number;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
extends StoredObjectVersionCreated {
|
||||
version: number;
|
||||
id: number;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
}
|
||||
|
||||
export interface StoredObjectStatusChange {
|
||||
id: number;
|
||||
filename: string;
|
||||
status: StoredObjectStatus;
|
||||
type: string;
|
||||
id: number;
|
||||
filename: string;
|
||||
status: StoredObjectStatus;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface StoredObjectVersionWithPointInTime
|
||||
extends StoredObjectVersionPersisted {
|
||||
"point-in-times": StoredObjectPointInTime[];
|
||||
"from-restored": StoredObjectVersionPersisted | null;
|
||||
extends StoredObjectVersionPersisted {
|
||||
"point-in-times": StoredObjectPointInTime[];
|
||||
"from-restored": StoredObjectVersionPersisted | null;
|
||||
}
|
||||
|
||||
export interface StoredObjectPointInTime {
|
||||
id: number;
|
||||
byUser: User | null;
|
||||
reason: "keep-before-conversion" | "keep-by-user";
|
||||
id: number;
|
||||
byUser: User | null;
|
||||
reason: "keep-before-conversion" | "keep-by-user";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,63 +82,63 @@ export type WopiEditButtonExecutableBeforeLeaveFunction = () => Promise<void>;
|
||||
* Object containing information for performering a POST request to a swift object store
|
||||
*/
|
||||
export interface PostStoreObjectSignature {
|
||||
method: "POST";
|
||||
max_file_size: number;
|
||||
max_file_count: 1;
|
||||
expires: number;
|
||||
submit_delay: 180;
|
||||
redirect: string;
|
||||
prefix: string;
|
||||
url: string;
|
||||
signature: string;
|
||||
method: "POST";
|
||||
max_file_size: number;
|
||||
max_file_count: 1;
|
||||
expires: number;
|
||||
submit_delay: 180;
|
||||
redirect: string;
|
||||
prefix: string;
|
||||
url: string;
|
||||
signature: string;
|
||||
}
|
||||
|
||||
export interface PDFPage {
|
||||
index: number;
|
||||
width: number;
|
||||
height: number;
|
||||
index: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
export interface SignatureZone {
|
||||
index: number | null;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
PDFPage: PDFPage;
|
||||
index: number | null;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
PDFPage: PDFPage;
|
||||
}
|
||||
|
||||
export interface Signature {
|
||||
id: number;
|
||||
storedObject: StoredObject;
|
||||
zones: SignatureZone[];
|
||||
id: number;
|
||||
storedObject: StoredObject;
|
||||
zones: SignatureZone[];
|
||||
}
|
||||
|
||||
export type SignedState =
|
||||
| "pending"
|
||||
| "signed"
|
||||
| "rejected"
|
||||
| "canceled"
|
||||
| "error";
|
||||
| "pending"
|
||||
| "signed"
|
||||
| "rejected"
|
||||
| "canceled"
|
||||
| "error";
|
||||
|
||||
export interface CheckSignature {
|
||||
state: SignedState;
|
||||
storedObject: StoredObject;
|
||||
state: SignedState;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
export type CanvasEvent = "select" | "add";
|
||||
|
||||
export interface ZoomLevel {
|
||||
id: number;
|
||||
zoom: number;
|
||||
label: {
|
||||
fr?: string;
|
||||
nl?: string;
|
||||
};
|
||||
id: number;
|
||||
zoom: number;
|
||||
label: {
|
||||
fr?: string;
|
||||
nl?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GenericDoc {
|
||||
type: "doc_store_generic_doc";
|
||||
key: string;
|
||||
context: "person" | "accompanying-period";
|
||||
doc_date: DateTime;
|
||||
type: "doc_store_generic_doc";
|
||||
key: string;
|
||||
context: "person" | "accompanying-period";
|
||||
doc_date: DateTime;
|
||||
}
|
||||
|
||||
@@ -1,67 +1,65 @@
|
||||
<template>
|
||||
<div v-if="isButtonGroupDisplayable" class="btn-group">
|
||||
<button
|
||||
:class="
|
||||
Object.assign({
|
||||
btn: true,
|
||||
'btn-outline-primary': true,
|
||||
'dropdown-toggle': true,
|
||||
'btn-sm': props.small,
|
||||
})
|
||||
"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
Actions
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li v-if="isEditableOnline">
|
||||
<wopi-edit-button
|
||||
:stored-object="props.storedObject"
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:execute-before-leave="props.executeBeforeLeave"
|
||||
></wopi-edit-button>
|
||||
</li>
|
||||
<li v-if="isEditableOnDesktop">
|
||||
<desktop-edit-button
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:edit-link="props.davLink"
|
||||
:expiration-link="props.davLinkExpiration"
|
||||
></desktop-edit-button>
|
||||
</li>
|
||||
<li v-if="isConvertibleToPdf">
|
||||
<convert-button
|
||||
:stored-object="props.storedObject"
|
||||
:filename="filename"
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
></convert-button>
|
||||
</li>
|
||||
<li v-if="isDownloadable">
|
||||
<download-button
|
||||
:stored-object="props.storedObject"
|
||||
:at-version="props.storedObject.currentVersion"
|
||||
:filename="filename"
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:display-action-string-in-button="true"
|
||||
></download-button>
|
||||
</li>
|
||||
<li v-if="isHistoryViewable">
|
||||
<history-button
|
||||
:stored-object="props.storedObject"
|
||||
:can-edit="
|
||||
canEdit && props.storedObject._permissions.canEdit
|
||||
"
|
||||
></history-button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else-if="'pending' === props.storedObject.status">
|
||||
<div class="btn btn-outline-info">Génération en cours</div>
|
||||
</div>
|
||||
<div v-else-if="'failure' === props.storedObject.status">
|
||||
<div class="btn btn-outline-danger">La génération a échoué</div>
|
||||
</div>
|
||||
<div v-if="isButtonGroupDisplayable" class="btn-group">
|
||||
<button
|
||||
:class="
|
||||
Object.assign({
|
||||
btn: true,
|
||||
'btn-outline-primary': true,
|
||||
'dropdown-toggle': true,
|
||||
'btn-sm': props.small,
|
||||
})
|
||||
"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
Actions
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li v-if="isEditableOnline">
|
||||
<wopi-edit-button
|
||||
:stored-object="props.storedObject"
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:execute-before-leave="props.executeBeforeLeave"
|
||||
></wopi-edit-button>
|
||||
</li>
|
||||
<li v-if="isEditableOnDesktop">
|
||||
<desktop-edit-button
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:edit-link="props.davLink"
|
||||
:expiration-link="props.davLinkExpiration"
|
||||
></desktop-edit-button>
|
||||
</li>
|
||||
<li v-if="isConvertibleToPdf">
|
||||
<convert-button
|
||||
:stored-object="props.storedObject"
|
||||
:filename="filename"
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
></convert-button>
|
||||
</li>
|
||||
<li v-if="isDownloadable">
|
||||
<download-button
|
||||
:stored-object="props.storedObject"
|
||||
:at-version="props.storedObject.currentVersion"
|
||||
:filename="filename"
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:display-action-string-in-button="true"
|
||||
></download-button>
|
||||
</li>
|
||||
<li v-if="isHistoryViewable">
|
||||
<history-button
|
||||
:stored-object="props.storedObject"
|
||||
:can-edit="canEdit && props.storedObject._permissions.canEdit"
|
||||
></history-button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else-if="'pending' === props.storedObject.status">
|
||||
<div class="btn btn-outline-info">Génération en cours</div>
|
||||
</div>
|
||||
<div v-else-if="'failure' === props.storedObject.status">
|
||||
<div class="btn btn-outline-danger">La génération a échoué</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -70,68 +68,66 @@ import ConvertButton from "./StoredObjectButton/ConvertButton.vue";
|
||||
import DownloadButton from "./StoredObjectButton/DownloadButton.vue";
|
||||
import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue";
|
||||
import {
|
||||
is_extension_editable,
|
||||
is_extension_viewable,
|
||||
is_object_ready,
|
||||
is_extension_editable,
|
||||
is_extension_viewable,
|
||||
is_object_ready,
|
||||
} from "./StoredObjectButton/helpers";
|
||||
import {
|
||||
StoredObject,
|
||||
StoredObjectStatusChange,
|
||||
StoredObjectVersion,
|
||||
WopiEditButtonExecutableBeforeLeaveFunction,
|
||||
StoredObject,
|
||||
StoredObjectStatusChange,
|
||||
StoredObjectVersion,
|
||||
WopiEditButtonExecutableBeforeLeaveFunction,
|
||||
} from "../types";
|
||||
import DesktopEditButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DesktopEditButton.vue";
|
||||
import HistoryButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton.vue";
|
||||
|
||||
interface DocumentActionButtonsGroupConfig {
|
||||
storedObject: StoredObject;
|
||||
small?: boolean;
|
||||
canEdit?: boolean;
|
||||
canDownload?: boolean;
|
||||
canConvertPdf?: boolean;
|
||||
returnPath?: string;
|
||||
storedObject: StoredObject;
|
||||
small?: boolean;
|
||||
canEdit?: boolean;
|
||||
canDownload?: boolean;
|
||||
canConvertPdf?: boolean;
|
||||
returnPath?: string;
|
||||
|
||||
/**
|
||||
* Will be the filename displayed to the user when he·she download the document
|
||||
* (the document will be saved on his disk with this name)
|
||||
*
|
||||
* If not set, 'document' will be used.
|
||||
*/
|
||||
filename?: string;
|
||||
/**
|
||||
* Will be the filename displayed to the user when he·she download the document
|
||||
* (the document will be saved on his disk with this name)
|
||||
*
|
||||
* If not set, 'document' will be used.
|
||||
*/
|
||||
filename?: string;
|
||||
|
||||
/**
|
||||
* If set, will execute this function before leaving to the editor
|
||||
*/
|
||||
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
|
||||
/**
|
||||
* If set, will execute this function before leaving to the editor
|
||||
*/
|
||||
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
|
||||
|
||||
/**
|
||||
* a link to download and edit file using webdav
|
||||
*/
|
||||
davLink?: string;
|
||||
/**
|
||||
* a link to download and edit file using webdav
|
||||
*/
|
||||
davLink?: string;
|
||||
|
||||
/**
|
||||
* the expiration date of the download, as a unix timestamp
|
||||
*/
|
||||
davLinkExpiration?: number;
|
||||
/**
|
||||
* the expiration date of the download, as a unix timestamp
|
||||
*/
|
||||
davLinkExpiration?: number;
|
||||
}
|
||||
|
||||
const emit =
|
||||
defineEmits<
|
||||
(
|
||||
e: "onStoredObjectStatusChange",
|
||||
newStatus: StoredObjectStatusChange,
|
||||
) => void
|
||||
>();
|
||||
defineEmits<
|
||||
(
|
||||
e: "onStoredObjectStatusChange",
|
||||
newStatus: StoredObjectStatusChange,
|
||||
) => void
|
||||
>();
|
||||
|
||||
const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
|
||||
small: false,
|
||||
canEdit: true,
|
||||
canDownload: true,
|
||||
canConvertPdf: true,
|
||||
returnPath:
|
||||
window.location.pathname +
|
||||
window.location.search +
|
||||
window.location.hash,
|
||||
small: false,
|
||||
canEdit: true,
|
||||
canDownload: true,
|
||||
canConvertPdf: true,
|
||||
returnPath:
|
||||
window.location.pathname + window.location.search + window.location.hash,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -145,93 +141,93 @@ let tryiesForReady = 0;
|
||||
const maxTryiesForReady = 120;
|
||||
|
||||
const isButtonGroupDisplayable = computed<boolean>(() => {
|
||||
return (
|
||||
isDownloadable.value ||
|
||||
isEditableOnline.value ||
|
||||
isEditableOnDesktop.value ||
|
||||
isConvertibleToPdf.value
|
||||
);
|
||||
return (
|
||||
isDownloadable.value ||
|
||||
isEditableOnline.value ||
|
||||
isEditableOnDesktop.value ||
|
||||
isConvertibleToPdf.value
|
||||
);
|
||||
});
|
||||
|
||||
const isDownloadable = computed<boolean>(() => {
|
||||
return (
|
||||
props.storedObject.status === "ready" ||
|
||||
// happens when the stored object version is just added, but not persisted
|
||||
(props.storedObject.currentVersion !== null &&
|
||||
props.storedObject.status === "empty")
|
||||
);
|
||||
return (
|
||||
props.storedObject.status === "ready" ||
|
||||
// happens when the stored object version is just added, but not persisted
|
||||
(props.storedObject.currentVersion !== null &&
|
||||
props.storedObject.status === "empty")
|
||||
);
|
||||
});
|
||||
|
||||
const isEditableOnline = computed<boolean>(() => {
|
||||
return (
|
||||
props.storedObject.status === "ready" &&
|
||||
props.storedObject._permissions.canEdit &&
|
||||
props.canEdit &&
|
||||
props.storedObject.currentVersion !== null &&
|
||||
is_extension_editable(props.storedObject.currentVersion.type) &&
|
||||
props.storedObject.currentVersion.persisted !== false
|
||||
);
|
||||
return (
|
||||
props.storedObject.status === "ready" &&
|
||||
props.storedObject._permissions.canEdit &&
|
||||
props.canEdit &&
|
||||
props.storedObject.currentVersion !== null &&
|
||||
is_extension_editable(props.storedObject.currentVersion.type) &&
|
||||
props.storedObject.currentVersion.persisted !== false
|
||||
);
|
||||
});
|
||||
|
||||
const isEditableOnDesktop = computed<boolean>(() => {
|
||||
return isEditableOnline.value;
|
||||
return isEditableOnline.value;
|
||||
});
|
||||
|
||||
const isConvertibleToPdf = computed<boolean>(() => {
|
||||
return (
|
||||
props.storedObject.status === "ready" &&
|
||||
props.storedObject._permissions.canSee &&
|
||||
props.canConvertPdf &&
|
||||
props.storedObject.currentVersion !== null &&
|
||||
is_extension_viewable(props.storedObject.currentVersion.type) &&
|
||||
props.storedObject.currentVersion.type !== "application/pdf" &&
|
||||
props.storedObject.currentVersion.persisted !== false
|
||||
);
|
||||
return (
|
||||
props.storedObject.status === "ready" &&
|
||||
props.storedObject._permissions.canSee &&
|
||||
props.canConvertPdf &&
|
||||
props.storedObject.currentVersion !== null &&
|
||||
is_extension_viewable(props.storedObject.currentVersion.type) &&
|
||||
props.storedObject.currentVersion.type !== "application/pdf" &&
|
||||
props.storedObject.currentVersion.persisted !== false
|
||||
);
|
||||
});
|
||||
|
||||
const isHistoryViewable = computed<boolean>(() => {
|
||||
return props.storedObject.status === "ready";
|
||||
return props.storedObject.status === "ready";
|
||||
});
|
||||
|
||||
const checkForReady = function (): void {
|
||||
if (
|
||||
"ready" === props.storedObject.status ||
|
||||
"empty" === props.storedObject.status ||
|
||||
"failure" === props.storedObject.status ||
|
||||
// stop reloading if the page stays opened for a long time
|
||||
tryiesForReady > maxTryiesForReady
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
"ready" === props.storedObject.status ||
|
||||
"empty" === props.storedObject.status ||
|
||||
"failure" === props.storedObject.status ||
|
||||
// stop reloading if the page stays opened for a long time
|
||||
tryiesForReady > maxTryiesForReady
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
tryiesForReady = tryiesForReady + 1;
|
||||
tryiesForReady = tryiesForReady + 1;
|
||||
|
||||
setTimeout(onObjectNewStatusCallback, 5000);
|
||||
setTimeout(onObjectNewStatusCallback, 5000);
|
||||
};
|
||||
|
||||
const onObjectNewStatusCallback = async function (): Promise<void> {
|
||||
if (props.storedObject.status === "stored_object_created") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const new_status = await is_object_ready(props.storedObject);
|
||||
if (props.storedObject.status !== new_status.status) {
|
||||
emit("onStoredObjectStatusChange", new_status);
|
||||
return Promise.resolve();
|
||||
} else if ("failure" === new_status.status) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if ("ready" !== new_status.status) {
|
||||
// we check for new status, unless it is ready
|
||||
checkForReady();
|
||||
}
|
||||
|
||||
if (props.storedObject.status === "stored_object_created") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const new_status = await is_object_ready(props.storedObject);
|
||||
if (props.storedObject.status !== new_status.status) {
|
||||
emit("onStoredObjectStatusChange", new_status);
|
||||
return Promise.resolve();
|
||||
} else if ("failure" === new_status.status) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if ("ready" !== new_status.status) {
|
||||
// we check for new status, unless it is ready
|
||||
checkForReady();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkForReady();
|
||||
checkForReady();
|
||||
});
|
||||
</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";
|
||||
|
||||
const appMessages = {
|
||||
fr: {
|
||||
yes: "Oui",
|
||||
are_you_sure: "Êtes-vous sûr·e?",
|
||||
you_are_going_to_sign: "Vous allez signer le document",
|
||||
signature_confirmation: "Confirmation de la signature",
|
||||
sign: "Signer",
|
||||
choose_another_signature: "Choisir une autre zone",
|
||||
cancel: "Annuler",
|
||||
last_sign_zone: "Zone de signature précédente",
|
||||
next_sign_zone: "Zone de signature suivante",
|
||||
add_sign_zone: "Ajouter une zone de signature",
|
||||
click_on_document: "Cliquer sur le document",
|
||||
last_zone: "Zone précédente",
|
||||
next_zone: "Zone suivante",
|
||||
add_zone: "Ajouter une zone",
|
||||
another_zone: "Autre zone",
|
||||
electronic_signature_in_progress: "Signature électronique en cours...",
|
||||
loading: "Chargement...",
|
||||
remove_sign_zone: "Enlever la zone",
|
||||
return: "Retour",
|
||||
see_all_pages: "Voir toutes les pages",
|
||||
all_pages: "Toutes les pages",
|
||||
},
|
||||
fr: {
|
||||
yes: "Oui",
|
||||
are_you_sure: "Êtes-vous sûr·e?",
|
||||
you_are_going_to_sign: "Vous allez signer le document",
|
||||
signature_confirmation: "Confirmation de la signature",
|
||||
sign: "Signer",
|
||||
choose_another_signature: "Choisir une autre zone",
|
||||
cancel: "Annuler",
|
||||
last_sign_zone: "Zone de signature précédente",
|
||||
next_sign_zone: "Zone de signature suivante",
|
||||
add_sign_zone: "Ajouter une zone de signature",
|
||||
click_on_document: "Cliquer sur le document",
|
||||
last_zone: "Zone précédente",
|
||||
next_zone: "Zone suivante",
|
||||
add_zone: "Ajouter une zone",
|
||||
another_zone: "Autre zone",
|
||||
electronic_signature_in_progress: "Signature électronique en cours...",
|
||||
loading: "Chargement...",
|
||||
remove_sign_zone: "Enlever la zone",
|
||||
return: "Retour",
|
||||
see_all_pages: "Voir toutes les pages",
|
||||
all_pages: "Toutes les pages",
|
||||
},
|
||||
};
|
||||
|
||||
const i18n = _createI18n(appMessages);
|
||||
|
||||
const app = createApp({
|
||||
template: `<app></app>`,
|
||||
template: `<app></app>`,
|
||||
})
|
||||
.use(i18n)
|
||||
.component("app", App)
|
||||
.mount("#document-signature");
|
||||
.use(i18n)
|
||||
.component("app", App)
|
||||
.mount("#document-signature");
|
||||
|
||||
@@ -1,208 +1,206 @@
|
||||
<script setup lang="ts">
|
||||
import { StoredObject, StoredObjectVersionCreated } from "../../types";
|
||||
import {
|
||||
encryptFile,
|
||||
fetchNewStoredObject,
|
||||
uploadVersion,
|
||||
encryptFile,
|
||||
fetchNewStoredObject,
|
||||
uploadVersion,
|
||||
} from "../../js/async-upload/uploader";
|
||||
import { computed, ref, Ref } from "vue";
|
||||
import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
|
||||
|
||||
interface DropFileConfig {
|
||||
existingDoc?: StoredObject;
|
||||
existingDoc?: StoredObject;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<DropFileConfig>(), {
|
||||
existingDoc: null,
|
||||
existingDoc: null,
|
||||
});
|
||||
|
||||
const emit =
|
||||
defineEmits<
|
||||
(
|
||||
e: "addDocument",
|
||||
{
|
||||
stored_object_version: StoredObjectVersionCreated,
|
||||
stored_object: StoredObject,
|
||||
file_name: string,
|
||||
},
|
||||
) => void
|
||||
>();
|
||||
defineEmits<
|
||||
(
|
||||
e: "addDocument",
|
||||
{
|
||||
stored_object_version: StoredObjectVersionCreated,
|
||||
stored_object: StoredObject,
|
||||
file_name: string,
|
||||
},
|
||||
) => void
|
||||
>();
|
||||
|
||||
const is_dragging: Ref<boolean> = ref(false);
|
||||
const uploading: Ref<boolean> = ref(false);
|
||||
const display_filename: Ref<string | null> = ref(null);
|
||||
|
||||
const has_existing_doc = computed<boolean>(() => {
|
||||
return props.existingDoc !== undefined && props.existingDoc !== null;
|
||||
return props.existingDoc !== undefined && props.existingDoc !== null;
|
||||
});
|
||||
|
||||
const onDragOver = (e: Event) => {
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
is_dragging.value = true;
|
||||
is_dragging.value = true;
|
||||
};
|
||||
|
||||
const onDragLeave = (e: Event) => {
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
is_dragging.value = false;
|
||||
is_dragging.value = false;
|
||||
};
|
||||
|
||||
const onDrop = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
const files = e.dataTransfer?.files;
|
||||
const files = e.dataTransfer?.files;
|
||||
|
||||
if (null === files || undefined === files) {
|
||||
console.error("no files transferred", e.dataTransfer);
|
||||
return;
|
||||
}
|
||||
if (files.length === 0) {
|
||||
console.error("no files given");
|
||||
return;
|
||||
}
|
||||
if (null === files || undefined === files) {
|
||||
console.error("no files transferred", e.dataTransfer);
|
||||
return;
|
||||
}
|
||||
if (files.length === 0) {
|
||||
console.error("no files given");
|
||||
return;
|
||||
}
|
||||
|
||||
handleFile(files[0]);
|
||||
handleFile(files[0]);
|
||||
};
|
||||
|
||||
const onZoneClick = (e: Event) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.addEventListener("change", onFileChange);
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.addEventListener("change", onFileChange);
|
||||
|
||||
input.click();
|
||||
input.click();
|
||||
};
|
||||
|
||||
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]) {
|
||||
console.log("file added", input.files[0]);
|
||||
const file = input.files[0];
|
||||
await handleFile(file);
|
||||
if (input.files && input.files[0]) {
|
||||
console.log("file added", input.files[0]);
|
||||
const file = input.files[0];
|
||||
await handleFile(file);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
throw "No file given";
|
||||
throw "No file given";
|
||||
};
|
||||
|
||||
const handleFile = async (file: File): Promise<void> => {
|
||||
uploading.value = true;
|
||||
display_filename.value = file.name;
|
||||
const type = file.type;
|
||||
uploading.value = true;
|
||||
display_filename.value = file.name;
|
||||
const type = file.type;
|
||||
|
||||
// create a stored_object if not exists
|
||||
let stored_object;
|
||||
if (null === props.existingDoc) {
|
||||
stored_object = await fetchNewStoredObject();
|
||||
} else {
|
||||
stored_object = props.existingDoc;
|
||||
}
|
||||
// create a stored_object if not exists
|
||||
let stored_object;
|
||||
if (null === props.existingDoc) {
|
||||
stored_object = await fetchNewStoredObject();
|
||||
} else {
|
||||
stored_object = props.existingDoc;
|
||||
}
|
||||
|
||||
const buffer = await file.arrayBuffer();
|
||||
const [encrypted, iv, jsonWebKey] = await encryptFile(buffer);
|
||||
const filename = await uploadVersion(encrypted, stored_object);
|
||||
const buffer = await file.arrayBuffer();
|
||||
const [encrypted, iv, jsonWebKey] = await encryptFile(buffer);
|
||||
const filename = await uploadVersion(encrypted, stored_object);
|
||||
|
||||
const stored_object_version: StoredObjectVersionCreated = {
|
||||
filename: filename,
|
||||
iv: Array.from(iv),
|
||||
keyInfos: jsonWebKey,
|
||||
type: type,
|
||||
persisted: false,
|
||||
};
|
||||
const stored_object_version: StoredObjectVersionCreated = {
|
||||
filename: filename,
|
||||
iv: Array.from(iv),
|
||||
keyInfos: jsonWebKey,
|
||||
type: type,
|
||||
persisted: false,
|
||||
};
|
||||
|
||||
const fileName = file.name;
|
||||
let file_name = "Nouveau document";
|
||||
const file_name_split = fileName.split(".");
|
||||
if (file_name_split.length > 1) {
|
||||
const extension = file_name_split
|
||||
? file_name_split[file_name_split.length - 1]
|
||||
: "";
|
||||
file_name = fileName.replace(extension, "").slice(0, -1);
|
||||
}
|
||||
const fileName = file.name;
|
||||
let file_name = "Nouveau document";
|
||||
const file_name_split = fileName.split(".");
|
||||
if (file_name_split.length > 1) {
|
||||
const extension = file_name_split
|
||||
? file_name_split[file_name_split.length - 1]
|
||||
: "";
|
||||
file_name = fileName.replace(extension, "").slice(0, -1);
|
||||
}
|
||||
|
||||
emit("addDocument", {
|
||||
stored_object,
|
||||
stored_object_version,
|
||||
file_name: file_name,
|
||||
});
|
||||
uploading.value = false;
|
||||
emit("addDocument", {
|
||||
stored_object,
|
||||
stored_object_version,
|
||||
file_name: file_name,
|
||||
});
|
||||
uploading.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="drop-file">
|
||||
<div
|
||||
v-if="!uploading"
|
||||
:class="{ area: true, dragging: is_dragging }"
|
||||
@click="onZoneClick"
|
||||
@dragover="onDragOver"
|
||||
@dragleave="onDragLeave"
|
||||
@drop="onDrop"
|
||||
>
|
||||
<p v-if="has_existing_doc" class="file-icon">
|
||||
<file-icon :type="props.existingDoc?.type"></file-icon>
|
||||
</p>
|
||||
<div class="drop-file">
|
||||
<div
|
||||
v-if="!uploading"
|
||||
:class="{ area: true, dragging: is_dragging }"
|
||||
@click="onZoneClick"
|
||||
@dragover="onDragOver"
|
||||
@dragleave="onDragLeave"
|
||||
@drop="onDrop"
|
||||
>
|
||||
<p v-if="has_existing_doc" class="file-icon">
|
||||
<file-icon :type="props.existingDoc?.type"></file-icon>
|
||||
</p>
|
||||
|
||||
<p v-if="display_filename !== null" class="display-filename">
|
||||
{{ display_filename }}
|
||||
</p>
|
||||
<!-- todo i18n -->
|
||||
<p v-if="has_existing_doc">
|
||||
Déposez un document ou cliquez ici pour remplacer le document
|
||||
existant
|
||||
</p>
|
||||
<p v-else>
|
||||
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>
|
||||
<p v-if="display_filename !== null" class="display-filename">
|
||||
{{ display_filename }}
|
||||
</p>
|
||||
<!-- todo i18n -->
|
||||
<p v-if="has_existing_doc">
|
||||
Déposez un document ou cliquez ici pour remplacer le document existant
|
||||
</p>
|
||||
<p v-else>
|
||||
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>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.drop-file {
|
||||
width: 100%;
|
||||
|
||||
.file-icon {
|
||||
font-size: xx-large;
|
||||
}
|
||||
|
||||
.display-filename {
|
||||
font-variant: small-caps;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
& > .area,
|
||||
& > .waiting {
|
||||
width: 100%;
|
||||
height: 10rem;
|
||||
|
||||
.file-icon {
|
||||
font-size: xx-large;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
// require for display in DropFileModal
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.display-filename {
|
||||
font-variant: small-caps;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
& > .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;
|
||||
}
|
||||
& > .area {
|
||||
border: 4px dashed #ccc;
|
||||
|
||||
&.dragging {
|
||||
border: 4px dashed blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,24 +6,24 @@ import { computed, reactive } from "vue";
|
||||
import { useToast } from "vue-toast-notification";
|
||||
|
||||
interface DropFileConfig {
|
||||
allowRemove: boolean;
|
||||
existingDoc?: StoredObject;
|
||||
allowRemove: boolean;
|
||||
existingDoc?: StoredObject;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<DropFileConfig>(), {
|
||||
allowRemove: false,
|
||||
allowRemove: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: "addDocument",
|
||||
{
|
||||
stored_object: StoredObject,
|
||||
stored_object_version: StoredObjectVersion,
|
||||
file_name: string,
|
||||
},
|
||||
): void;
|
||||
(e: "removeDocument"): void;
|
||||
(
|
||||
e: "addDocument",
|
||||
{
|
||||
stored_object: StoredObject,
|
||||
stored_object_version: StoredObjectVersion,
|
||||
file_name: string,
|
||||
},
|
||||
): void;
|
||||
(e: "removeDocument"): void;
|
||||
}>();
|
||||
|
||||
const $toast = useToast();
|
||||
@@ -33,67 +33,67 @@ const state = reactive({ showModal: false });
|
||||
const modalClasses = { "modal-dialog-centered": true, "modal-md": true };
|
||||
|
||||
const buttonState = computed<"add" | "replace">(() => {
|
||||
if (props.existingDoc === undefined || props.existingDoc === null) {
|
||||
return "add";
|
||||
}
|
||||
if (props.existingDoc === undefined || props.existingDoc === null) {
|
||||
return "add";
|
||||
}
|
||||
|
||||
return "replace";
|
||||
return "replace";
|
||||
});
|
||||
|
||||
function onAddDocument({
|
||||
stored_object,
|
||||
stored_object_version,
|
||||
file_name,
|
||||
stored_object,
|
||||
stored_object_version,
|
||||
file_name,
|
||||
}: {
|
||||
stored_object: StoredObject;
|
||||
stored_object_version: StoredObjectVersion;
|
||||
file_name: string;
|
||||
stored_object: StoredObject;
|
||||
stored_object_version: StoredObjectVersion;
|
||||
file_name: string;
|
||||
}): void {
|
||||
const message =
|
||||
buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
|
||||
$toast.success(message);
|
||||
emit("addDocument", { stored_object_version, stored_object, file_name });
|
||||
state.showModal = false;
|
||||
const message =
|
||||
buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
|
||||
$toast.success(message);
|
||||
emit("addDocument", { stored_object_version, stored_object, file_name });
|
||||
state.showModal = false;
|
||||
}
|
||||
|
||||
function onRemoveDocument(): void {
|
||||
emit("removeDocument");
|
||||
emit("removeDocument");
|
||||
}
|
||||
|
||||
function openModal(): void {
|
||||
state.showModal = true;
|
||||
state.showModal = true;
|
||||
}
|
||||
|
||||
function closeModal(): void {
|
||||
state.showModal = false;
|
||||
state.showModal = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
v-if="buttonState === 'add'"
|
||||
@click="openModal"
|
||||
class="btn btn-create"
|
||||
>
|
||||
Ajouter un document
|
||||
</button>
|
||||
<button v-else @click="openModal" class="btn btn-edit">
|
||||
Remplacer le document
|
||||
</button>
|
||||
<modal
|
||||
v-if="state.showModal"
|
||||
:modal-dialog-class="modalClasses"
|
||||
@close="closeModal"
|
||||
>
|
||||
<template v-slot:body>
|
||||
<drop-file-widget
|
||||
:existing-doc="existingDoc"
|
||||
:allow-remove="allowRemove"
|
||||
@add-document="onAddDocument"
|
||||
@remove-document="onRemoveDocument"
|
||||
></drop-file-widget>
|
||||
</template>
|
||||
</modal>
|
||||
<button
|
||||
v-if="buttonState === 'add'"
|
||||
@click="openModal"
|
||||
class="btn btn-create"
|
||||
>
|
||||
Ajouter un document
|
||||
</button>
|
||||
<button v-else @click="openModal" class="btn btn-edit">
|
||||
Remplacer le document
|
||||
</button>
|
||||
<modal
|
||||
v-if="state.showModal"
|
||||
:modal-dialog-class="modalClasses"
|
||||
@close="closeModal"
|
||||
>
|
||||
<template v-slot:body>
|
||||
<drop-file-widget
|
||||
:existing-doc="existingDoc"
|
||||
:allow-remove="allowRemove"
|
||||
@add-document="onAddDocument"
|
||||
@remove-document="onRemoveDocument"
|
||||
></drop-file-widget>
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -5,97 +5,97 @@ import DropFile from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFile.vue";
|
||||
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
|
||||
|
||||
interface DropFileConfig {
|
||||
allowRemove: boolean;
|
||||
existingDoc?: StoredObject;
|
||||
allowRemove: boolean;
|
||||
existingDoc?: StoredObject;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<DropFileConfig>(), {
|
||||
allowRemove: false,
|
||||
allowRemove: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: "addDocument",
|
||||
{
|
||||
stored_object: StoredObject,
|
||||
stored_object_version: StoredObjectVersion,
|
||||
file_name: string,
|
||||
},
|
||||
): void;
|
||||
(e: "removeDocument"): void;
|
||||
(
|
||||
e: "addDocument",
|
||||
{
|
||||
stored_object: StoredObject,
|
||||
stored_object_version: StoredObjectVersion,
|
||||
file_name: string,
|
||||
},
|
||||
): void;
|
||||
(e: "removeDocument"): void;
|
||||
}>();
|
||||
|
||||
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>(() => {
|
||||
if (props.existingDoc === undefined || props.existingDoc === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (props.existingDoc.status !== "ready") {
|
||||
return undefined;
|
||||
}
|
||||
if (props.existingDoc === undefined || props.existingDoc === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (props.existingDoc.status !== "ready") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return props.existingDoc._links?.dav_link?.expiration;
|
||||
return props.existingDoc._links?.dav_link?.expiration;
|
||||
});
|
||||
|
||||
const dav_link_href = computed<string | undefined>(() => {
|
||||
if (props.existingDoc === undefined || props.existingDoc === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (props.existingDoc.status !== "ready") {
|
||||
return undefined;
|
||||
}
|
||||
if (props.existingDoc === undefined || props.existingDoc === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (props.existingDoc.status !== "ready") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return props.existingDoc._links?.dav_link?.href;
|
||||
return props.existingDoc._links?.dav_link?.href;
|
||||
});
|
||||
|
||||
const onAddDocument = ({
|
||||
stored_object,
|
||||
stored_object_version,
|
||||
file_name,
|
||||
stored_object,
|
||||
stored_object_version,
|
||||
file_name,
|
||||
}: {
|
||||
stored_object: StoredObject;
|
||||
stored_object_version: StoredObjectVersion;
|
||||
file_name: string;
|
||||
stored_object: StoredObject;
|
||||
stored_object_version: StoredObjectVersion;
|
||||
file_name: string;
|
||||
}): void => {
|
||||
emit("addDocument", { stored_object, stored_object_version, file_name });
|
||||
emit("addDocument", { stored_object, stored_object_version, file_name });
|
||||
};
|
||||
|
||||
const onRemoveDocument = (e: Event): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
emit("removeDocument");
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
emit("removeDocument");
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<drop-file
|
||||
:existingDoc="props.existingDoc"
|
||||
@addDocument="onAddDocument"
|
||||
></drop-file>
|
||||
<div>
|
||||
<drop-file
|
||||
:existingDoc="props.existingDoc"
|
||||
@addDocument="onAddDocument"
|
||||
></drop-file>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li v-if="has_existing_doc">
|
||||
<document-action-buttons-group
|
||||
:stored-object="props.existingDoc"
|
||||
:can-edit="props.existingDoc?.status === 'ready'"
|
||||
:can-download="true"
|
||||
:dav-link="dav_link_href"
|
||||
:dav-link-expiration="dav_link_expiration"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
v-if="allowRemove"
|
||||
class="btn btn-delete"
|
||||
@click="onRemoveDocument($event)"
|
||||
></button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="record_actions">
|
||||
<li v-if="has_existing_doc">
|
||||
<document-action-buttons-group
|
||||
:stored-object="props.existingDoc"
|
||||
:can-edit="props.existingDoc?.status === 'ready'"
|
||||
:can-download="true"
|
||||
:dav-link="dav_link_href"
|
||||
:dav-link-expiration="dav_link_expiration"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
v-if="allowRemove"
|
||||
class="btn btn-delete"
|
||||
@click="onRemoveDocument($event)"
|
||||
></button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
interface FileIconConfig {
|
||||
type: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
const props = defineProps<FileIconConfig>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<i class="fa fa-file-pdf-o" v-if="props.type === 'application/pdf'"></i>
|
||||
<i
|
||||
class="fa fa-file-word-o"
|
||||
v-else-if="props.type === 'application/vnd.oasis.opendocument.text'"
|
||||
></i>
|
||||
<i
|
||||
class="fa fa-file-word-o"
|
||||
v-else-if="
|
||||
props.type ===
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
"
|
||||
></i>
|
||||
<i
|
||||
class="fa fa-file-word-o"
|
||||
v-else-if="props.type === 'application/msword'"
|
||||
></i>
|
||||
<i
|
||||
class="fa fa-file-excel-o"
|
||||
v-else-if="
|
||||
props.type ===
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
"
|
||||
></i>
|
||||
<i
|
||||
class="fa fa-file-excel-o"
|
||||
v-else-if="props.type === 'application/vnd.ms-excel'"
|
||||
></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-archive-o"
|
||||
v-else-if="props.type === 'application/x-zip-compressed'"
|
||||
></i>
|
||||
<i class="fa fa-file-code-o" v-else></i>
|
||||
<i class="fa fa-file-pdf-o" v-if="props.type === 'application/pdf'"></i>
|
||||
<i
|
||||
class="fa fa-file-word-o"
|
||||
v-else-if="props.type === 'application/vnd.oasis.opendocument.text'"
|
||||
></i>
|
||||
<i
|
||||
class="fa fa-file-word-o"
|
||||
v-else-if="
|
||||
props.type ===
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
"
|
||||
></i>
|
||||
<i
|
||||
class="fa fa-file-word-o"
|
||||
v-else-if="props.type === 'application/msword'"
|
||||
></i>
|
||||
<i
|
||||
class="fa fa-file-excel-o"
|
||||
v-else-if="
|
||||
props.type ===
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
"
|
||||
></i>
|
||||
<i
|
||||
class="fa fa-file-excel-o"
|
||||
v-else-if="props.type === 'application/vnd.ms-excel'"
|
||||
></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-archive-o"
|
||||
v-else-if="props.type === 'application/x-zip-compressed'"
|
||||
></i>
|
||||
<i class="fa fa-file-code-o" v-else></i>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<template>
|
||||
<a :class="props.classes" @click="download_and_open($event)" ref="btn">
|
||||
<i class="fa fa-file-pdf-o"></i>
|
||||
Télécharger en pdf
|
||||
</a>
|
||||
<a :class="props.classes" @click="download_and_open($event)" ref="btn">
|
||||
<i class="fa fa-file-pdf-o"></i>
|
||||
Télécharger en pdf
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
build_convert_link,
|
||||
download_and_decrypt_doc,
|
||||
download_doc,
|
||||
build_convert_link,
|
||||
download_and_decrypt_doc,
|
||||
download_doc,
|
||||
} from "./helpers";
|
||||
import mime from "mime";
|
||||
import { reactive, ref } from "vue";
|
||||
import { StoredObject } from "../../types";
|
||||
|
||||
interface ConvertButtonConfig {
|
||||
storedObject: StoredObject;
|
||||
classes: Record<string, boolean>;
|
||||
filename?: string;
|
||||
storedObject: StoredObject;
|
||||
classes: Record<string, boolean>;
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
interface DownloadButtonState {
|
||||
content: null | string;
|
||||
content: null | string;
|
||||
}
|
||||
|
||||
const props = defineProps<ConvertButtonConfig>();
|
||||
@@ -30,36 +30,34 @@ const state: DownloadButtonState = reactive({ content: null });
|
||||
const btn = ref<HTMLAnchorElement | null>(null);
|
||||
|
||||
async function download_and_open(event: Event): Promise<void> {
|
||||
const button = event.target as HTMLAnchorElement;
|
||||
const button = event.target as HTMLAnchorElement;
|
||||
|
||||
if (null === state.content) {
|
||||
event.preventDefault();
|
||||
if (null === state.content) {
|
||||
event.preventDefault();
|
||||
|
||||
const raw = await download_doc(
|
||||
build_convert_link(props.storedObject.uuid),
|
||||
);
|
||||
state.content = window.URL.createObjectURL(raw);
|
||||
const raw = await download_doc(build_convert_link(props.storedObject.uuid));
|
||||
state.content = window.URL.createObjectURL(raw);
|
||||
|
||||
button.href = window.URL.createObjectURL(raw);
|
||||
button.type = "application/pdf";
|
||||
button.href = window.URL.createObjectURL(raw);
|
||||
button.type = "application/pdf";
|
||||
|
||||
button.download = props.filename + ".pdf" || "document.pdf";
|
||||
}
|
||||
button.download = props.filename + ".pdf" || "document.pdf";
|
||||
}
|
||||
|
||||
button.click();
|
||||
const reset_pending = setTimeout(reset_state, 45000);
|
||||
button.click();
|
||||
const reset_pending = setTimeout(reset_state, 45000);
|
||||
}
|
||||
|
||||
function reset_state(): void {
|
||||
state.content = null;
|
||||
btn.value?.removeAttribute("download");
|
||||
btn.value?.removeAttribute("href");
|
||||
btn.value?.removeAttribute("type");
|
||||
state.content = null;
|
||||
btn.value?.removeAttribute("download");
|
||||
btn.value?.removeAttribute("href");
|
||||
btn.value?.removeAttribute("type");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
i.fa::before {
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,13 +3,13 @@ import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
||||
import { computed, reactive } from "vue";
|
||||
|
||||
export interface DesktopEditButtonConfig {
|
||||
editLink: null;
|
||||
classes: Record<string, boolean>;
|
||||
expirationLink: number | Date;
|
||||
editLink: null;
|
||||
classes: Record<string, boolean>;
|
||||
expirationLink: number | Date;
|
||||
}
|
||||
|
||||
interface DesktopEditButtonState {
|
||||
modalOpened: boolean;
|
||||
modalOpened: boolean;
|
||||
}
|
||||
|
||||
const state: DesktopEditButtonState = reactive({ modalOpened: false });
|
||||
@@ -17,80 +17,76 @@ const state: DesktopEditButtonState = reactive({ modalOpened: false });
|
||||
const props = defineProps<DesktopEditButtonConfig>();
|
||||
|
||||
const buildCommand = computed<string>(
|
||||
() => "vnd.libreoffice.command:ofe|u|" + props.editLink,
|
||||
() => "vnd.libreoffice.command:ofe|u|" + props.editLink,
|
||||
);
|
||||
|
||||
const editionUntilFormatted = computed<string>(() => {
|
||||
let d;
|
||||
let d;
|
||||
|
||||
if (props.expirationLink instanceof Date) {
|
||||
d = props.expirationLink;
|
||||
} else {
|
||||
d = new Date(props.expirationLink * 1000);
|
||||
}
|
||||
console.log(props.expirationLink);
|
||||
if (props.expirationLink instanceof Date) {
|
||||
d = props.expirationLink;
|
||||
} else {
|
||||
d = new Date(props.expirationLink * 1000);
|
||||
}
|
||||
console.log(props.expirationLink);
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
dateStyle: "long",
|
||||
timeStyle: "medium",
|
||||
}).format(d);
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
dateStyle: "long",
|
||||
timeStyle: "medium",
|
||||
}).format(d);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<modal v-if="state.modalOpened" @close="state.modalOpened = false">
|
||||
<template v-slot:body>
|
||||
<div class="desktop-edit">
|
||||
<p class="center">
|
||||
Veuillez enregistrer vos modifications avant le
|
||||
</p>
|
||||
<p>
|
||||
<strong>{{ editionUntilFormatted }}</strong>
|
||||
</p>
|
||||
<teleport to="body">
|
||||
<modal v-if="state.modalOpened" @close="state.modalOpened = false">
|
||||
<template v-slot:body>
|
||||
<div class="desktop-edit">
|
||||
<p class="center">Veuillez enregistrer vos modifications avant le</p>
|
||||
<p>
|
||||
<strong>{{ editionUntilFormatted }}</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a class="btn btn-primary" :href="buildCommand"
|
||||
>Ouvrir le document pour édition</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<a class="btn btn-primary" :href="buildCommand"
|
||||
>Ouvrir le document pour édition</a
|
||||
>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<small
|
||||
>Le document peut être édité uniquement en utilisant
|
||||
Libre Office.</small
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<small
|
||||
>Le document peut être édité uniquement en utilisant Libre
|
||||
Office.</small
|
||||
>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<small
|
||||
>En cas d'échec lors de l'enregistrement, sauver le
|
||||
document sur le poste de travail avant de le déposer
|
||||
à nouveau ici.</small
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<small
|
||||
>En cas d'échec lors de l'enregistrement, sauver le document sur
|
||||
le poste de travail avant de le déposer à nouveau ici.</small
|
||||
>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<small
|
||||
>Vous pouvez naviguez sur d'autres pages pendant
|
||||
l'édition.</small
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
<a :class="props.classes" @click="state.modalOpened = true">
|
||||
<i class="fa fa-desktop"></i>
|
||||
Éditer sur le bureau
|
||||
</a>
|
||||
<p>
|
||||
<small
|
||||
>Vous pouvez naviguez sur d'autres pages pendant l'édition.</small
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
<a :class="props.classes" @click="state.modalOpened = true">
|
||||
<i class="fa fa-desktop"></i>
|
||||
Éditer sur le bureau
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.desktop-edit {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
i.fa::before {
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<template>
|
||||
<a
|
||||
v-if="!state.is_ready"
|
||||
:class="props.classes"
|
||||
@click="download_and_open()"
|
||||
title="Télécharger"
|
||||
>
|
||||
<i class="fa fa-download"></i>
|
||||
<template v-if="displayActionStringInButton">Télécharger</template>
|
||||
</a>
|
||||
<a
|
||||
v-else
|
||||
:class="props.classes"
|
||||
target="_blank"
|
||||
:type="props.atVersion.type"
|
||||
:download="buildDocumentName()"
|
||||
:href="state.href_url"
|
||||
ref="open_button"
|
||||
title="Ouvrir"
|
||||
>
|
||||
<i class="fa fa-external-link"></i>
|
||||
<template v-if="displayActionStringInButton">Ouvrir</template>
|
||||
</a>
|
||||
<a
|
||||
v-if="!state.is_ready"
|
||||
:class="props.classes"
|
||||
@click="download_and_open()"
|
||||
title="Télécharger"
|
||||
>
|
||||
<i class="fa fa-download"></i>
|
||||
<template v-if="displayActionStringInButton">Télécharger</template>
|
||||
</a>
|
||||
<a
|
||||
v-else
|
||||
:class="props.classes"
|
||||
target="_blank"
|
||||
:type="props.atVersion.type"
|
||||
:download="buildDocumentName()"
|
||||
:href="state.href_url"
|
||||
ref="open_button"
|
||||
title="Ouvrir"
|
||||
>
|
||||
<i class="fa fa-external-link"></i>
|
||||
<template v-if="displayActionStringInButton">Ouvrir</template>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -30,112 +30,109 @@ import mime from "mime";
|
||||
import { StoredObject, StoredObjectVersion } from "../../types";
|
||||
|
||||
interface DownloadButtonConfig {
|
||||
storedObject: StoredObject;
|
||||
atVersion: StoredObjectVersion;
|
||||
classes: Record<string, boolean>;
|
||||
filename?: string;
|
||||
/**
|
||||
* if true, display the action string into the button. If false, displays only
|
||||
* the icon
|
||||
*/
|
||||
displayActionStringInButton?: boolean;
|
||||
/**
|
||||
* if true, will download directly the file on load
|
||||
*/
|
||||
directDownload?: boolean;
|
||||
storedObject: StoredObject;
|
||||
atVersion: StoredObjectVersion;
|
||||
classes: Record<string, boolean>;
|
||||
filename?: string;
|
||||
/**
|
||||
* if true, display the action string into the button. If false, displays only
|
||||
* the icon
|
||||
*/
|
||||
displayActionStringInButton?: boolean;
|
||||
/**
|
||||
* if true, will download directly the file on load
|
||||
*/
|
||||
directDownload?: boolean;
|
||||
}
|
||||
|
||||
interface DownloadButtonState {
|
||||
is_ready: boolean;
|
||||
is_running: boolean;
|
||||
href_url: string;
|
||||
is_ready: boolean;
|
||||
is_running: boolean;
|
||||
href_url: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<DownloadButtonConfig>(), {
|
||||
displayActionStringInButton: true,
|
||||
directDownload: false,
|
||||
displayActionStringInButton: true,
|
||||
directDownload: false,
|
||||
});
|
||||
const state: DownloadButtonState = reactive({
|
||||
is_ready: false,
|
||||
is_running: false,
|
||||
href_url: "#",
|
||||
is_ready: false,
|
||||
is_running: false,
|
||||
href_url: "#",
|
||||
});
|
||||
|
||||
const open_button = ref<HTMLAnchorElement | null>(null);
|
||||
|
||||
function buildDocumentName(): string {
|
||||
let document_name = props.filename ?? props.storedObject.title;
|
||||
let document_name = props.filename ?? props.storedObject.title;
|
||||
|
||||
if ("" === document_name || null === document_name) {
|
||||
document_name = "document";
|
||||
}
|
||||
if ("" === document_name || null === document_name) {
|
||||
document_name = "document";
|
||||
}
|
||||
|
||||
const ext = mime.getExtension(props.atVersion.type);
|
||||
const ext = mime.getExtension(props.atVersion.type);
|
||||
|
||||
if (null !== ext) {
|
||||
return document_name + "." + ext;
|
||||
}
|
||||
if (null !== ext) {
|
||||
return document_name + "." + ext;
|
||||
}
|
||||
|
||||
return document_name;
|
||||
return document_name;
|
||||
}
|
||||
|
||||
async function download_and_open(): Promise<void> {
|
||||
if (state.is_running) {
|
||||
console.log("state is running, aborting");
|
||||
return;
|
||||
}
|
||||
if (state.is_running) {
|
||||
console.log("state is running, aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
state.is_running = true;
|
||||
state.is_running = true;
|
||||
|
||||
if (state.is_ready) {
|
||||
console.log("state is ready. This should not happens");
|
||||
return;
|
||||
}
|
||||
if (state.is_ready) {
|
||||
console.log("state is ready. This should not happens");
|
||||
return;
|
||||
}
|
||||
|
||||
let raw;
|
||||
let raw;
|
||||
|
||||
try {
|
||||
raw = await download_and_decrypt_doc(
|
||||
props.storedObject,
|
||||
props.atVersion,
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("error while downloading and decrypting document");
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
raw = await download_and_decrypt_doc(props.storedObject, props.atVersion);
|
||||
} catch (e) {
|
||||
console.error("error while downloading and decrypting document");
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
state.href_url = window.URL.createObjectURL(raw);
|
||||
state.is_running = false;
|
||||
state.is_ready = true;
|
||||
state.href_url = window.URL.createObjectURL(raw);
|
||||
state.is_running = false;
|
||||
state.is_ready = true;
|
||||
|
||||
if (!props.directDownload) {
|
||||
await nextTick();
|
||||
open_button.value?.click();
|
||||
if (!props.directDownload) {
|
||||
await nextTick();
|
||||
open_button.value?.click();
|
||||
|
||||
console.log("open button should have been clicked");
|
||||
setTimeout(reset_state, 45000);
|
||||
}
|
||||
console.log("open button should have been clicked");
|
||||
setTimeout(reset_state, 45000);
|
||||
}
|
||||
}
|
||||
|
||||
function reset_state(): void {
|
||||
state.href_url = "#";
|
||||
state.is_ready = false;
|
||||
state.is_running = false;
|
||||
state.href_url = "#";
|
||||
state.is_ready = false;
|
||||
state.is_running = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.directDownload) {
|
||||
download_and_open();
|
||||
}
|
||||
if (props.directDownload) {
|
||||
download_and_open();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
i.fa::before {
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
}
|
||||
i.fa {
|
||||
margin-right: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import HistoryButtonModal from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonModal.vue";
|
||||
import {
|
||||
StoredObject,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
StoredObject,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
} from "./../../types";
|
||||
import { computed, reactive, ref, useTemplateRef } from "vue";
|
||||
import { get_versions } from "./HistoryButton/api";
|
||||
|
||||
interface HistoryButtonConfig {
|
||||
storedObject: StoredObject;
|
||||
canEdit: boolean;
|
||||
storedObject: StoredObject;
|
||||
canEdit: boolean;
|
||||
}
|
||||
|
||||
interface HistoryButtonState {
|
||||
versions: StoredObjectVersionWithPointInTime[];
|
||||
loaded: boolean;
|
||||
versions: StoredObjectVersionWithPointInTime[];
|
||||
loaded: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<HistoryButtonConfig>();
|
||||
@@ -22,47 +22,47 @@ const state = reactive<HistoryButtonState>({ versions: [], loaded: false });
|
||||
const modal = useTemplateRef<typeof HistoryButtonModal>("modal");
|
||||
|
||||
const download_version_and_open_modal = async function (): Promise<void> {
|
||||
if (null !== modal.value) {
|
||||
modal.value.open();
|
||||
} else {
|
||||
console.log("modal is null");
|
||||
}
|
||||
if (null !== modal.value) {
|
||||
modal.value.open();
|
||||
} else {
|
||||
console.log("modal is null");
|
||||
}
|
||||
|
||||
if (!state.loaded) {
|
||||
const versions = await get_versions(props.storedObject);
|
||||
if (!state.loaded) {
|
||||
const versions = await get_versions(props.storedObject);
|
||||
|
||||
for (const version of versions) {
|
||||
state.versions.push(version);
|
||||
}
|
||||
state.loaded = true;
|
||||
for (const version of versions) {
|
||||
state.versions.push(version);
|
||||
}
|
||||
state.loaded = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onRestoreVersion = ({
|
||||
newVersion,
|
||||
newVersion,
|
||||
}: {
|
||||
newVersion: StoredObjectVersionWithPointInTime;
|
||||
newVersion: StoredObjectVersionWithPointInTime;
|
||||
}) => {
|
||||
state.versions.unshift(newVersion);
|
||||
state.versions.unshift(newVersion);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a @click="download_version_and_open_modal" class="dropdown-item">
|
||||
<history-button-modal
|
||||
ref="modal"
|
||||
:versions="state.versions"
|
||||
:stored-object="storedObject"
|
||||
:can-edit="canEdit"
|
||||
@restore-version="onRestoreVersion"
|
||||
></history-button-modal>
|
||||
<i class="fa fa-history"></i>
|
||||
Historique
|
||||
</a>
|
||||
<a @click="download_version_and_open_modal" class="dropdown-item">
|
||||
<history-button-modal
|
||||
ref="modal"
|
||||
:versions="state.versions"
|
||||
:stored-object="storedObject"
|
||||
:can-edit="canEdit"
|
||||
@restore-version="onRestoreVersion"
|
||||
></history-button-modal>
|
||||
<i class="fa fa-history"></i>
|
||||
Historique
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
i.fa::before {
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
StoredObject,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
StoredObject,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
} from "./../../../types";
|
||||
import HistoryButtonListItem from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonListItem.vue";
|
||||
import { computed, reactive } from "vue";
|
||||
|
||||
interface HistoryButtonListConfig {
|
||||
versions: StoredObjectVersionWithPointInTime[];
|
||||
storedObject: StoredObject;
|
||||
canEdit: boolean;
|
||||
versions: StoredObjectVersionWithPointInTime[];
|
||||
storedObject: StoredObject;
|
||||
canEdit: boolean;
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
||||
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
||||
}>();
|
||||
|
||||
interface HistoryButtonListState {
|
||||
/**
|
||||
* Contains the number of the newly created version when a version is restored.
|
||||
*/
|
||||
restored: number;
|
||||
/**
|
||||
* Contains the number of the newly created version when a version is restored.
|
||||
*/
|
||||
restored: number;
|
||||
}
|
||||
|
||||
const props = defineProps<HistoryButtonListConfig>();
|
||||
@@ -28,11 +28,11 @@ const props = defineProps<HistoryButtonListConfig>();
|
||||
const state = reactive<HistoryButtonListState>({ restored: -1 });
|
||||
|
||||
const higher_version = computed<number>(() =>
|
||||
props.versions.reduce(
|
||||
(accumulator: number, version: StoredObjectVersionWithPointInTime) =>
|
||||
Math.max(accumulator, version.version),
|
||||
-1,
|
||||
),
|
||||
props.versions.reduce(
|
||||
(accumulator: number, version: StoredObjectVersionWithPointInTime) =>
|
||||
Math.max(accumulator, version.version),
|
||||
-1,
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -41,32 +41,32 @@ const higher_version = computed<number>(() =>
|
||||
* internally, keep track of the newly restored version
|
||||
*/
|
||||
const onRestored = ({
|
||||
newVersion,
|
||||
newVersion,
|
||||
}: {
|
||||
newVersion: StoredObjectVersionWithPointInTime;
|
||||
newVersion: StoredObjectVersionWithPointInTime;
|
||||
}) => {
|
||||
state.restored = newVersion.version;
|
||||
emit("restoreVersion", { newVersion });
|
||||
state.restored = newVersion.version;
|
||||
emit("restoreVersion", { newVersion });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="props.versions.length > 0">
|
||||
<div class="container">
|
||||
<template v-for="v in props.versions" :key="v.id">
|
||||
<history-button-list-item
|
||||
:version="v"
|
||||
:can-edit="canEdit"
|
||||
:is-current="higher_version === v.version"
|
||||
:stored-object="storedObject"
|
||||
@restore-version="onRestored"
|
||||
></history-button-list-item>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>Chargement des versions</p>
|
||||
</template>
|
||||
<template v-if="props.versions.length > 0">
|
||||
<div class="container">
|
||||
<template v-for="v in props.versions" :key="v.id">
|
||||
<history-button-list-item
|
||||
:version="v"
|
||||
:can-edit="canEdit"
|
||||
:is-current="higher_version === v.version"
|
||||
:stored-object="storedObject"
|
||||
@restore-version="onRestored"
|
||||
></history-button-list-item>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>Chargement des versions</p>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
StoredObject,
|
||||
StoredObjectPointInTime,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
StoredObject,
|
||||
StoredObjectPointInTime,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
} from "./../../../types";
|
||||
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue";
|
||||
import { ISOToDatetime } from "./../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||
@@ -12,185 +12,173 @@ import DownloadButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/Downloa
|
||||
import { computed } from "vue";
|
||||
|
||||
interface HistoryButtonListItemConfig {
|
||||
version: StoredObjectVersionWithPointInTime;
|
||||
storedObject: StoredObject;
|
||||
canEdit: boolean;
|
||||
isCurrent: boolean;
|
||||
version: StoredObjectVersionWithPointInTime;
|
||||
storedObject: StoredObject;
|
||||
canEdit: boolean;
|
||||
isCurrent: boolean;
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
||||
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
||||
}>();
|
||||
|
||||
const props = defineProps<HistoryButtonListItemConfig>();
|
||||
|
||||
const onRestore = ({
|
||||
newVersion,
|
||||
newVersion,
|
||||
}: {
|
||||
newVersion: StoredObjectVersionWithPointInTime;
|
||||
newVersion: StoredObjectVersionWithPointInTime;
|
||||
}) => {
|
||||
emit("restoreVersion", { newVersion });
|
||||
emit("restoreVersion", { newVersion });
|
||||
};
|
||||
|
||||
const isKeptBeforeConversion = computed<boolean>(() => {
|
||||
if ("point-in-times" in props.version) {
|
||||
return props.version["point-in-times"].reduce(
|
||||
(accumulator: boolean, pit: StoredObjectPointInTime) =>
|
||||
accumulator || "keep-before-conversion" === pit.reason,
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if ("point-in-times" in props.version) {
|
||||
return props.version["point-in-times"].reduce(
|
||||
(accumulator: boolean, pit: StoredObjectPointInTime) =>
|
||||
accumulator || "keep-before-conversion" === pit.reason,
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const isRestored = computed<boolean>(
|
||||
() => props.version.version > 0 && null !== props.version["from-restored"],
|
||||
() => props.version.version > 0 && null !== props.version["from-restored"],
|
||||
);
|
||||
|
||||
const isDuplicated = computed<boolean>(
|
||||
() =>
|
||||
props.version.version === 0 && null !== props.version["from-restored"],
|
||||
() => props.version.version === 0 && null !== props.version["from-restored"],
|
||||
);
|
||||
|
||||
const classes = computed<{
|
||||
row: true;
|
||||
"row-hover": true;
|
||||
"blinking-1": boolean;
|
||||
"blinking-2": boolean;
|
||||
row: true;
|
||||
"row-hover": true;
|
||||
"blinking-1": boolean;
|
||||
"blinking-2": boolean;
|
||||
}>(() => ({
|
||||
row: true,
|
||||
"row-hover": true,
|
||||
"blinking-1": props.isRestored && 0 === props.version.version % 2,
|
||||
"blinking-2": props.isRestored && 1 === props.version.version % 2,
|
||||
row: true,
|
||||
"row-hover": true,
|
||||
"blinking-1": props.isRestored && 0 === props.version.version % 2,
|
||||
"blinking-2": props.isRestored && 1 === props.version.version % 2,
|
||||
}));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="classes">
|
||||
<div
|
||||
class="col-12 tags"
|
||||
v-if="
|
||||
isCurrent ||
|
||||
isKeptBeforeConversion ||
|
||||
isRestored ||
|
||||
isDuplicated
|
||||
"
|
||||
>
|
||||
<span class="badge bg-success" v-if="isCurrent"
|
||||
>Version actuelle</span
|
||||
>
|
||||
<span class="badge bg-info" v-if="isKeptBeforeConversion"
|
||||
>Conservée avant conversion dans un autre format</span
|
||||
>
|
||||
<span class="badge bg-info" v-if="isRestored"
|
||||
>Restaurée depuis la version
|
||||
{{ version["from-restored"]?.version + 1 }}</span
|
||||
>
|
||||
<span class="badge bg-info" v-if="isDuplicated"
|
||||
>Dupliqué depuis un autre document</span
|
||||
>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<file-icon :type="version.type"></file-icon>
|
||||
<span
|
||||
><strong> #{{ version.version + 1 }} </strong></span
|
||||
>
|
||||
<template
|
||||
v-if="version.createdBy !== null && version.createdAt !== null"
|
||||
><strong v-if="version.version == 0">créé par</strong
|
||||
><strong v-else>modifié par</strong>
|
||||
<span class="badge-user"
|
||||
><UserRenderBoxBadge
|
||||
:user="version.createdBy"
|
||||
></UserRenderBoxBadge
|
||||
></span>
|
||||
<strong>à</strong>
|
||||
{{
|
||||
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
|
||||
}}</template
|
||||
><template
|
||||
v-if="version.createdBy === null && version.createdAt !== null"
|
||||
><strong v-if="version.version == 0">Créé le</strong
|
||||
><strong v-else>modifié le</strong>
|
||||
{{
|
||||
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
|
||||
}}</template
|
||||
>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<ul class="record_actions small slim on-version-actions">
|
||||
<li v-if="canEdit && !isCurrent">
|
||||
<restore-version-button
|
||||
:stored-object-version="props.version"
|
||||
@restore-version="onRestore"
|
||||
></restore-version-button>
|
||||
</li>
|
||||
<li>
|
||||
<download-button
|
||||
:stored-object="storedObject"
|
||||
:at-version="version"
|
||||
:classes="{
|
||||
btn: true,
|
||||
'btn-outline-primary': true,
|
||||
'btn-sm': true,
|
||||
}"
|
||||
:display-action-string-in-button="false"
|
||||
></download-button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div :class="classes">
|
||||
<div
|
||||
class="col-12 tags"
|
||||
v-if="isCurrent || isKeptBeforeConversion || isRestored || isDuplicated"
|
||||
>
|
||||
<span class="badge bg-success" v-if="isCurrent">Version actuelle</span>
|
||||
<span class="badge bg-info" v-if="isKeptBeforeConversion"
|
||||
>Conservée avant conversion dans un autre format</span
|
||||
>
|
||||
<span class="badge bg-info" v-if="isRestored"
|
||||
>Restaurée depuis la version
|
||||
{{ version["from-restored"]?.version + 1 }}</span
|
||||
>
|
||||
<span class="badge bg-info" v-if="isDuplicated"
|
||||
>Dupliqué depuis un autre document</span
|
||||
>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<file-icon :type="version.type"></file-icon>
|
||||
<span
|
||||
><strong> #{{ version.version + 1 }} </strong></span
|
||||
>
|
||||
<template v-if="version.createdBy !== null && version.createdAt !== null"
|
||||
><strong v-if="version.version == 0">créé par</strong
|
||||
><strong v-else>modifié par</strong>
|
||||
<span class="badge-user"
|
||||
><UserRenderBoxBadge :user="version.createdBy"></UserRenderBoxBadge
|
||||
></span>
|
||||
<strong>à</strong>
|
||||
{{
|
||||
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
|
||||
}}</template
|
||||
><template v-if="version.createdBy === null && version.createdAt !== null"
|
||||
><strong v-if="version.version == 0">Créé le</strong
|
||||
><strong v-else>modifié le</strong>
|
||||
{{
|
||||
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
|
||||
}}</template
|
||||
>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<ul class="record_actions small slim on-version-actions">
|
||||
<li v-if="canEdit && !isCurrent">
|
||||
<restore-version-button
|
||||
:stored-object-version="props.version"
|
||||
@restore-version="onRestore"
|
||||
></restore-version-button>
|
||||
</li>
|
||||
<li>
|
||||
<download-button
|
||||
:stored-object="storedObject"
|
||||
:at-version="version"
|
||||
:classes="{
|
||||
btn: true,
|
||||
'btn-outline-primary': true,
|
||||
'btn-sm': true,
|
||||
}"
|
||||
:display-action-string-in-button="false"
|
||||
></download-button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
div.tags {
|
||||
span.badge:not(:last-child) {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
span.badge:not(:last-child) {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
// to make the animation restart, we have the same animation twice,
|
||||
// and alternate between both
|
||||
.blinking-1 {
|
||||
animation-name: backgroundColorPalette-1;
|
||||
animation-duration: 8s;
|
||||
animation-iteration-count: 1;
|
||||
animation-direction: normal;
|
||||
animation-timing-function: linear;
|
||||
animation-name: backgroundColorPalette-1;
|
||||
animation-duration: 8s;
|
||||
animation-iteration-count: 1;
|
||||
animation-direction: normal;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
@keyframes backgroundColorPalette-1 {
|
||||
0% {
|
||||
background: var(--bs-chill-green-dark);
|
||||
}
|
||||
25% {
|
||||
background: var(--bs-chill-green);
|
||||
}
|
||||
65% {
|
||||
background: var(--bs-chill-beige);
|
||||
}
|
||||
100% {
|
||||
background: unset;
|
||||
}
|
||||
0% {
|
||||
background: var(--bs-chill-green-dark);
|
||||
}
|
||||
25% {
|
||||
background: var(--bs-chill-green);
|
||||
}
|
||||
65% {
|
||||
background: var(--bs-chill-beige);
|
||||
}
|
||||
100% {
|
||||
background: unset;
|
||||
}
|
||||
}
|
||||
.blinking-2 {
|
||||
animation-name: backgroundColorPalette-2;
|
||||
animation-duration: 8s;
|
||||
animation-iteration-count: 1;
|
||||
animation-direction: normal;
|
||||
animation-timing-function: linear;
|
||||
animation-name: backgroundColorPalette-2;
|
||||
animation-duration: 8s;
|
||||
animation-iteration-count: 1;
|
||||
animation-direction: normal;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
@keyframes backgroundColorPalette-2 {
|
||||
0% {
|
||||
background: var(--bs-chill-green-dark);
|
||||
}
|
||||
25% {
|
||||
background: var(--bs-chill-green);
|
||||
}
|
||||
65% {
|
||||
background: var(--bs-chill-beige);
|
||||
}
|
||||
100% {
|
||||
background: unset;
|
||||
}
|
||||
0% {
|
||||
background: var(--bs-chill-green-dark);
|
||||
}
|
||||
25% {
|
||||
background: var(--bs-chill-green);
|
||||
}
|
||||
65% {
|
||||
background: var(--bs-chill-beige);
|
||||
}
|
||||
100% {
|
||||
background: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,54 +3,54 @@ import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
||||
import { reactive } from "vue";
|
||||
import HistoryButtonList from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue";
|
||||
import {
|
||||
StoredObject,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
StoredObject,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
} from "./../../../types";
|
||||
|
||||
interface HistoryButtonListConfig {
|
||||
versions: StoredObjectVersionWithPointInTime[];
|
||||
storedObject: StoredObject;
|
||||
canEdit: boolean;
|
||||
versions: StoredObjectVersionWithPointInTime[];
|
||||
storedObject: StoredObject;
|
||||
canEdit: boolean;
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
||||
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
||||
}>();
|
||||
|
||||
interface HistoryButtonModalState {
|
||||
opened: boolean;
|
||||
opened: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<HistoryButtonListConfig>();
|
||||
const state = reactive<HistoryButtonModalState>({ opened: false });
|
||||
|
||||
const open = () => {
|
||||
state.opened = true;
|
||||
state.opened = true;
|
||||
};
|
||||
|
||||
const onRestoreVersion = (payload: {
|
||||
newVersion: StoredObjectVersionWithPointInTime;
|
||||
newVersion: StoredObjectVersionWithPointInTime;
|
||||
}) => emit("restoreVersion", payload);
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<modal v-if="state.opened" @close="state.opened = false">
|
||||
<template v-slot:header>
|
||||
<h3>Historique des versions du document</h3>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<p>Les versions sont conservées pendant 90 jours.</p>
|
||||
<history-button-list
|
||||
:versions="props.versions"
|
||||
:can-edit="canEdit"
|
||||
:stored-object="storedObject"
|
||||
@restore-version="onRestoreVersion"
|
||||
></history-button-list>
|
||||
</template>
|
||||
</modal>
|
||||
</Teleport>
|
||||
<Teleport to="body">
|
||||
<modal v-if="state.opened" @close="state.opened = false">
|
||||
<template v-slot:header>
|
||||
<h3>Historique des versions du document</h3>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<p>Les versions sont conservées pendant 90 jours.</p>
|
||||
<history-button-list
|
||||
:versions="props.versions"
|
||||
:can-edit="canEdit"
|
||||
:stored-object="storedObject"
|
||||
@restore-version="onRestoreVersion"
|
||||
></history-button-list>
|
||||
</template>
|
||||
</modal>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
StoredObjectVersionPersisted,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
StoredObjectVersionPersisted,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
} from "../../../types";
|
||||
import { useToast } from "vue-toast-notification";
|
||||
import { restore_version } from "./api";
|
||||
|
||||
interface RestoreVersionButtonProps {
|
||||
storedObjectVersion: StoredObjectVersionPersisted;
|
||||
storedObjectVersion: StoredObjectVersionPersisted;
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
||||
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
||||
}>();
|
||||
|
||||
const props = defineProps<RestoreVersionButtonProps>();
|
||||
@@ -19,21 +19,21 @@ const props = defineProps<RestoreVersionButtonProps>();
|
||||
const $toast = useToast();
|
||||
|
||||
const restore_version_fn = async () => {
|
||||
const newVersion = await restore_version(props.storedObjectVersion);
|
||||
const newVersion = await restore_version(props.storedObjectVersion);
|
||||
|
||||
$toast.success("Version restaurée");
|
||||
emit("restoreVersion", { newVersion });
|
||||
$toast.success("Version restaurée");
|
||||
emit("restoreVersion", { newVersion });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="btn btn-outline-action"
|
||||
@click="restore_version_fn"
|
||||
title="Restaurer"
|
||||
>
|
||||
<i class="fa fa-rotate-left"></i> Restaurer
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-outline-action"
|
||||
@click="restore_version_fn"
|
||||
title="Restaurer"
|
||||
>
|
||||
<i class="fa fa-rotate-left"></i> Restaurer
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import {
|
||||
StoredObject,
|
||||
StoredObjectVersionPersisted,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
StoredObject,
|
||||
StoredObjectVersionPersisted,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
} from "../../../types";
|
||||
import {
|
||||
fetchResults,
|
||||
makeFetch,
|
||||
fetchResults,
|
||||
makeFetch,
|
||||
} from "../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
|
||||
export const get_versions = async (
|
||||
storedObject: StoredObject,
|
||||
storedObject: StoredObject,
|
||||
): Promise<StoredObjectVersionWithPointInTime[]> => {
|
||||
const versions = await fetchResults<StoredObjectVersionWithPointInTime>(
|
||||
`/api/1.0/doc-store/stored-object/${storedObject.uuid}/versions`,
|
||||
);
|
||||
const versions = await fetchResults<StoredObjectVersionWithPointInTime>(
|
||||
`/api/1.0/doc-store/stored-object/${storedObject.uuid}/versions`,
|
||||
);
|
||||
|
||||
return versions.sort(
|
||||
(
|
||||
a: StoredObjectVersionWithPointInTime,
|
||||
b: StoredObjectVersionWithPointInTime,
|
||||
) => b.version - a.version,
|
||||
);
|
||||
return versions.sort(
|
||||
(
|
||||
a: StoredObjectVersionWithPointInTime,
|
||||
b: StoredObjectVersionWithPointInTime,
|
||||
) => b.version - a.version,
|
||||
);
|
||||
};
|
||||
|
||||
export const restore_version = async (
|
||||
version: StoredObjectVersionPersisted,
|
||||
version: StoredObjectVersionPersisted,
|
||||
): Promise<StoredObjectVersionWithPointInTime> => {
|
||||
return await makeFetch<null, StoredObjectVersionWithPointInTime>(
|
||||
"POST",
|
||||
`/api/1.0/doc-store/stored-object/restore-from-version/${version.id}`,
|
||||
);
|
||||
return await makeFetch<null, StoredObjectVersionWithPointInTime>(
|
||||
"POST",
|
||||
`/api/1.0/doc-store/stored-object/restore-from-version/${version.id}`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
<template>
|
||||
<a
|
||||
:class="Object.assign(props.classes, { btn: true })"
|
||||
@click="beforeLeave($event)"
|
||||
:href="
|
||||
build_wopi_editor_link(props.storedObject.uuid, props.returnPath)
|
||||
"
|
||||
>
|
||||
<i class="fa fa-paragraph"></i>
|
||||
Editer en ligne
|
||||
</a>
|
||||
<a
|
||||
:class="Object.assign(props.classes, { btn: true })"
|
||||
@click="beforeLeave($event)"
|
||||
:href="build_wopi_editor_link(props.storedObject.uuid, props.returnPath)"
|
||||
>
|
||||
<i class="fa fa-paragraph"></i>
|
||||
Editer en ligne
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import WopiEditButton from "./WopiEditButton.vue";
|
||||
import { build_wopi_editor_link } from "./helpers";
|
||||
import {
|
||||
StoredObject,
|
||||
WopiEditButtonExecutableBeforeLeaveFunction,
|
||||
StoredObject,
|
||||
WopiEditButtonExecutableBeforeLeaveFunction,
|
||||
} from "../../types";
|
||||
|
||||
interface WopiEditButtonConfig {
|
||||
storedObject: StoredObject;
|
||||
returnPath?: string;
|
||||
classes: Record<string, boolean>;
|
||||
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
|
||||
storedObject: StoredObject;
|
||||
returnPath?: string;
|
||||
classes: Record<string, boolean>;
|
||||
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
|
||||
}
|
||||
|
||||
const props = defineProps<WopiEditButtonConfig>();
|
||||
@@ -31,24 +29,24 @@ const props = defineProps<WopiEditButtonConfig>();
|
||||
let executed = false;
|
||||
|
||||
async function beforeLeave(event: Event): Promise<true> {
|
||||
if (props.executeBeforeLeave === undefined || executed === true) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
await props.executeBeforeLeave();
|
||||
executed = true;
|
||||
|
||||
const link = event.target as HTMLAnchorElement;
|
||||
link.click();
|
||||
|
||||
if (props.executeBeforeLeave === undefined || executed === true) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
await props.executeBeforeLeave();
|
||||
executed = true;
|
||||
|
||||
const link = event.target as HTMLAnchorElement;
|
||||
link.click();
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
i.fa::before {
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,235 +1,230 @@
|
||||
import {
|
||||
StoredObject,
|
||||
StoredObjectStatus,
|
||||
StoredObjectStatusChange,
|
||||
StoredObjectVersion,
|
||||
StoredObject,
|
||||
StoredObjectStatus,
|
||||
StoredObjectStatusChange,
|
||||
StoredObjectVersion,
|
||||
} from "../../types";
|
||||
import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
|
||||
const MIMES_EDIT = new Set([
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.oasis.opendocument.text",
|
||||
"application/vnd.oasis.opendocument.text-flat-xml",
|
||||
"application/vnd.oasis.opendocument.spreadsheet",
|
||||
"application/vnd.oasis.opendocument.spreadsheet-flat-xml",
|
||||
"application/vnd.oasis.opendocument.presentation",
|
||||
"application/vnd.oasis.opendocument.presentation-flat-xml",
|
||||
"application/vnd.oasis.opendocument.graphics",
|
||||
"application/vnd.oasis.opendocument.graphics-flat-xml",
|
||||
"application/vnd.oasis.opendocument.chart",
|
||||
"application/msword",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.ms-word.document.macroEnabled.12",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
||||
"application/vnd.ms-excel.sheet.macroEnabled.12",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
||||
"application/x-dif-document",
|
||||
"text/spreadsheet",
|
||||
"text/csv",
|
||||
"application/x-dbase",
|
||||
"text/rtf",
|
||||
"text/plain",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.oasis.opendocument.text",
|
||||
"application/vnd.oasis.opendocument.text-flat-xml",
|
||||
"application/vnd.oasis.opendocument.spreadsheet",
|
||||
"application/vnd.oasis.opendocument.spreadsheet-flat-xml",
|
||||
"application/vnd.oasis.opendocument.presentation",
|
||||
"application/vnd.oasis.opendocument.presentation-flat-xml",
|
||||
"application/vnd.oasis.opendocument.graphics",
|
||||
"application/vnd.oasis.opendocument.graphics-flat-xml",
|
||||
"application/vnd.oasis.opendocument.chart",
|
||||
"application/msword",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.ms-word.document.macroEnabled.12",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
||||
"application/vnd.ms-excel.sheet.macroEnabled.12",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
||||
"application/x-dif-document",
|
||||
"text/spreadsheet",
|
||||
"text/csv",
|
||||
"application/x-dbase",
|
||||
"text/rtf",
|
||||
"text/plain",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
||||
]);
|
||||
|
||||
const MIMES_VIEW = new Set([
|
||||
...MIMES_EDIT,
|
||||
[
|
||||
"image/svg+xml",
|
||||
"application/vnd.sun.xml.writer",
|
||||
"application/vnd.sun.xml.calc",
|
||||
"application/vnd.sun.xml.impress",
|
||||
"application/vnd.sun.xml.draw",
|
||||
"application/vnd.sun.xml.writer.global",
|
||||
"application/vnd.sun.xml.writer.template",
|
||||
"application/vnd.sun.xml.calc.template",
|
||||
"application/vnd.sun.xml.impress.template",
|
||||
"application/vnd.sun.xml.draw.template",
|
||||
"application/vnd.oasis.opendocument.text-master",
|
||||
"application/vnd.oasis.opendocument.text-template",
|
||||
"application/vnd.oasis.opendocument.text-master-template",
|
||||
"application/vnd.oasis.opendocument.spreadsheet-template",
|
||||
"application/vnd.oasis.opendocument.presentation-template",
|
||||
"application/vnd.oasis.opendocument.graphics-template",
|
||||
"application/vnd.ms-word.template.macroEnabled.12",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
||||
"application/vnd.ms-excel.template.macroEnabled.12",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.template",
|
||||
"application/vnd.ms-powerpoint.template.macroEnabled.12",
|
||||
"application/vnd.wordperfect",
|
||||
"application/x-aportisdoc",
|
||||
"application/x-hwp",
|
||||
"application/vnd.ms-works",
|
||||
"application/x-mswrite",
|
||||
"application/vnd.lotus-1-2-3",
|
||||
"image/cgm",
|
||||
"image/vnd.dxf",
|
||||
"image/x-emf",
|
||||
"image/x-wmf",
|
||||
"application/coreldraw",
|
||||
"application/vnd.visio2013",
|
||||
"application/vnd.visio",
|
||||
"application/vnd.ms-visio.drawing",
|
||||
"application/x-mspublisher",
|
||||
"application/x-sony-bbeb",
|
||||
"application/x-gnumeric",
|
||||
"application/macwriteii",
|
||||
"application/x-iwork-numbers-sffnumbers",
|
||||
"application/vnd.oasis.opendocument.text-web",
|
||||
"application/x-pagemaker",
|
||||
"application/x-fictionbook+xml",
|
||||
"application/clarisworks",
|
||||
"image/x-wpg",
|
||||
"application/x-iwork-pages-sffpages",
|
||||
"application/x-iwork-keynote-sffkey",
|
||||
"application/x-abiword",
|
||||
"image/x-freehand",
|
||||
"application/vnd.sun.xml.chart",
|
||||
"application/x-t602",
|
||||
"image/bmp",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/tiff",
|
||||
"image/jpg",
|
||||
"image/jpeg",
|
||||
"application/pdf",
|
||||
],
|
||||
...MIMES_EDIT,
|
||||
[
|
||||
"image/svg+xml",
|
||||
"application/vnd.sun.xml.writer",
|
||||
"application/vnd.sun.xml.calc",
|
||||
"application/vnd.sun.xml.impress",
|
||||
"application/vnd.sun.xml.draw",
|
||||
"application/vnd.sun.xml.writer.global",
|
||||
"application/vnd.sun.xml.writer.template",
|
||||
"application/vnd.sun.xml.calc.template",
|
||||
"application/vnd.sun.xml.impress.template",
|
||||
"application/vnd.sun.xml.draw.template",
|
||||
"application/vnd.oasis.opendocument.text-master",
|
||||
"application/vnd.oasis.opendocument.text-template",
|
||||
"application/vnd.oasis.opendocument.text-master-template",
|
||||
"application/vnd.oasis.opendocument.spreadsheet-template",
|
||||
"application/vnd.oasis.opendocument.presentation-template",
|
||||
"application/vnd.oasis.opendocument.graphics-template",
|
||||
"application/vnd.ms-word.template.macroEnabled.12",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
||||
"application/vnd.ms-excel.template.macroEnabled.12",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.template",
|
||||
"application/vnd.ms-powerpoint.template.macroEnabled.12",
|
||||
"application/vnd.wordperfect",
|
||||
"application/x-aportisdoc",
|
||||
"application/x-hwp",
|
||||
"application/vnd.ms-works",
|
||||
"application/x-mswrite",
|
||||
"application/vnd.lotus-1-2-3",
|
||||
"image/cgm",
|
||||
"image/vnd.dxf",
|
||||
"image/x-emf",
|
||||
"image/x-wmf",
|
||||
"application/coreldraw",
|
||||
"application/vnd.visio2013",
|
||||
"application/vnd.visio",
|
||||
"application/vnd.ms-visio.drawing",
|
||||
"application/x-mspublisher",
|
||||
"application/x-sony-bbeb",
|
||||
"application/x-gnumeric",
|
||||
"application/macwriteii",
|
||||
"application/x-iwork-numbers-sffnumbers",
|
||||
"application/vnd.oasis.opendocument.text-web",
|
||||
"application/x-pagemaker",
|
||||
"application/x-fictionbook+xml",
|
||||
"application/clarisworks",
|
||||
"image/x-wpg",
|
||||
"application/x-iwork-pages-sffpages",
|
||||
"application/x-iwork-keynote-sffkey",
|
||||
"application/x-abiword",
|
||||
"image/x-freehand",
|
||||
"application/vnd.sun.xml.chart",
|
||||
"application/x-t602",
|
||||
"image/bmp",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/tiff",
|
||||
"image/jpg",
|
||||
"image/jpeg",
|
||||
"application/pdf",
|
||||
],
|
||||
]);
|
||||
|
||||
export interface SignedUrlGet {
|
||||
method: "GET" | "HEAD";
|
||||
url: string;
|
||||
expires: number;
|
||||
object_name: string;
|
||||
method: "GET" | "HEAD";
|
||||
url: string;
|
||||
expires: number;
|
||||
object_name: string;
|
||||
}
|
||||
|
||||
function is_extension_editable(mimeType: string): boolean {
|
||||
return MIMES_EDIT.has(mimeType);
|
||||
return MIMES_EDIT.has(mimeType);
|
||||
}
|
||||
|
||||
function is_extension_viewable(mimeType: string): boolean {
|
||||
return MIMES_VIEW.has(mimeType);
|
||||
return MIMES_VIEW.has(mimeType);
|
||||
}
|
||||
|
||||
function build_convert_link(uuid: string) {
|
||||
return `/chill/wopi/convert/${uuid}`;
|
||||
return `/chill/wopi/convert/${uuid}`;
|
||||
}
|
||||
|
||||
function build_download_info_link(
|
||||
storedObject: StoredObject,
|
||||
atVersion: null | StoredObjectVersion,
|
||||
storedObject: StoredObject,
|
||||
atVersion: null | StoredObjectVersion,
|
||||
): string {
|
||||
const url = `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/get`;
|
||||
const url = `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/get`;
|
||||
|
||||
if (null !== atVersion) {
|
||||
const params = new URLSearchParams({ version: atVersion.filename });
|
||||
if (null !== atVersion) {
|
||||
const params = new URLSearchParams({ version: atVersion.filename });
|
||||
|
||||
return url + "?" + params.toString();
|
||||
}
|
||||
return url + "?" + params.toString();
|
||||
}
|
||||
|
||||
return url;
|
||||
return url;
|
||||
}
|
||||
|
||||
async function download_info_link(
|
||||
storedObject: StoredObject,
|
||||
atVersion: null | StoredObjectVersion,
|
||||
storedObject: StoredObject,
|
||||
atVersion: null | StoredObjectVersion,
|
||||
): Promise<SignedUrlGet> {
|
||||
return makeFetch("GET", build_download_info_link(storedObject, atVersion));
|
||||
return makeFetch("GET", build_download_info_link(storedObject, atVersion));
|
||||
}
|
||||
|
||||
function build_wopi_editor_link(uuid: string, returnPath?: string) {
|
||||
if (returnPath === undefined) {
|
||||
returnPath =
|
||||
window.location.pathname +
|
||||
window.location.search +
|
||||
window.location.hash;
|
||||
}
|
||||
if (returnPath === undefined) {
|
||||
returnPath =
|
||||
window.location.pathname + window.location.search + window.location.hash;
|
||||
}
|
||||
|
||||
return (
|
||||
`/chill/wopi/edit/${uuid}?returnPath=` + encodeURIComponent(returnPath)
|
||||
);
|
||||
return (
|
||||
`/chill/wopi/edit/${uuid}?returnPath=` + encodeURIComponent(returnPath)
|
||||
);
|
||||
}
|
||||
|
||||
function download_doc(url: string): Promise<Blob> {
|
||||
return window.fetch(url).then((r) => {
|
||||
if (r.ok) {
|
||||
return r.blob();
|
||||
}
|
||||
return window.fetch(url).then((r) => {
|
||||
if (r.ok) {
|
||||
return r.blob();
|
||||
}
|
||||
|
||||
throw new Error("Could not download document");
|
||||
});
|
||||
throw new Error("Could not download document");
|
||||
});
|
||||
}
|
||||
|
||||
async function download_and_decrypt_doc(
|
||||
storedObject: StoredObject,
|
||||
atVersion: null | StoredObjectVersion,
|
||||
storedObject: StoredObject,
|
||||
atVersion: null | StoredObjectVersion,
|
||||
): Promise<Blob> {
|
||||
const algo = "AES-CBC";
|
||||
const algo = "AES-CBC";
|
||||
|
||||
const atVersionToDownload = atVersion ?? storedObject.currentVersion;
|
||||
const atVersionToDownload = atVersion ?? storedObject.currentVersion;
|
||||
|
||||
if (null === atVersionToDownload) {
|
||||
throw new Error("no version associated to stored object");
|
||||
}
|
||||
if (null === atVersionToDownload) {
|
||||
throw new Error("no version associated to stored object");
|
||||
}
|
||||
|
||||
// sometimes, the downloadInfo may be embedded into the storedObject
|
||||
console.log("storedObject", storedObject);
|
||||
let downloadInfo;
|
||||
if (
|
||||
typeof storedObject._links !== "undefined" &&
|
||||
typeof storedObject._links.downloadLink !== "undefined"
|
||||
) {
|
||||
downloadInfo = storedObject._links.downloadLink;
|
||||
} else {
|
||||
downloadInfo = await download_info_link(
|
||||
storedObject,
|
||||
atVersionToDownload,
|
||||
);
|
||||
}
|
||||
// sometimes, the downloadInfo may be embedded into the storedObject
|
||||
console.log("storedObject", storedObject);
|
||||
let downloadInfo;
|
||||
if (
|
||||
typeof storedObject._links !== "undefined" &&
|
||||
typeof storedObject._links.downloadLink !== "undefined"
|
||||
) {
|
||||
downloadInfo = storedObject._links.downloadLink;
|
||||
} else {
|
||||
downloadInfo = await download_info_link(storedObject, atVersionToDownload);
|
||||
}
|
||||
|
||||
const rawResponse = await window.fetch(downloadInfo.url);
|
||||
const rawResponse = await window.fetch(downloadInfo.url);
|
||||
|
||||
if (!rawResponse.ok) {
|
||||
throw new Error(
|
||||
"error while downloading raw file " +
|
||||
rawResponse.status +
|
||||
" " +
|
||||
rawResponse.statusText,
|
||||
);
|
||||
}
|
||||
if (!rawResponse.ok) {
|
||||
throw new Error(
|
||||
"error while downloading raw file " +
|
||||
rawResponse.status +
|
||||
" " +
|
||||
rawResponse.statusText,
|
||||
);
|
||||
}
|
||||
|
||||
if (atVersionToDownload.iv.length === 0) {
|
||||
return rawResponse.blob();
|
||||
}
|
||||
if (atVersionToDownload.iv.length === 0) {
|
||||
return rawResponse.blob();
|
||||
}
|
||||
|
||||
const rawBuffer = await rawResponse.arrayBuffer();
|
||||
try {
|
||||
const key = await window.crypto.subtle.importKey(
|
||||
"jwk",
|
||||
atVersionToDownload.keyInfos,
|
||||
{ name: algo },
|
||||
false,
|
||||
["decrypt"],
|
||||
);
|
||||
const iv = Uint8Array.from(atVersionToDownload.iv);
|
||||
const decrypted = await window.crypto.subtle.decrypt(
|
||||
{ name: algo, iv: iv },
|
||||
key,
|
||||
rawBuffer,
|
||||
);
|
||||
const rawBuffer = await rawResponse.arrayBuffer();
|
||||
try {
|
||||
const key = await window.crypto.subtle.importKey(
|
||||
"jwk",
|
||||
atVersionToDownload.keyInfos,
|
||||
{ name: algo },
|
||||
false,
|
||||
["decrypt"],
|
||||
);
|
||||
const iv = Uint8Array.from(atVersionToDownload.iv);
|
||||
const decrypted = await window.crypto.subtle.decrypt(
|
||||
{ name: algo, iv: iv },
|
||||
key,
|
||||
rawBuffer,
|
||||
);
|
||||
|
||||
return Promise.resolve(new Blob([decrypted]));
|
||||
} catch (e) {
|
||||
console.error("encounter error while keys and decrypt operations");
|
||||
console.error(e);
|
||||
return Promise.resolve(new Blob([decrypted]));
|
||||
} catch (e) {
|
||||
console.error("encounter error while keys and decrypt operations");
|
||||
console.error(e);
|
||||
|
||||
throw e;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,48 +234,45 @@ async function download_and_decrypt_doc(
|
||||
* storage.
|
||||
*/
|
||||
async function download_doc_as_pdf(storedObject: StoredObject): Promise<Blob> {
|
||||
if (null === storedObject.currentVersion) {
|
||||
throw new Error("the stored object does not count any version");
|
||||
}
|
||||
if (null === storedObject.currentVersion) {
|
||||
throw new Error("the stored object does not count any version");
|
||||
}
|
||||
|
||||
if (storedObject.currentVersion?.type === "application/pdf") {
|
||||
return download_and_decrypt_doc(
|
||||
storedObject,
|
||||
storedObject.currentVersion,
|
||||
);
|
||||
}
|
||||
if (storedObject.currentVersion?.type === "application/pdf") {
|
||||
return download_and_decrypt_doc(storedObject, storedObject.currentVersion);
|
||||
}
|
||||
|
||||
const convertLink = build_convert_link(storedObject.uuid);
|
||||
const response = await fetch(convertLink);
|
||||
const convertLink = build_convert_link(storedObject.uuid);
|
||||
const response = await fetch(convertLink);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Could not convert the document: " + response.status);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error("Could not convert the document: " + response.status);
|
||||
}
|
||||
|
||||
return response.blob();
|
||||
return response.blob();
|
||||
}
|
||||
|
||||
async function is_object_ready(
|
||||
storedObject: StoredObject,
|
||||
storedObject: StoredObject,
|
||||
): Promise<StoredObjectStatusChange> {
|
||||
const new_status_response = await window.fetch(
|
||||
`/api/1.0/doc-store/stored-object/${storedObject.uuid}/is-ready`,
|
||||
);
|
||||
const new_status_response = await window.fetch(
|
||||
`/api/1.0/doc-store/stored-object/${storedObject.uuid}/is-ready`,
|
||||
);
|
||||
|
||||
if (!new_status_response.ok) {
|
||||
throw new Error("could not fetch the new status");
|
||||
}
|
||||
if (!new_status_response.ok) {
|
||||
throw new Error("could not fetch the new status");
|
||||
}
|
||||
|
||||
return await new_status_response.json();
|
||||
return await new_status_response.json();
|
||||
}
|
||||
|
||||
export {
|
||||
build_convert_link,
|
||||
build_wopi_editor_link,
|
||||
download_and_decrypt_doc,
|
||||
download_doc,
|
||||
download_doc_as_pdf,
|
||||
is_extension_editable,
|
||||
is_extension_viewable,
|
||||
is_object_ready,
|
||||
build_convert_link,
|
||||
build_wopi_editor_link,
|
||||
download_and_decrypt_doc,
|
||||
download_doc,
|
||||
download_doc_as_pdf,
|
||||
is_extension_editable,
|
||||
is_extension_viewable,
|
||||
is_object_ready,
|
||||
};
|
||||
|
||||
@@ -613,4 +613,14 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current object is an instance of User.
|
||||
*
|
||||
* @return bool returns true if the current object is an instance of User, false otherwise
|
||||
*/
|
||||
public function isUser(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff
|
||||
|
||||
/**
|
||||
* Create a CSV List for the export.
|
||||
*/
|
||||
|
||||
@@ -76,6 +76,24 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
|
||||
->formatOutOfCountryCallingNumber($phoneNumber, $this->config['default_carrier_code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NumberParseException
|
||||
*/
|
||||
public function parse(string $phoneNumber): PhoneNumber
|
||||
{
|
||||
$sanitizedPhoneNumber = $phoneNumber;
|
||||
|
||||
if (str_starts_with($sanitizedPhoneNumber, '00')) {
|
||||
$sanitizedPhoneNumber = '+'.substr($sanitizedPhoneNumber, 2, null);
|
||||
}
|
||||
|
||||
if (!str_starts_with($sanitizedPhoneNumber, '+') && !str_starts_with($sanitizedPhoneNumber, '0')) {
|
||||
$sanitizedPhoneNumber = '+'.$sanitizedPhoneNumber;
|
||||
}
|
||||
|
||||
return $this->phoneNumberUtil->parse($sanitizedPhoneNumber, $this->config['default_carrier_code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get type (mobile, landline, ...) for phone number.
|
||||
*/
|
||||
|
||||
@@ -13,15 +13,15 @@
|
||||
*
|
||||
*/
|
||||
export const dateToISO = (date: Date | null): string | null => {
|
||||
if (null === date) {
|
||||
return null;
|
||||
}
|
||||
if (null === date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
date.getFullYear(),
|
||||
(date.getMonth() + 1).toString().padStart(2, "0"),
|
||||
date.getDate().toString().padStart(2, "0"),
|
||||
].join("-");
|
||||
return [
|
||||
date.getFullYear(),
|
||||
(date.getMonth() + 1).toString().padStart(2, "0"),
|
||||
date.getDate().toString().padStart(2, "0"),
|
||||
].join("-");
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -30,16 +30,16 @@ export const dateToISO = (date: Date | null): string | null => {
|
||||
* **Experimental**
|
||||
*/
|
||||
export const ISOToDate = (str: string | null): Date | null => {
|
||||
if (null === str) {
|
||||
return null;
|
||||
}
|
||||
if ("" === str.trim()) {
|
||||
return null;
|
||||
}
|
||||
if (null === str) {
|
||||
return null;
|
||||
}
|
||||
if ("" === str.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [year, month, day] = str.split("-").map((p) => parseInt(p));
|
||||
const [year, month, day] = str.split("-").map((p) => parseInt(p));
|
||||
|
||||
return new Date(year, month - 1, day, 0, 0, 0, 0);
|
||||
return new Date(year, month - 1, day, 0, 0, 0, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -47,21 +47,19 @@ export const ISOToDate = (str: string | null): Date | null => {
|
||||
*
|
||||
*/
|
||||
export const ISOToDatetime = (str: string | null): Date | null => {
|
||||
if (null === str) {
|
||||
return null;
|
||||
}
|
||||
if (null === str) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [cal, times] = str.split("T"),
|
||||
[year, month, date] = cal.split("-").map((s) => parseInt(s)),
|
||||
[time, timezone] = times.split(times.charAt(8)),
|
||||
[hours, minutes, seconds] = time.split(":").map((s) => parseInt(s));
|
||||
if ("0000" === timezone) {
|
||||
return new Date(
|
||||
Date.UTC(year, month - 1, date, hours, minutes, seconds),
|
||||
);
|
||||
}
|
||||
const [cal, times] = str.split("T"),
|
||||
[year, month, date] = cal.split("-").map((s) => parseInt(s)),
|
||||
[time, timezone] = times.split(times.charAt(8)),
|
||||
[hours, minutes, seconds] = time.split(":").map((s) => parseInt(s));
|
||||
if ("0000" === timezone) {
|
||||
return new Date(Date.UTC(year, month - 1, date, hours, minutes, seconds));
|
||||
}
|
||||
|
||||
return new Date(year, month - 1, date, hours, minutes, seconds);
|
||||
return new Date(year, month - 1, date, hours, minutes, seconds);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -69,96 +67,94 @@ export const ISOToDatetime = (str: string | null): Date | null => {
|
||||
*
|
||||
*/
|
||||
export const datetimeToISO = (date: Date): string => {
|
||||
let cal, time, offset;
|
||||
cal = [
|
||||
date.getFullYear(),
|
||||
(date.getMonth() + 1).toString().padStart(2, "0"),
|
||||
date.getDate().toString().padStart(2, "0"),
|
||||
].join("-");
|
||||
let cal, time, offset;
|
||||
cal = [
|
||||
date.getFullYear(),
|
||||
(date.getMonth() + 1).toString().padStart(2, "0"),
|
||||
date.getDate().toString().padStart(2, "0"),
|
||||
].join("-");
|
||||
|
||||
time = [
|
||||
date.getHours().toString().padStart(2, "0"),
|
||||
date.getMinutes().toString().padStart(2, "0"),
|
||||
date.getSeconds().toString().padStart(2, "0"),
|
||||
].join(":");
|
||||
time = [
|
||||
date.getHours().toString().padStart(2, "0"),
|
||||
date.getMinutes().toString().padStart(2, "0"),
|
||||
date.getSeconds().toString().padStart(2, "0"),
|
||||
].join(":");
|
||||
|
||||
offset = [
|
||||
date.getTimezoneOffset() <= 0 ? "+" : "-",
|
||||
Math.abs(Math.floor(date.getTimezoneOffset() / 60))
|
||||
.toString()
|
||||
.padStart(2, "0"),
|
||||
":",
|
||||
Math.abs(date.getTimezoneOffset() % 60)
|
||||
.toString()
|
||||
.padStart(2, "0"),
|
||||
].join("");
|
||||
offset = [
|
||||
date.getTimezoneOffset() <= 0 ? "+" : "-",
|
||||
Math.abs(Math.floor(date.getTimezoneOffset() / 60))
|
||||
.toString()
|
||||
.padStart(2, "0"),
|
||||
":",
|
||||
Math.abs(date.getTimezoneOffset() % 60)
|
||||
.toString()
|
||||
.padStart(2, "0"),
|
||||
].join("");
|
||||
|
||||
const x = cal + "T" + time + offset;
|
||||
const x = cal + "T" + time + offset;
|
||||
|
||||
return x;
|
||||
return x;
|
||||
};
|
||||
|
||||
export const intervalDaysToISO = (days: number | string | null): string => {
|
||||
if (null === days) {
|
||||
return "P0D";
|
||||
}
|
||||
if (null === days) {
|
||||
return "P0D";
|
||||
}
|
||||
|
||||
return `P${days}D`;
|
||||
return `P${days}D`;
|
||||
};
|
||||
|
||||
export const intervalISOToDays = (str: string | null): number | null => {
|
||||
if (null === str) {
|
||||
return null;
|
||||
}
|
||||
if (null === str) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ("" === str.trim()) {
|
||||
return null;
|
||||
}
|
||||
if ("" === str.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let days = 0;
|
||||
let isDate = true;
|
||||
let vstring = "";
|
||||
for (let i = 0; i < str.length; i = i + 1) {
|
||||
if (!isDate) {
|
||||
continue;
|
||||
}
|
||||
switch (str.charAt(i)) {
|
||||
case "P":
|
||||
isDate = true;
|
||||
break;
|
||||
case "T":
|
||||
isDate = false;
|
||||
break;
|
||||
case "0":
|
||||
case "1":
|
||||
case "2":
|
||||
case "3":
|
||||
case "4":
|
||||
case "5":
|
||||
case "6":
|
||||
case "7":
|
||||
case "8":
|
||||
case "9":
|
||||
vstring = vstring + str.charAt(i);
|
||||
break;
|
||||
case "Y":
|
||||
days = days + Number.parseInt(vstring) * 365;
|
||||
vstring = "";
|
||||
break;
|
||||
case "M":
|
||||
days = days + Number.parseInt(vstring) * 30;
|
||||
vstring = "";
|
||||
break;
|
||||
case "D":
|
||||
days = days + Number.parseInt(vstring);
|
||||
vstring = "";
|
||||
break;
|
||||
default:
|
||||
throw Error(
|
||||
"this character should not appears: " + str.charAt(i),
|
||||
);
|
||||
}
|
||||
let days = 0;
|
||||
let isDate = true;
|
||||
let vstring = "";
|
||||
for (let i = 0; i < str.length; i = i + 1) {
|
||||
if (!isDate) {
|
||||
continue;
|
||||
}
|
||||
switch (str.charAt(i)) {
|
||||
case "P":
|
||||
isDate = true;
|
||||
break;
|
||||
case "T":
|
||||
isDate = false;
|
||||
break;
|
||||
case "0":
|
||||
case "1":
|
||||
case "2":
|
||||
case "3":
|
||||
case "4":
|
||||
case "5":
|
||||
case "6":
|
||||
case "7":
|
||||
case "8":
|
||||
case "9":
|
||||
vstring = vstring + str.charAt(i);
|
||||
break;
|
||||
case "Y":
|
||||
days = days + Number.parseInt(vstring) * 365;
|
||||
vstring = "";
|
||||
break;
|
||||
case "M":
|
||||
days = days + Number.parseInt(vstring) * 30;
|
||||
vstring = "";
|
||||
break;
|
||||
case "D":
|
||||
days = days + Number.parseInt(vstring);
|
||||
vstring = "";
|
||||
break;
|
||||
default:
|
||||
throw Error("this character should not appears: " + str.charAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
return days;
|
||||
return days;
|
||||
};
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
import {
|
||||
Address,
|
||||
GeographicalUnitLayer,
|
||||
SimpleGeographicalUnit,
|
||||
Address,
|
||||
GeographicalUnitLayer,
|
||||
SimpleGeographicalUnit,
|
||||
} from "../../types";
|
||||
import { fetchResults, makeFetch } from "./apiMethods";
|
||||
|
||||
export const getAddressById = async (address_id: number): Promise<Address> => {
|
||||
const url = `/api/1.0/main/address/${address_id}.json`;
|
||||
const url = `/api/1.0/main/address/${address_id}.json`;
|
||||
|
||||
const response = await fetch(url);
|
||||
const response = await fetch(url);
|
||||
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
throw Error("Error with request resource response");
|
||||
throw Error("Error with request resource response");
|
||||
};
|
||||
|
||||
export const getGeographicalUnitsByAddress = async (
|
||||
address: Address,
|
||||
address: Address,
|
||||
): Promise<SimpleGeographicalUnit[]> => {
|
||||
return fetchResults<SimpleGeographicalUnit>(
|
||||
`/api/1.0/main/geographical-unit/by-address/${address.address_id}.json`,
|
||||
);
|
||||
return fetchResults<SimpleGeographicalUnit>(
|
||||
`/api/1.0/main/geographical-unit/by-address/${address.address_id}.json`,
|
||||
);
|
||||
};
|
||||
|
||||
export const getAllGeographicalUnitLayers = async (): Promise<
|
||||
GeographicalUnitLayer[]
|
||||
GeographicalUnitLayer[]
|
||||
> => {
|
||||
return fetchResults<GeographicalUnitLayer>(
|
||||
`/api/1.0/main/geographical-unit-layer.json`,
|
||||
);
|
||||
return fetchResults<GeographicalUnitLayer>(
|
||||
`/api/1.0/main/geographical-unit-layer.json`,
|
||||
);
|
||||
};
|
||||
|
||||
export const syncAddressWithReference = async (
|
||||
address: Address,
|
||||
address: Address,
|
||||
): Promise<Address> => {
|
||||
return makeFetch<null, Address>(
|
||||
"POST",
|
||||
`/api/1.0/main/address/reference-match/${address.address_id}/sync-with-reference`,
|
||||
);
|
||||
return makeFetch<null, Address>(
|
||||
"POST",
|
||||
`/api/1.0/main/address/reference-match/${address.address_id}/sync-with-reference`,
|
||||
);
|
||||
};
|
||||
|
||||
export const markAddressReviewed = async (
|
||||
address: Address,
|
||||
address: Address,
|
||||
): Promise<Address> => {
|
||||
return makeFetch<null, Address>(
|
||||
"POST",
|
||||
`/api/1.0/main/address/reference-match/${address.address_id}/set/reviewed`,
|
||||
);
|
||||
return makeFetch<null, Address>(
|
||||
"POST",
|
||||
`/api/1.0/main/address/reference-match/${address.address_id}/set/reviewed`,
|
||||
);
|
||||
};
|
||||
|
||||
export const markAddressToReview = async (
|
||||
address: Address,
|
||||
address: Address,
|
||||
): Promise<Address> => {
|
||||
return makeFetch<null, Address>(
|
||||
"POST",
|
||||
`/api/1.0/main/address/reference-match/${address.address_id}/set/to_review`,
|
||||
);
|
||||
return makeFetch<null, Address>(
|
||||
"POST",
|
||||
`/api/1.0/main/address/reference-match/${address.address_id}/set/to_review`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,57 +6,57 @@ export type fetchOption = Record<string, boolean | string | number | null>;
|
||||
export type Params = Record<string, number | string>;
|
||||
|
||||
export interface PaginationResponse<T> {
|
||||
pagination: {
|
||||
more: boolean;
|
||||
items_per_page: number;
|
||||
};
|
||||
results: T[];
|
||||
count: number;
|
||||
pagination: {
|
||||
more: boolean;
|
||||
items_per_page: number;
|
||||
};
|
||||
results: T[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export type FetchParams = Record<string, string | number | null>;
|
||||
|
||||
export interface TransportExceptionInterface {
|
||||
name: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ValidationExceptionInterface
|
||||
extends TransportExceptionInterface {
|
||||
name: "ValidationException";
|
||||
error: object;
|
||||
violations: string[];
|
||||
titles: string[];
|
||||
propertyPaths: string[];
|
||||
extends TransportExceptionInterface {
|
||||
name: "ValidationException";
|
||||
error: object;
|
||||
violations: string[];
|
||||
titles: string[];
|
||||
propertyPaths: string[];
|
||||
}
|
||||
|
||||
export interface ValidationErrorResponse extends TransportExceptionInterface {
|
||||
violations: {
|
||||
title: string;
|
||||
propertyPath: string;
|
||||
}[];
|
||||
violations: {
|
||||
title: string;
|
||||
propertyPath: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface AccessExceptionInterface extends TransportExceptionInterface {
|
||||
name: "AccessException";
|
||||
violations: string[];
|
||||
name: "AccessException";
|
||||
violations: string[];
|
||||
}
|
||||
|
||||
export interface NotFoundExceptionInterface
|
||||
extends TransportExceptionInterface {
|
||||
name: "NotFoundException";
|
||||
extends TransportExceptionInterface {
|
||||
name: "NotFoundException";
|
||||
}
|
||||
|
||||
export interface ServerExceptionInterface extends TransportExceptionInterface {
|
||||
name: "ServerException";
|
||||
message: string;
|
||||
code: number;
|
||||
body: string;
|
||||
name: "ServerException";
|
||||
message: string;
|
||||
code: number;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface ConflictHttpExceptionInterface
|
||||
extends TransportExceptionInterface {
|
||||
name: "ConflictHttpException";
|
||||
violations: string[];
|
||||
extends TransportExceptionInterface {
|
||||
name: "ConflictHttpException";
|
||||
violations: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,223 +66,223 @@ export interface ConflictHttpExceptionInterface
|
||||
* and use of the @link{fetchResults} method.
|
||||
*/
|
||||
export const makeFetch = <Input, Output>(
|
||||
method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE",
|
||||
url: string,
|
||||
body?: body | Input | null,
|
||||
options?: FetchParams,
|
||||
method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE",
|
||||
url: string,
|
||||
body?: body | Input | null,
|
||||
options?: FetchParams,
|
||||
): Promise<Output> => {
|
||||
let opts = {
|
||||
method: method,
|
||||
headers: {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
},
|
||||
let opts = {
|
||||
method: method,
|
||||
headers: {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
},
|
||||
};
|
||||
|
||||
if (body !== null && typeof body !== "undefined") {
|
||||
Object.assign(opts, { body: JSON.stringify(body) });
|
||||
}
|
||||
|
||||
if (typeof options !== "undefined") {
|
||||
opts = Object.assign(opts, options);
|
||||
}
|
||||
return fetch(url, opts).then((response) => {
|
||||
if (response.status === 204) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (response.status === 422) {
|
||||
return response.json().then((response) => {
|
||||
throw ValidationException(response);
|
||||
});
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
throw AccessException(response);
|
||||
}
|
||||
|
||||
if (response.status === 409) {
|
||||
throw ConflictHttpException(response);
|
||||
}
|
||||
|
||||
throw {
|
||||
name: "Exception",
|
||||
sta: response.status,
|
||||
txt: response.statusText,
|
||||
err: new Error(),
|
||||
violations: response.body,
|
||||
};
|
||||
|
||||
if (body !== null && typeof body !== "undefined") {
|
||||
Object.assign(opts, { body: JSON.stringify(body) });
|
||||
}
|
||||
|
||||
if (typeof options !== "undefined") {
|
||||
opts = Object.assign(opts, options);
|
||||
}
|
||||
return fetch(url, opts).then((response) => {
|
||||
if (response.status === 204) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (response.status === 422) {
|
||||
return response.json().then((response) => {
|
||||
throw ValidationException(response);
|
||||
});
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
throw AccessException(response);
|
||||
}
|
||||
|
||||
if (response.status === 409) {
|
||||
throw ConflictHttpException(response);
|
||||
}
|
||||
|
||||
throw {
|
||||
name: "Exception",
|
||||
sta: response.status,
|
||||
txt: response.statusText,
|
||||
err: new Error(),
|
||||
violations: response.body,
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch results with certain parameters
|
||||
*/
|
||||
function _fetchAction<T>(
|
||||
page: number,
|
||||
uri: string,
|
||||
params?: FetchParams,
|
||||
page: number,
|
||||
uri: string,
|
||||
params?: FetchParams,
|
||||
): Promise<PaginationResponse<T>> {
|
||||
const item_per_page = 50;
|
||||
const item_per_page = 50;
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.append("item_per_page", item_per_page.toString());
|
||||
searchParams.append("page", page.toString());
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.append("item_per_page", item_per_page.toString());
|
||||
searchParams.append("page", page.toString());
|
||||
|
||||
if (params !== undefined) {
|
||||
Object.keys(params).forEach((key) => {
|
||||
const v = params[key];
|
||||
if (typeof v === "string") {
|
||||
searchParams.append(key, v);
|
||||
} else if (typeof v === "number") {
|
||||
searchParams.append(key, v.toString());
|
||||
} else if (v === null) {
|
||||
searchParams.append(key, "");
|
||||
}
|
||||
if (params !== undefined) {
|
||||
Object.keys(params).forEach((key) => {
|
||||
const v = params[key];
|
||||
if (typeof v === "string") {
|
||||
searchParams.append(key, v);
|
||||
} else if (typeof v === "number") {
|
||||
searchParams.append(key, v.toString());
|
||||
} else if (v === null) {
|
||||
searchParams.append(key, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const url = uri + "?" + searchParams.toString();
|
||||
|
||||
return fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (response.status === 404) {
|
||||
throw NotFoundException(response);
|
||||
}
|
||||
|
||||
if (response.status === 422) {
|
||||
return response.json().then((response) => {
|
||||
throw ValidationException(response);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const url = uri + "?" + searchParams.toString();
|
||||
if (response.status === 403) {
|
||||
throw AccessException(response);
|
||||
}
|
||||
|
||||
return fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
},
|
||||
if (response.status >= 500) {
|
||||
return response.text().then((body) => {
|
||||
throw ServerException(response.status, body);
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error("other network error");
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (response.status === 404) {
|
||||
throw NotFoundException(response);
|
||||
}
|
||||
|
||||
if (response.status === 422) {
|
||||
return response.json().then((response) => {
|
||||
throw ValidationException(response);
|
||||
});
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
throw AccessException(response);
|
||||
}
|
||||
|
||||
if (response.status >= 500) {
|
||||
return response.text().then((body) => {
|
||||
throw ServerException(response.status, body);
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error("other network error");
|
||||
})
|
||||
.catch(
|
||||
(
|
||||
reason:
|
||||
| NotFoundExceptionInterface
|
||||
| ServerExceptionInterface
|
||||
| ValidationExceptionInterface
|
||||
| TransportExceptionInterface,
|
||||
) => {
|
||||
console.error(reason);
|
||||
throw reason;
|
||||
},
|
||||
);
|
||||
.catch(
|
||||
(
|
||||
reason:
|
||||
| NotFoundExceptionInterface
|
||||
| ServerExceptionInterface
|
||||
| ValidationExceptionInterface
|
||||
| TransportExceptionInterface,
|
||||
) => {
|
||||
console.error(reason);
|
||||
throw reason;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export const fetchResults = async <T>(
|
||||
uri: string,
|
||||
params?: FetchParams,
|
||||
uri: string,
|
||||
params?: FetchParams,
|
||||
): Promise<T[]> => {
|
||||
const promises: Promise<T[]>[] = [];
|
||||
let page = 1;
|
||||
const firstData: PaginationResponse<T> = (await _fetchAction(
|
||||
page,
|
||||
uri,
|
||||
params,
|
||||
)) as PaginationResponse<T>;
|
||||
const promises: Promise<T[]>[] = [];
|
||||
let page = 1;
|
||||
const firstData: PaginationResponse<T> = (await _fetchAction(
|
||||
page,
|
||||
uri,
|
||||
params,
|
||||
)) as PaginationResponse<T>;
|
||||
|
||||
promises.push(Promise.resolve(firstData.results));
|
||||
promises.push(Promise.resolve(firstData.results));
|
||||
|
||||
if (firstData.pagination.more) {
|
||||
do {
|
||||
page = ++page;
|
||||
promises.push(
|
||||
_fetchAction<T>(page, uri, params).then((r) =>
|
||||
Promise.resolve(r.results),
|
||||
),
|
||||
);
|
||||
} while (page * firstData.pagination.items_per_page < firstData.count);
|
||||
}
|
||||
if (firstData.pagination.more) {
|
||||
do {
|
||||
page = ++page;
|
||||
promises.push(
|
||||
_fetchAction<T>(page, uri, params).then((r) =>
|
||||
Promise.resolve(r.results),
|
||||
),
|
||||
);
|
||||
} while (page * firstData.pagination.items_per_page < firstData.count);
|
||||
}
|
||||
|
||||
return Promise.all(promises).then((values) => values.flat());
|
||||
return Promise.all(promises).then((values) => values.flat());
|
||||
};
|
||||
|
||||
export const fetchScopes = (): Promise<Scope[]> => {
|
||||
return fetchResults("/api/1.0/main/scope.json");
|
||||
return fetchResults("/api/1.0/main/scope.json");
|
||||
};
|
||||
|
||||
/**
|
||||
* Error objects to be thrown
|
||||
*/
|
||||
const ValidationException = (
|
||||
response: ValidationErrorResponse,
|
||||
response: ValidationErrorResponse,
|
||||
): ValidationExceptionInterface => {
|
||||
const error = {} as ValidationExceptionInterface;
|
||||
error.name = "ValidationException";
|
||||
error.violations = response.violations.map(
|
||||
(violation) => `${violation.title}: ${violation.propertyPath}`,
|
||||
);
|
||||
error.titles = response.violations.map((violation) => violation.title);
|
||||
error.propertyPaths = response.violations.map(
|
||||
(violation) => violation.propertyPath,
|
||||
);
|
||||
return error;
|
||||
const error = {} as ValidationExceptionInterface;
|
||||
error.name = "ValidationException";
|
||||
error.violations = response.violations.map(
|
||||
(violation) => `${violation.title}: ${violation.propertyPath}`,
|
||||
);
|
||||
error.titles = response.violations.map((violation) => violation.title);
|
||||
error.propertyPaths = response.violations.map(
|
||||
(violation) => violation.propertyPath,
|
||||
);
|
||||
return error;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const AccessException = (response: Response): AccessExceptionInterface => {
|
||||
const error = {} as AccessExceptionInterface;
|
||||
error.name = "AccessException";
|
||||
error.violations = ["You are not allowed to perform this action"];
|
||||
const error = {} as AccessExceptionInterface;
|
||||
error.name = "AccessException";
|
||||
error.violations = ["You are not allowed to perform this action"];
|
||||
|
||||
return error;
|
||||
return error;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const NotFoundException = (response: Response): NotFoundExceptionInterface => {
|
||||
const error = {} as NotFoundExceptionInterface;
|
||||
error.name = "NotFoundException";
|
||||
const error = {} as NotFoundExceptionInterface;
|
||||
error.name = "NotFoundException";
|
||||
|
||||
return error;
|
||||
return error;
|
||||
};
|
||||
|
||||
const ServerException = (
|
||||
code: number,
|
||||
body: string,
|
||||
code: number,
|
||||
body: string,
|
||||
): ServerExceptionInterface => {
|
||||
const error = {} as ServerExceptionInterface;
|
||||
error.name = "ServerException";
|
||||
error.code = code;
|
||||
error.body = body;
|
||||
const error = {} as ServerExceptionInterface;
|
||||
error.name = "ServerException";
|
||||
error.code = code;
|
||||
error.body = body;
|
||||
|
||||
return error;
|
||||
return error;
|
||||
};
|
||||
|
||||
const ConflictHttpException = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
response: Response,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
response: Response,
|
||||
): ConflictHttpExceptionInterface => {
|
||||
const error = {} as ConflictHttpExceptionInterface;
|
||||
const error = {} as ConflictHttpExceptionInterface;
|
||||
|
||||
error.name = "ConflictHttpException";
|
||||
error.violations = [
|
||||
"Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again",
|
||||
];
|
||||
error.name = "ConflictHttpException";
|
||||
error.violations = [
|
||||
"Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again",
|
||||
];
|
||||
|
||||
return error;
|
||||
return error;
|
||||
};
|
||||
|
||||
@@ -2,17 +2,17 @@ import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
import { ExportGeneration } from "ChillMainAssets/types";
|
||||
|
||||
export const fetchExportGenerationStatus = async (
|
||||
exportGenerationId: string,
|
||||
exportGenerationId: string,
|
||||
): Promise<ExportGeneration> =>
|
||||
makeFetch(
|
||||
"GET",
|
||||
`/api/1.0/main/export-generation/${exportGenerationId}/object`,
|
||||
);
|
||||
makeFetch(
|
||||
"GET",
|
||||
`/api/1.0/main/export-generation/${exportGenerationId}/object`,
|
||||
);
|
||||
|
||||
export const generateFromSavedExport = async (
|
||||
savedExportUuid: string,
|
||||
savedExportUuid: string,
|
||||
): Promise<ExportGeneration> =>
|
||||
makeFetch(
|
||||
"POST",
|
||||
`/api/1.0/main/export/export-generation/create-from-saved-export/${savedExportUuid}`,
|
||||
);
|
||||
makeFetch(
|
||||
"POST",
|
||||
`/api/1.0/main/export/export-generation/create-from-saved-export/${savedExportUuid}`,
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { fetchResults } from "./apiMethods";
|
||||
import { Location, LocationType } from "../../types";
|
||||
|
||||
export const getLocations = (): Promise<Location[]> =>
|
||||
fetchResults("/api/1.0/main/location.json");
|
||||
fetchResults("/api/1.0/main/location.json");
|
||||
|
||||
export const getLocationTypes = (): Promise<LocationType[]> =>
|
||||
fetchResults("/api/1.0/main/location-type.json");
|
||||
fetchResults("/api/1.0/main/location-type.json");
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export function buildReturnPath(location: Location): string {
|
||||
return location.pathname + location.search;
|
||||
return location.pathname + location.search;
|
||||
}
|
||||
|
||||
@@ -2,23 +2,23 @@ import { User } from "../../types";
|
||||
import { makeFetch } from "./apiMethods";
|
||||
|
||||
export const whoami = (): Promise<User> => {
|
||||
const url = `/api/1.0/main/whoami.json`;
|
||||
return fetch(url).then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw {
|
||||
msg: "Error while getting whoami.",
|
||||
sta: response.status,
|
||||
txt: response.statusText,
|
||||
err: new Error(),
|
||||
body: response.body,
|
||||
};
|
||||
});
|
||||
const url = `/api/1.0/main/whoami.json`;
|
||||
return fetch(url).then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw {
|
||||
msg: "Error while getting whoami.",
|
||||
sta: response.status,
|
||||
txt: response.statusText,
|
||||
err: new Error(),
|
||||
body: response.body,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const whereami = (): Promise<Location | null> => {
|
||||
const url = `/api/1.0/main/user-current-location.json`;
|
||||
const url = `/api/1.0/main/user-current-location.json`;
|
||||
|
||||
return makeFetch<null, Location | null>("GET", url);
|
||||
return makeFetch<null, Location | null>("GET", url);
|
||||
};
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
const buildLinkCreate = function (
|
||||
relatedEntityClass: string,
|
||||
relatedEntityId: number,
|
||||
to: number | null,
|
||||
returnPath: string | null,
|
||||
relatedEntityClass: string,
|
||||
relatedEntityId: number,
|
||||
to: number | null,
|
||||
returnPath: string | null,
|
||||
): string {
|
||||
const params = new URLSearchParams();
|
||||
params.append("entityClass", relatedEntityClass);
|
||||
params.append("entityId", relatedEntityId.toString());
|
||||
const params = new URLSearchParams();
|
||||
params.append("entityClass", relatedEntityClass);
|
||||
params.append("entityId", relatedEntityId.toString());
|
||||
|
||||
if (null !== to) {
|
||||
params.append("tos[0]", to.toString());
|
||||
}
|
||||
if (null !== to) {
|
||||
params.append("tos[0]", to.toString());
|
||||
}
|
||||
|
||||
if (null !== returnPath) {
|
||||
params.append("returnPath", returnPath);
|
||||
}
|
||||
if (null !== returnPath) {
|
||||
params.append("returnPath", returnPath);
|
||||
}
|
||||
|
||||
return `/fr/notification/create?${params.toString()}`;
|
||||
return `/fr/notification/create?${params.toString()}`;
|
||||
};
|
||||
|
||||
export { buildLinkCreate };
|
||||
|
||||
@@ -10,17 +10,17 @@
|
||||
* @throws {Error} If the related entity ID is undefined.
|
||||
*/
|
||||
export const buildLinkCreate = (
|
||||
workflowName: string,
|
||||
relatedEntityClass: string,
|
||||
relatedEntityId: number | undefined,
|
||||
workflowName: string,
|
||||
relatedEntityClass: string,
|
||||
relatedEntityId: number | undefined,
|
||||
): string => {
|
||||
if (typeof relatedEntityId === "undefined") {
|
||||
throw new Error("the related entity id is not set");
|
||||
}
|
||||
const params = new URLSearchParams();
|
||||
params.set("entityClass", relatedEntityClass);
|
||||
params.set("entityId", relatedEntityId.toString(10));
|
||||
params.set("workflow", workflowName);
|
||||
if (typeof relatedEntityId === "undefined") {
|
||||
throw new Error("the related entity id is not set");
|
||||
}
|
||||
const params = new URLSearchParams();
|
||||
params.set("entityClass", relatedEntityClass);
|
||||
params.set("entityId", relatedEntityId.toString(10));
|
||||
params.set("workflow", workflowName);
|
||||
|
||||
return `/fr/main/workflow/create?` + params.toString();
|
||||
return `/fr/main/workflow/create?` + params.toString();
|
||||
};
|
||||
|
||||
@@ -9,33 +9,33 @@ import { TranslatableString } from "ChillMainAssets/types";
|
||||
* @returns The localized string or null if no translation is available
|
||||
*/
|
||||
export function localizeString(
|
||||
translatableString: TranslatableString | null | undefined,
|
||||
locale?: string,
|
||||
translatableString: TranslatableString | null | undefined,
|
||||
locale?: string,
|
||||
): string {
|
||||
if (!translatableString || Object.keys(translatableString).length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const currentLocale = locale || navigator.language.split("-")[0] || "fr";
|
||||
|
||||
if (translatableString[currentLocale]) {
|
||||
return translatableString[currentLocale];
|
||||
}
|
||||
|
||||
// Define fallback locales
|
||||
const fallbackLocales: string[] = ["fr", "en"];
|
||||
|
||||
for (const fallbackLocale of fallbackLocales) {
|
||||
if (translatableString[fallbackLocale]) {
|
||||
return translatableString[fallbackLocale];
|
||||
}
|
||||
}
|
||||
|
||||
// No fallback translation found, use the first available
|
||||
const availableLocales = Object.keys(translatableString);
|
||||
if (availableLocales.length > 0) {
|
||||
return translatableString[availableLocales[0]];
|
||||
}
|
||||
|
||||
if (!translatableString || Object.keys(translatableString).length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const currentLocale = locale || navigator.language.split("-")[0] || "fr";
|
||||
|
||||
if (translatableString[currentLocale]) {
|
||||
return translatableString[currentLocale];
|
||||
}
|
||||
|
||||
// Define fallback locales
|
||||
const fallbackLocales: string[] = ["fr", "en"];
|
||||
|
||||
for (const fallbackLocale of fallbackLocales) {
|
||||
if (translatableString[fallbackLocale]) {
|
||||
return translatableString[fallbackLocale];
|
||||
}
|
||||
}
|
||||
|
||||
// No fallback translation found, use the first available
|
||||
const availableLocales = Object.keys(translatableString);
|
||||
if (availableLocales.length > 0) {
|
||||
return translatableString[availableLocales[0]];
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -3,20 +3,20 @@ import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/gener
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
|
||||
export const find_attachments_by_workflow = async (
|
||||
workflowId: number,
|
||||
workflowId: number,
|
||||
): Promise<WorkflowAttachment[]> =>
|
||||
makeFetch("GET", `/api/1.0/main/workflow/${workflowId}/attachment`);
|
||||
makeFetch("GET", `/api/1.0/main/workflow/${workflowId}/attachment`);
|
||||
|
||||
export const create_attachment = async (
|
||||
workflowId: number,
|
||||
genericDoc: GenericDocForAccompanyingPeriod,
|
||||
workflowId: number,
|
||||
genericDoc: GenericDocForAccompanyingPeriod,
|
||||
): Promise<WorkflowAttachment> =>
|
||||
makeFetch("POST", `/api/1.0/main/workflow/${workflowId}/attachment`, {
|
||||
relatedGenericDocKey: genericDoc.key,
|
||||
relatedGenericDocIdentifiers: genericDoc.identifiers,
|
||||
});
|
||||
makeFetch("POST", `/api/1.0/main/workflow/${workflowId}/attachment`, {
|
||||
relatedGenericDocKey: genericDoc.key,
|
||||
relatedGenericDocIdentifiers: genericDoc.identifiers,
|
||||
});
|
||||
|
||||
export const delete_attachment = async (
|
||||
attachment: WorkflowAttachment,
|
||||
attachment: WorkflowAttachment,
|
||||
): Promise<void> =>
|
||||
makeFetch("DELETE", `/api/1.0/main/workflow/attachment/${attachment.id}`);
|
||||
makeFetch("DELETE", `/api/1.0/main/workflow/attachment/${attachment.id}`);
|
||||
|
||||
@@ -7,43 +7,43 @@ import { Address } from "../../types";
|
||||
const i18n = _createI18n({});
|
||||
|
||||
document
|
||||
.querySelectorAll<HTMLSpanElement>("span[data-address-details]")
|
||||
.forEach((el) => {
|
||||
const dataset = el.dataset as {
|
||||
addressId: string;
|
||||
addressRefStatus: string;
|
||||
.querySelectorAll<HTMLSpanElement>("span[data-address-details]")
|
||||
.forEach((el) => {
|
||||
const dataset = el.dataset as {
|
||||
addressId: string;
|
||||
addressRefStatus: string;
|
||||
};
|
||||
|
||||
const app = createApp({
|
||||
components: { AddressDetailsButton },
|
||||
data() {
|
||||
return {
|
||||
addressId: Number.parseInt(dataset.addressId),
|
||||
addressRefStatus: dataset.addressRefStatus,
|
||||
};
|
||||
|
||||
const app = createApp({
|
||||
components: { AddressDetailsButton },
|
||||
data() {
|
||||
return {
|
||||
addressId: Number.parseInt(dataset.addressId),
|
||||
addressRefStatus: dataset.addressRefStatus,
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<address-details-button :address_id="addressId" :address_ref_status="addressRefStatus" @update-address="onUpdateAddress"></address-details-button>',
|
||||
methods: {
|
||||
onUpdateAddress: (address: Address): void => {
|
||||
if (
|
||||
address.refStatus === "to_review" ||
|
||||
address.refStatus === "reviewed"
|
||||
) {
|
||||
// in this two case, the address content do not change
|
||||
return;
|
||||
}
|
||||
if (
|
||||
window.confirm(
|
||||
"L'adresse a été modifiée. Vous pouvez continuer votre travail. Cependant, pour afficher les données immédiatement, veuillez recharger la page. \n\n Voulez-vous recharger la page immédiatement ?",
|
||||
)
|
||||
) {
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.use(i18n);
|
||||
app.mount(el);
|
||||
},
|
||||
template:
|
||||
'<address-details-button :address_id="addressId" :address_ref_status="addressRefStatus" @update-address="onUpdateAddress"></address-details-button>',
|
||||
methods: {
|
||||
onUpdateAddress: (address: Address): void => {
|
||||
if (
|
||||
address.refStatus === "to_review" ||
|
||||
address.refStatus === "reviewed"
|
||||
) {
|
||||
// in this two case, the address content do not change
|
||||
return;
|
||||
}
|
||||
if (
|
||||
window.confirm(
|
||||
"L'adresse a été modifiée. Vous pouvez continuer votre travail. Cependant, pour afficher les données immédiatement, veuillez recharger la page. \n\n Voulez-vous recharger la page immédiatement ?",
|
||||
)
|
||||
) {
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.use(i18n);
|
||||
app.mount(el);
|
||||
});
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import {
|
||||
Essentials,
|
||||
Bold,
|
||||
Italic,
|
||||
Paragraph,
|
||||
Markdown,
|
||||
BlockQuote,
|
||||
Heading,
|
||||
Link,
|
||||
List,
|
||||
Emoji,
|
||||
Mention,
|
||||
Fullscreen,
|
||||
Essentials,
|
||||
Bold,
|
||||
Italic,
|
||||
Paragraph,
|
||||
Markdown,
|
||||
BlockQuote,
|
||||
Heading,
|
||||
Link,
|
||||
List,
|
||||
Emoji,
|
||||
Mention,
|
||||
Fullscreen,
|
||||
} from "ckeditor5";
|
||||
import coreTranslations from "ckeditor5/translations/fr.js";
|
||||
|
||||
@@ -19,41 +19,41 @@ import "ckeditor5/ckeditor5.css";
|
||||
import "./index.scss";
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
Essentials,
|
||||
Markdown,
|
||||
Bold,
|
||||
Italic,
|
||||
BlockQuote,
|
||||
Heading,
|
||||
Link,
|
||||
List,
|
||||
Paragraph,
|
||||
// both Emoji and Mention are required for Emoji feature
|
||||
Emoji,
|
||||
Mention,
|
||||
// to enable fullscreen
|
||||
Fullscreen,
|
||||
plugins: [
|
||||
Essentials,
|
||||
Markdown,
|
||||
Bold,
|
||||
Italic,
|
||||
BlockQuote,
|
||||
Heading,
|
||||
Link,
|
||||
List,
|
||||
Paragraph,
|
||||
// both Emoji and Mention are required for Emoji feature
|
||||
Emoji,
|
||||
Mention,
|
||||
// to enable fullscreen
|
||||
Fullscreen,
|
||||
],
|
||||
toolbar: {
|
||||
items: [
|
||||
"heading",
|
||||
"|",
|
||||
"bold",
|
||||
"italic",
|
||||
"link",
|
||||
"bulletedList",
|
||||
"numberedList",
|
||||
"blockQuote",
|
||||
"|",
|
||||
"emoji",
|
||||
"|",
|
||||
"undo",
|
||||
"redo",
|
||||
"|",
|
||||
"fullscreen",
|
||||
],
|
||||
toolbar: {
|
||||
items: [
|
||||
"heading",
|
||||
"|",
|
||||
"bold",
|
||||
"italic",
|
||||
"link",
|
||||
"bulletedList",
|
||||
"numberedList",
|
||||
"blockQuote",
|
||||
"|",
|
||||
"emoji",
|
||||
"|",
|
||||
"undo",
|
||||
"redo",
|
||||
"|",
|
||||
"fullscreen",
|
||||
],
|
||||
},
|
||||
translations: [coreTranslations],
|
||||
licenseKey: "GPL",
|
||||
},
|
||||
translations: [coreTranslations],
|
||||
licenseKey: "GPL",
|
||||
};
|
||||
|
||||
@@ -2,31 +2,31 @@ import { createApp } from "vue";
|
||||
import CommentEditor from "ChillMainAssets/vuejs/_components/CommentEditor/CommentEditor.vue";
|
||||
|
||||
const ckeditorFields: NodeListOf<HTMLTextAreaElement> =
|
||||
document.querySelectorAll("textarea[ckeditor]");
|
||||
document.querySelectorAll("textarea[ckeditor]");
|
||||
ckeditorFields.forEach((field: HTMLTextAreaElement): void => {
|
||||
const content = field.value;
|
||||
const div = document.createElement("div");
|
||||
const content = field.value;
|
||||
const div = document.createElement("div");
|
||||
|
||||
if (field.parentNode !== null) {
|
||||
field.parentNode.insertBefore(div, field);
|
||||
} else {
|
||||
throw "parent is null";
|
||||
}
|
||||
if (field.parentNode !== null) {
|
||||
field.parentNode.insertBefore(div, field);
|
||||
} else {
|
||||
throw "parent is null";
|
||||
}
|
||||
|
||||
createApp({
|
||||
components: { CommentEditor },
|
||||
template: `<comment-editor v-model="content" @update:modelValue="handleInput"></comment-editor>`,
|
||||
data() {
|
||||
return {
|
||||
content,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleInput() {
|
||||
field.value = this.content;
|
||||
},
|
||||
},
|
||||
}).mount(div);
|
||||
createApp({
|
||||
components: { CommentEditor },
|
||||
template: `<comment-editor v-model="content" @update:modelValue="handleInput"></comment-editor>`,
|
||||
data() {
|
||||
return {
|
||||
content,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleInput() {
|
||||
field.value = this.content;
|
||||
},
|
||||
},
|
||||
}).mount(div);
|
||||
|
||||
field.style.display = "none";
|
||||
field.style.display = "none";
|
||||
});
|
||||
|
||||
@@ -31,157 +31,157 @@
|
||||
import "./collection.scss";
|
||||
|
||||
declare global {
|
||||
interface GlobalEventHandlersEventMap {
|
||||
"show-hide-show": CustomEvent<{
|
||||
id: number;
|
||||
froms: HTMLElement[];
|
||||
container: HTMLElement;
|
||||
}>;
|
||||
}
|
||||
interface GlobalEventHandlersEventMap {
|
||||
"show-hide-show": CustomEvent<{
|
||||
id: number;
|
||||
froms: HTMLElement[];
|
||||
container: HTMLElement;
|
||||
}>;
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionEventPayload {
|
||||
collection: HTMLUListElement;
|
||||
entry: HTMLLIElement;
|
||||
collection: HTMLUListElement;
|
||||
entry: HTMLLIElement;
|
||||
|
||||
constructor(collection: HTMLUListElement, entry: HTMLLIElement) {
|
||||
this.collection = collection;
|
||||
this.entry = entry;
|
||||
}
|
||||
constructor(collection: HTMLUListElement, entry: HTMLLIElement) {
|
||||
this.collection = collection;
|
||||
this.entry = entry;
|
||||
}
|
||||
}
|
||||
|
||||
export const handleAdd = (button: any): void => {
|
||||
const form_name = button.dataset.collectionAddTarget,
|
||||
prototype = button.dataset.formPrototype,
|
||||
collection: HTMLUListElement | null = document.querySelector(
|
||||
'ul[data-collection-name="' + form_name + '"]',
|
||||
);
|
||||
const form_name = button.dataset.collectionAddTarget,
|
||||
prototype = button.dataset.formPrototype,
|
||||
collection: HTMLUListElement | null = document.querySelector(
|
||||
'ul[data-collection-name="' + form_name + '"]',
|
||||
);
|
||||
|
||||
if (collection === null) {
|
||||
return;
|
||||
if (collection === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const empty_explain: HTMLLIElement | null = collection.querySelector(
|
||||
"li[data-collection-empty-explain]",
|
||||
),
|
||||
entry = document.createElement("li"),
|
||||
counter = collection.querySelectorAll("li.entry").length, // Updated counter logic
|
||||
content = prototype.replace(/__name__/g, counter.toString()),
|
||||
event = new CustomEvent("collection-add-entry", {
|
||||
detail: new CollectionEventPayload(collection, entry),
|
||||
});
|
||||
|
||||
console.log(counter);
|
||||
console.log(content);
|
||||
|
||||
entry.innerHTML = content;
|
||||
entry.classList.add("entry");
|
||||
|
||||
if ("collectionRegular" in collection.dataset) {
|
||||
initializeRemove(collection, entry);
|
||||
if (empty_explain !== null) {
|
||||
empty_explain.remove();
|
||||
}
|
||||
}
|
||||
|
||||
const empty_explain: HTMLLIElement | null = collection.querySelector(
|
||||
"li[data-collection-empty-explain]",
|
||||
),
|
||||
entry = document.createElement("li"),
|
||||
counter = collection.querySelectorAll("li.entry").length, // Updated counter logic
|
||||
content = prototype.replace(/__name__/g, counter.toString()),
|
||||
event = new CustomEvent("collection-add-entry", {
|
||||
detail: new CollectionEventPayload(collection, entry),
|
||||
});
|
||||
|
||||
console.log(counter);
|
||||
console.log(content);
|
||||
|
||||
entry.innerHTML = content;
|
||||
entry.classList.add("entry");
|
||||
|
||||
if ("collectionRegular" in collection.dataset) {
|
||||
initializeRemove(collection, entry);
|
||||
if (empty_explain !== null) {
|
||||
empty_explain.remove();
|
||||
}
|
||||
}
|
||||
|
||||
collection.appendChild(entry);
|
||||
collection.dispatchEvent(event);
|
||||
window.dispatchEvent(event);
|
||||
collection.appendChild(entry);
|
||||
collection.dispatchEvent(event);
|
||||
window.dispatchEvent(event);
|
||||
};
|
||||
|
||||
const initializeRemove = (
|
||||
collection: HTMLUListElement,
|
||||
entry: HTMLLIElement,
|
||||
collection: HTMLUListElement,
|
||||
entry: HTMLLIElement,
|
||||
): void => {
|
||||
const button = buildRemoveButton(collection, entry);
|
||||
if (null === button) {
|
||||
return;
|
||||
}
|
||||
entry.appendChild(button);
|
||||
const button = buildRemoveButton(collection, entry);
|
||||
if (null === button) {
|
||||
return;
|
||||
}
|
||||
entry.appendChild(button);
|
||||
};
|
||||
|
||||
export const buildRemoveButton = (
|
||||
collection: HTMLUListElement,
|
||||
entry: HTMLLIElement,
|
||||
collection: HTMLUListElement,
|
||||
entry: HTMLLIElement,
|
||||
): HTMLButtonElement | null => {
|
||||
const button = document.createElement("button"),
|
||||
isPersisted = entry.dataset.collectionIsPersisted || "",
|
||||
content = collection.dataset.collectionButtonRemoveLabel || "",
|
||||
allowDelete = collection.dataset.collectionAllowDelete || "",
|
||||
event = new CustomEvent("collection-remove-entry", {
|
||||
detail: new CollectionEventPayload(collection, entry),
|
||||
});
|
||||
|
||||
if (allowDelete === "0" && isPersisted === "1") {
|
||||
return null;
|
||||
}
|
||||
button.classList.add("btn", "btn-delete", "remove-entry");
|
||||
button.textContent = content;
|
||||
button.addEventListener("click", (e: Event) => {
|
||||
e.preventDefault();
|
||||
entry.remove();
|
||||
collection.dispatchEvent(event);
|
||||
window.dispatchEvent(event);
|
||||
const button = document.createElement("button"),
|
||||
isPersisted = entry.dataset.collectionIsPersisted || "",
|
||||
content = collection.dataset.collectionButtonRemoveLabel || "",
|
||||
allowDelete = collection.dataset.collectionAllowDelete || "",
|
||||
event = new CustomEvent("collection-remove-entry", {
|
||||
detail: new CollectionEventPayload(collection, entry),
|
||||
});
|
||||
|
||||
return button;
|
||||
if (allowDelete === "0" && isPersisted === "1") {
|
||||
return null;
|
||||
}
|
||||
button.classList.add("btn", "btn-delete", "remove-entry");
|
||||
button.textContent = content;
|
||||
button.addEventListener("click", (e: Event) => {
|
||||
e.preventDefault();
|
||||
entry.remove();
|
||||
collection.dispatchEvent(event);
|
||||
window.dispatchEvent(event);
|
||||
});
|
||||
|
||||
return button;
|
||||
};
|
||||
|
||||
const collectionsInit = new Set<string>();
|
||||
const buttonsInit = new Set<string>();
|
||||
|
||||
const initialize = function (target: Document | Element): void {
|
||||
const addButtons: NodeListOf<HTMLButtonElement> = document.querySelectorAll(
|
||||
"button[data-collection-add-target]",
|
||||
),
|
||||
collections: NodeListOf<HTMLUListElement> = document.querySelectorAll(
|
||||
"ul[data-collection-regular]",
|
||||
);
|
||||
const addButtons: NodeListOf<HTMLButtonElement> = document.querySelectorAll(
|
||||
"button[data-collection-add-target]",
|
||||
),
|
||||
collections: NodeListOf<HTMLUListElement> = document.querySelectorAll(
|
||||
"ul[data-collection-regular]",
|
||||
);
|
||||
|
||||
for (let i = 0; i < addButtons.length; i++) {
|
||||
const addButton = addButtons[i];
|
||||
const uniqid = addButton.dataset.uniqid as string;
|
||||
if (buttonsInit.has(uniqid)) {
|
||||
continue;
|
||||
}
|
||||
buttonsInit.add(uniqid);
|
||||
addButton.addEventListener("click", (e: Event) => {
|
||||
e.preventDefault();
|
||||
handleAdd(e.target);
|
||||
});
|
||||
for (let i = 0; i < addButtons.length; i++) {
|
||||
const addButton = addButtons[i];
|
||||
const uniqid = addButton.dataset.uniqid as string;
|
||||
if (buttonsInit.has(uniqid)) {
|
||||
continue;
|
||||
}
|
||||
for (let i = 0; i < collections.length; i++) {
|
||||
const collection = collections[i];
|
||||
const uniqid = collection.dataset.uniqid as string;
|
||||
if (collectionsInit.has(uniqid)) {
|
||||
continue;
|
||||
}
|
||||
collectionsInit.add(uniqid);
|
||||
const entries: NodeListOf<HTMLLIElement> =
|
||||
collection.querySelectorAll(":scope > li");
|
||||
for (let j = 0; j < entries.length; j++) {
|
||||
if (entries[j].dataset.collectionEmptyExplain === "1") {
|
||||
continue;
|
||||
}
|
||||
initializeRemove(collections[i], entries[j]);
|
||||
}
|
||||
buttonsInit.add(uniqid);
|
||||
addButton.addEventListener("click", (e: Event) => {
|
||||
e.preventDefault();
|
||||
handleAdd(e.target);
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < collections.length; i++) {
|
||||
const collection = collections[i];
|
||||
const uniqid = collection.dataset.uniqid as string;
|
||||
if (collectionsInit.has(uniqid)) {
|
||||
continue;
|
||||
}
|
||||
collectionsInit.add(uniqid);
|
||||
const entries: NodeListOf<HTMLLIElement> =
|
||||
collection.querySelectorAll(":scope > li");
|
||||
for (let j = 0; j < entries.length; j++) {
|
||||
if (entries[j].dataset.collectionEmptyExplain === "1") {
|
||||
continue;
|
||||
}
|
||||
initializeRemove(collections[i], entries[j]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
initialize(document);
|
||||
initialize(document);
|
||||
});
|
||||
|
||||
window.addEventListener(
|
||||
"show-hide-show",
|
||||
(
|
||||
event: CustomEvent<{
|
||||
id: number;
|
||||
container: HTMLElement;
|
||||
froms: HTMLElement[];
|
||||
}>,
|
||||
) => {
|
||||
const container = event.detail.container as HTMLElement;
|
||||
initialize(container);
|
||||
},
|
||||
"show-hide-show",
|
||||
(
|
||||
event: CustomEvent<{
|
||||
id: number;
|
||||
container: HTMLElement;
|
||||
froms: HTMLElement[];
|
||||
}>,
|
||||
) => {
|
||||
const container = event.detail.container as HTMLElement;
|
||||
initialize(container);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -5,39 +5,39 @@ import NotificationReadAllToggle from "../../vuejs/_components/Notification/Noti
|
||||
const i18n = _createI18n({});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const elements = document.querySelectorAll(".notification_all_read");
|
||||
const elements = document.querySelectorAll(".notification_all_read");
|
||||
|
||||
elements.forEach((element) => {
|
||||
console.log("launch");
|
||||
createApp({
|
||||
template: `<notification-read-all-toggle @markAsRead="markAsRead" @markAsUnRead="markAsUnread"></notification-read-all-toggle>`,
|
||||
components: {
|
||||
NotificationReadAllToggle,
|
||||
},
|
||||
methods: {
|
||||
markAsRead(id: number) {
|
||||
const el = document.querySelector<HTMLDivElement>(
|
||||
`div.notification-status[data-notification-id="${id}"]`,
|
||||
);
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
el.classList.add("read");
|
||||
el.classList.remove("unread");
|
||||
},
|
||||
markAsUnread(id: number) {
|
||||
const el = document.querySelector<HTMLDivElement>(
|
||||
`div.notification-status[data-notification-id="${id}"]`,
|
||||
);
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
el.classList.remove("read");
|
||||
el.classList.add("unread");
|
||||
},
|
||||
},
|
||||
})
|
||||
.use(i18n)
|
||||
.mount(element);
|
||||
});
|
||||
elements.forEach((element) => {
|
||||
console.log("launch");
|
||||
createApp({
|
||||
template: `<notification-read-all-toggle @markAsRead="markAsRead" @markAsUnRead="markAsUnread"></notification-read-all-toggle>`,
|
||||
components: {
|
||||
NotificationReadAllToggle,
|
||||
},
|
||||
methods: {
|
||||
markAsRead(id: number) {
|
||||
const el = document.querySelector<HTMLDivElement>(
|
||||
`div.notification-status[data-notification-id="${id}"]`,
|
||||
);
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
el.classList.add("read");
|
||||
el.classList.remove("unread");
|
||||
},
|
||||
markAsUnread(id: number) {
|
||||
const el = document.querySelector<HTMLDivElement>(
|
||||
`div.notification-status[data-notification-id="${id}"]`,
|
||||
);
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
el.classList.remove("read");
|
||||
el.classList.add("unread");
|
||||
},
|
||||
},
|
||||
})
|
||||
.use(i18n)
|
||||
.mount(element);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,9 +124,10 @@ function loadDynamicPicker(element) {
|
||||
removeEntity({ entity }) {
|
||||
if (
|
||||
-1 ===
|
||||
this.suggested.findIndex(
|
||||
(e) => e.type === entity.type && e.id === entity.id,
|
||||
)
|
||||
this.suggested.findIndex(
|
||||
(e) => e.type === entity.type && e.id === entity.id,
|
||||
) &&
|
||||
"me" !== entity
|
||||
) {
|
||||
this.suggested.push(entity);
|
||||
}
|
||||
|
||||
@@ -2,216 +2,279 @@ import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc";
|
||||
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
|
||||
|
||||
export interface DateTime {
|
||||
datetime: string;
|
||||
datetime8601: string;
|
||||
datetime: string;
|
||||
datetime8601: string;
|
||||
}
|
||||
|
||||
export interface Civility {
|
||||
id: number;
|
||||
// TODO
|
||||
id: number;
|
||||
// TODO
|
||||
}
|
||||
|
||||
export interface Household {
|
||||
type: "household";
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface Job {
|
||||
id: number;
|
||||
type: "user_job";
|
||||
label: {
|
||||
fr: string; // could have other key. How to do that in ts ?
|
||||
};
|
||||
id: number;
|
||||
type: "user_job";
|
||||
label: {
|
||||
fr: string; // could have other key. How to do that in ts ?
|
||||
};
|
||||
}
|
||||
|
||||
export interface Center {
|
||||
id: number;
|
||||
type: "center";
|
||||
name: string;
|
||||
id: number;
|
||||
type: "center";
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Scope {
|
||||
id: number;
|
||||
type: "scope";
|
||||
name: {
|
||||
fr: string;
|
||||
};
|
||||
id: number;
|
||||
type: "scope";
|
||||
name: {
|
||||
fr: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ResultItem<T> {
|
||||
result: T;
|
||||
relevance: number;
|
||||
result: T;
|
||||
relevance: number;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
type: "user";
|
||||
id: number;
|
||||
username: string;
|
||||
text: string;
|
||||
text_without_absence: string;
|
||||
email: string;
|
||||
user_job: Job;
|
||||
label: string;
|
||||
// todo: mainCenter; mainJob; etc..
|
||||
type: "user";
|
||||
id: number;
|
||||
username: string;
|
||||
text: string;
|
||||
text_without_absence: string;
|
||||
email: string;
|
||||
user_job: Job;
|
||||
label: string;
|
||||
// todo: mainCenter; mainJob; etc..
|
||||
}
|
||||
|
||||
export interface UserGroup {
|
||||
type: "user_group";
|
||||
id: number;
|
||||
label: TranslatableString;
|
||||
backgroundColor: string;
|
||||
foregroundColor: string;
|
||||
excludeKey: string;
|
||||
text: string;
|
||||
type: "user_group";
|
||||
id: number;
|
||||
label: TranslatableString;
|
||||
backgroundColor: string;
|
||||
foregroundColor: string;
|
||||
excludeKey: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export type UserGroupOrUser = User | UserGroup;
|
||||
|
||||
export interface UserAssociatedInterface {
|
||||
type: "user";
|
||||
id: number;
|
||||
type: "user";
|
||||
id: number;
|
||||
}
|
||||
|
||||
export type TranslatableString = Record<string, string>;
|
||||
|
||||
export interface Postcode {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
center: Point;
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
center: Point;
|
||||
}
|
||||
|
||||
export interface Point {
|
||||
type: "Point";
|
||||
coordinates: [lat: number, lon: number];
|
||||
type: "Point";
|
||||
coordinates: [lat: number, lon: number];
|
||||
}
|
||||
|
||||
export interface Country {
|
||||
id: number;
|
||||
name: TranslatableString;
|
||||
code: string;
|
||||
id: number;
|
||||
name: TranslatableString;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export type AddressRefStatus = "match" | "to_review" | "reviewed";
|
||||
|
||||
export interface Address {
|
||||
type: "address";
|
||||
address_id: number;
|
||||
text: string;
|
||||
street: string;
|
||||
streetNumber: string;
|
||||
postcode: Postcode;
|
||||
country: Country;
|
||||
floor: string | null;
|
||||
corridor: string | null;
|
||||
steps: string | null;
|
||||
flat: string | null;
|
||||
buildingName: string | null;
|
||||
distribution: string | null;
|
||||
extra: string | null;
|
||||
confidential: boolean;
|
||||
lines: string[];
|
||||
addressReference: AddressReference | null;
|
||||
validFrom: DateTime;
|
||||
validTo: DateTime | null;
|
||||
point: Point | null;
|
||||
refStatus: AddressRefStatus;
|
||||
isNoAddress: boolean;
|
||||
type: "address";
|
||||
address_id: number;
|
||||
text: string;
|
||||
street: string;
|
||||
streetNumber: string;
|
||||
postcode: Postcode;
|
||||
country: Country;
|
||||
floor: string | null;
|
||||
corridor: string | null;
|
||||
steps: string | null;
|
||||
flat: string | null;
|
||||
buildingName: string | null;
|
||||
distribution: string | null;
|
||||
extra: string | null;
|
||||
confidential: boolean;
|
||||
lines: string[];
|
||||
addressReference: AddressReference | null;
|
||||
validFrom: DateTime;
|
||||
validTo: DateTime | null;
|
||||
point: Point | null;
|
||||
refStatus: AddressRefStatus;
|
||||
isNoAddress: boolean;
|
||||
}
|
||||
|
||||
export interface AddressWithPoint extends Address {
|
||||
point: Point;
|
||||
point: Point;
|
||||
}
|
||||
|
||||
export interface AddressReference {
|
||||
id: number;
|
||||
createdAt: DateTime | null;
|
||||
deletedAt: DateTime | null;
|
||||
municipalityCode: string;
|
||||
point: Point;
|
||||
postcode: Postcode;
|
||||
refId: string;
|
||||
source: string;
|
||||
street: string;
|
||||
streetNumber: string;
|
||||
updatedAt: DateTime | null;
|
||||
id: number;
|
||||
createdAt: DateTime | null;
|
||||
deletedAt: DateTime | null;
|
||||
municipalityCode: string;
|
||||
point: Point;
|
||||
postcode: Postcode;
|
||||
refId: string;
|
||||
source: string;
|
||||
street: string;
|
||||
streetNumber: string;
|
||||
updatedAt: DateTime | null;
|
||||
}
|
||||
|
||||
export interface SimpleGeographicalUnit {
|
||||
id: number;
|
||||
layerId: number;
|
||||
unitName: string;
|
||||
unitRefId: string;
|
||||
id: number;
|
||||
layerId: number;
|
||||
unitName: string;
|
||||
unitRefId: string;
|
||||
}
|
||||
|
||||
export interface GeographicalUnitLayer {
|
||||
id: number;
|
||||
name: TranslatableString;
|
||||
refId: string;
|
||||
id: number;
|
||||
name: TranslatableString;
|
||||
refId: string;
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
type: "location";
|
||||
id: number;
|
||||
active: boolean;
|
||||
address: Address | null;
|
||||
availableForUsers: boolean;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
updatedAt: DateTime | null;
|
||||
updatedBy: User | null;
|
||||
email: string | null;
|
||||
name: string;
|
||||
phonenumber1: string | null;
|
||||
phonenumber2: string | null;
|
||||
locationType: LocationType;
|
||||
type: "location";
|
||||
id: number;
|
||||
active: boolean;
|
||||
address: Address | null;
|
||||
availableForUsers: boolean;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
updatedAt: DateTime | null;
|
||||
updatedBy: User | null;
|
||||
email: string | null;
|
||||
name: string;
|
||||
phonenumber1: string | null;
|
||||
phonenumber2: string | null;
|
||||
locationType: LocationType;
|
||||
}
|
||||
|
||||
export interface LocationAssociated {
|
||||
type: "location";
|
||||
id: number;
|
||||
type: "location";
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface LocationType {
|
||||
type: "location-type";
|
||||
id: number;
|
||||
active: boolean;
|
||||
addressRequired: "optional" | "required";
|
||||
availableForUsers: boolean;
|
||||
editableByUsers: boolean;
|
||||
contactData: "optional" | "required";
|
||||
title: TranslatableString;
|
||||
type: "location-type";
|
||||
id: number;
|
||||
active: boolean;
|
||||
addressRequired: "optional" | "required";
|
||||
availableForUsers: boolean;
|
||||
editableByUsers: boolean;
|
||||
contactData: "optional" | "required";
|
||||
title: TranslatableString;
|
||||
}
|
||||
|
||||
export interface NewsItemType {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
startDate: DateTime;
|
||||
endDate: DateTime | null;
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
startDate: DateTime;
|
||||
endDate: DateTime | null;
|
||||
}
|
||||
|
||||
export interface WorkflowAvailable {
|
||||
name: string;
|
||||
text: string;
|
||||
name: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface WorkflowAttachment {
|
||||
id: number;
|
||||
relatedGenericDocKey: string;
|
||||
relatedGenericDocIdentifiers: object;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
updatedAt: DateTime | null;
|
||||
updatedBy: User | null;
|
||||
genericDoc: null | GenericDoc;
|
||||
id: number;
|
||||
relatedGenericDocKey: string;
|
||||
relatedGenericDocIdentifiers: object;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
updatedAt: DateTime | null;
|
||||
updatedBy: User | null;
|
||||
genericDoc: null | GenericDoc;
|
||||
}
|
||||
|
||||
export interface ExportGeneration {
|
||||
id: string;
|
||||
type: "export_generation";
|
||||
exportAlias: string;
|
||||
createdBy: User | null;
|
||||
createdAt: DateTime | null;
|
||||
status: StoredObjectStatus;
|
||||
storedObject: StoredObject;
|
||||
id: string;
|
||||
type: "export_generation";
|
||||
exportAlias: string;
|
||||
createdBy: User | null;
|
||||
createdAt: DateTime | null;
|
||||
status: StoredObjectStatus;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
export interface PrivateCommentEmbeddable {
|
||||
comments: Record<number, string>;
|
||||
comments: Record<number, string>;
|
||||
}
|
||||
|
||||
// API Exception types
|
||||
export interface TransportExceptionInterface {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ValidationExceptionInterface
|
||||
extends TransportExceptionInterface {
|
||||
name: "ValidationException";
|
||||
error: object;
|
||||
violations: string[];
|
||||
titles: string[];
|
||||
propertyPaths: string[];
|
||||
}
|
||||
|
||||
export interface AccessExceptionInterface extends TransportExceptionInterface {
|
||||
name: "AccessException";
|
||||
violations: string[];
|
||||
}
|
||||
|
||||
export interface NotFoundExceptionInterface
|
||||
extends TransportExceptionInterface {
|
||||
name: "NotFoundException";
|
||||
}
|
||||
|
||||
export interface ServerExceptionInterface extends TransportExceptionInterface {
|
||||
name: "ServerException";
|
||||
message: string;
|
||||
code: number;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface ConflictHttpExceptionInterface
|
||||
extends TransportExceptionInterface {
|
||||
name: "ConflictHttpException";
|
||||
violations: string[];
|
||||
}
|
||||
|
||||
export type ApiException =
|
||||
| ValidationExceptionInterface
|
||||
| AccessExceptionInterface
|
||||
| NotFoundExceptionInterface
|
||||
| ServerExceptionInterface
|
||||
| ConflictHttpExceptionInterface;
|
||||
|
||||
export interface Modal {
|
||||
showModal: boolean;
|
||||
modalDialogClass: string;
|
||||
}
|
||||
|
||||
export interface Selected {
|
||||
result: UserGroupOrUser;
|
||||
}
|
||||
|
||||
export interface addNewEntities {
|
||||
selected: Selected[];
|
||||
modal: Modal;
|
||||
}
|
||||
|
||||
@@ -1,130 +1,130 @@
|
||||
<template>
|
||||
<add-address
|
||||
:key="key"
|
||||
:context="context"
|
||||
:options="options"
|
||||
:address-changed-callback="submitAddress"
|
||||
ref="addAddress"
|
||||
/>
|
||||
<add-address
|
||||
:key="key"
|
||||
:context="context"
|
||||
:options="options"
|
||||
:address-changed-callback="submitAddress"
|
||||
ref="addAddress"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddAddress from "./components/AddAddress.vue";
|
||||
import {
|
||||
postAddressToHousehold,
|
||||
postAddressToPerson,
|
||||
postAddressToHousehold,
|
||||
postAddressToPerson,
|
||||
} from "ChillPersonAssets/vuejs/_api/AddAddress";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
AddAddress,
|
||||
name: "App",
|
||||
components: {
|
||||
AddAddress,
|
||||
},
|
||||
props: ["addAddress", "callback"],
|
||||
emits: ["addressEdited", "addressCreated"],
|
||||
computed: {
|
||||
context() {
|
||||
return this.addAddress.context;
|
||||
},
|
||||
props: ["addAddress", "callback"],
|
||||
emits: ["addressEdited", "addressCreated"],
|
||||
computed: {
|
||||
context() {
|
||||
return this.addAddress.context;
|
||||
},
|
||||
options() {
|
||||
return this.addAddress.options;
|
||||
},
|
||||
key() {
|
||||
return this.context.edit
|
||||
? "address_" + this.context.addressId
|
||||
: this.context.target.name + "_" + this.context.target.id;
|
||||
},
|
||||
options() {
|
||||
return this.addAddress.options;
|
||||
},
|
||||
mounted() {
|
||||
//console.log('AddAddress: data context', this.context);
|
||||
//console.log('AddAddress: data options', this.options);
|
||||
key() {
|
||||
return this.context.edit
|
||||
? "address_" + this.context.addressId
|
||||
: this.context.target.name + "_" + this.context.target.id;
|
||||
},
|
||||
methods: {
|
||||
displayErrors() {
|
||||
return this.$refs.addAddress.errorMsg;
|
||||
},
|
||||
submitAddress(payload) {
|
||||
console.log("@@@ click on Submit Address Button", payload);
|
||||
|
||||
// Existing address
|
||||
if (this.context.edit) {
|
||||
// address is already linked, just finish !
|
||||
this.$refs.addAddress.afterLastPaneAction({});
|
||||
this.$emit("addressEdited", payload);
|
||||
|
||||
// New created address
|
||||
} else {
|
||||
this.postAddressTo(payload);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Post new created address to targetEntity
|
||||
*/
|
||||
postAddressTo(payload) {
|
||||
this.$emit("addressCreated", payload);
|
||||
|
||||
console.log(
|
||||
"postAddress",
|
||||
payload.addressId,
|
||||
"To",
|
||||
payload.target,
|
||||
payload.targetId,
|
||||
);
|
||||
switch (payload.target) {
|
||||
case "household":
|
||||
postAddressToHousehold(payload.targetId, payload.addressId)
|
||||
.then(
|
||||
(address) =>
|
||||
new Promise((resolve, reject) => {
|
||||
console.log("..household address", address);
|
||||
this.$refs.addAddress.flag.loading = false;
|
||||
this.$refs.addAddress.flag.success = true;
|
||||
|
||||
// finish
|
||||
this.$refs.addAddress.afterLastPaneAction({
|
||||
addressId: address.address_id,
|
||||
});
|
||||
|
||||
resolve();
|
||||
}),
|
||||
)
|
||||
.catch((error) => {
|
||||
this.$refs.addAddress.errorMsg.push(error);
|
||||
this.$refs.addAddress.flag.loading = false;
|
||||
});
|
||||
break;
|
||||
case "person":
|
||||
postAddressToPerson(payload.targetId, payload.addressId)
|
||||
.then(
|
||||
(address) =>
|
||||
new Promise((resolve, reject) => {
|
||||
console.log("..person address", address);
|
||||
this.$refs.addAddress.flag.loading = false;
|
||||
this.$refs.addAddress.flag.success = true;
|
||||
|
||||
// finish
|
||||
this.$refs.addAddress.afterLastPaneAction({
|
||||
addressId: address.address_id,
|
||||
});
|
||||
|
||||
resolve();
|
||||
}),
|
||||
)
|
||||
.catch((error) => {
|
||||
this.$refs.addAddress.errorMsg.push(error);
|
||||
this.$refs.addAddress.flag.loading = false;
|
||||
});
|
||||
break;
|
||||
case "thirdparty":
|
||||
console.log("TODO write postAddressToThirdparty");
|
||||
break;
|
||||
default:
|
||||
this.$refs.addAddress.errorMsg.push(
|
||||
"That entity is not managed by address !",
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
//console.log('AddAddress: data context', this.context);
|
||||
//console.log('AddAddress: data options', this.options);
|
||||
},
|
||||
methods: {
|
||||
displayErrors() {
|
||||
return this.$refs.addAddress.errorMsg;
|
||||
},
|
||||
submitAddress(payload) {
|
||||
console.log("@@@ click on Submit Address Button", payload);
|
||||
|
||||
// Existing address
|
||||
if (this.context.edit) {
|
||||
// address is already linked, just finish !
|
||||
this.$refs.addAddress.afterLastPaneAction({});
|
||||
this.$emit("addressEdited", payload);
|
||||
|
||||
// New created address
|
||||
} else {
|
||||
this.postAddressTo(payload);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Post new created address to targetEntity
|
||||
*/
|
||||
postAddressTo(payload) {
|
||||
this.$emit("addressCreated", payload);
|
||||
|
||||
console.log(
|
||||
"postAddress",
|
||||
payload.addressId,
|
||||
"To",
|
||||
payload.target,
|
||||
payload.targetId,
|
||||
);
|
||||
switch (payload.target) {
|
||||
case "household":
|
||||
postAddressToHousehold(payload.targetId, payload.addressId)
|
||||
.then(
|
||||
(address) =>
|
||||
new Promise((resolve, reject) => {
|
||||
console.log("..household address", address);
|
||||
this.$refs.addAddress.flag.loading = false;
|
||||
this.$refs.addAddress.flag.success = true;
|
||||
|
||||
// finish
|
||||
this.$refs.addAddress.afterLastPaneAction({
|
||||
addressId: address.address_id,
|
||||
});
|
||||
|
||||
resolve();
|
||||
}),
|
||||
)
|
||||
.catch((error) => {
|
||||
this.$refs.addAddress.errorMsg.push(error);
|
||||
this.$refs.addAddress.flag.loading = false;
|
||||
});
|
||||
break;
|
||||
case "person":
|
||||
postAddressToPerson(payload.targetId, payload.addressId)
|
||||
.then(
|
||||
(address) =>
|
||||
new Promise((resolve, reject) => {
|
||||
console.log("..person address", address);
|
||||
this.$refs.addAddress.flag.loading = false;
|
||||
this.$refs.addAddress.flag.success = true;
|
||||
|
||||
// finish
|
||||
this.$refs.addAddress.afterLastPaneAction({
|
||||
addressId: address.address_id,
|
||||
});
|
||||
|
||||
resolve();
|
||||
}),
|
||||
)
|
||||
.catch((error) => {
|
||||
this.$refs.addAddress.errorMsg.push(error);
|
||||
this.$refs.addAddress.flag.loading = false;
|
||||
});
|
||||
break;
|
||||
case "thirdparty":
|
||||
console.log("TODO write postAddressToThirdparty");
|
||||
break;
|
||||
default:
|
||||
this.$refs.addAddress.errorMsg.push(
|
||||
"That entity is not managed by address !",
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
<template>
|
||||
<ul
|
||||
class="record_actions"
|
||||
v-if="!options.onlyButton"
|
||||
:class="{ 'sticky-form-buttons': isStickyForm }"
|
||||
>
|
||||
<li v-if="isStickyForm" class="cancel">
|
||||
<slot name="before" />
|
||||
</li>
|
||||
<li>
|
||||
<slot name="action" />
|
||||
</li>
|
||||
<li v-if="isStickyForm">
|
||||
<slot name="after" />
|
||||
</li>
|
||||
</ul>
|
||||
<slot v-else name="action" />
|
||||
<ul
|
||||
class="record_actions"
|
||||
v-if="!options.onlyButton"
|
||||
:class="{ 'sticky-form-buttons': isStickyForm }"
|
||||
>
|
||||
<li v-if="isStickyForm" class="cancel">
|
||||
<slot name="before" />
|
||||
</li>
|
||||
<li>
|
||||
<slot name="action" />
|
||||
</li>
|
||||
<li v-if="isStickyForm">
|
||||
<slot name="after" />
|
||||
</li>
|
||||
</ul>
|
||||
<slot v-else name="action" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ActionButtons",
|
||||
props: ["options", "defaultz"],
|
||||
computed: {
|
||||
isStickyForm() {
|
||||
return typeof this.options.stickyActions !== "undefined"
|
||||
? this.options.stickyActions
|
||||
: this.defaultz.stickyActions;
|
||||
},
|
||||
name: "ActionButtons",
|
||||
props: ["options", "defaultz"],
|
||||
computed: {
|
||||
isStickyForm() {
|
||||
return typeof this.options.stickyActions !== "undefined"
|
||||
? this.options.stickyActions
|
||||
: this.defaultz.stickyActions;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user