mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 17:28:23 +00:00 
			
		
		
		
	Compare commits
	
		
			236 Commits
		
	
	
		
			425-rename
			...
			create-adm
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 19785795ad | |||
| e8450a519d | |||
| c66f0122bf | |||
| 37291b2756 | |||
| a88575463b | |||
| 6d2e78ce55 | |||
| 61ca700bbe | |||
|  | b43aeebc3c | ||
| 056e2dcc5f | |||
| e57d1ac696 | |||
| 0eff1d2e79 | |||
| 3928b2cc7a | |||
| 4f51ef81ad | |||
| 4637dc692c | |||
| 38935edb93 | |||
|  | e1ef65d4ca | ||
| ec9d0be70b | |||
|  | 0ba2cbc1e8 | ||
| e87429933a | |||
| 8e2e676e3d | |||
| e12ad563a3 | |||
|  | 711aa8db9b | ||
| e78d44953f | |||
| 18f67801c7 | |||
| c815e6bc69 | |||
| 807f2711fe | |||
|  | cd594cd580 | ||
| fb6b26bfb5 | |||
| c5cedb8bd6 | |||
| 2665e43a61 | |||
| 25561cdf63 | |||
| 10b73e06e1 | |||
|  | e7c04e34a9 | ||
| 164beee7c6 | |||
|  | 4d96eb9457 | ||
| fe2eba3b29 | |||
|  | 61d1232e31 | ||
| 6594d4f6a6 | |||
| 1a66a9e864 | |||
| 1b74c119dc | |||
| 14d88810f3 | |||
| 445a2c9358 | |||
| c8baf0a8aa | |||
| faed443a96 | |||
| bbf387d96f | |||
|  | b7c9b60744 | ||
| 2bd303bbbe | |||
| c5e6122d2c | |||
| 088b876e20 | |||
| 3400656d7c | |||
| 568c8be7fd | |||
| 538ecc42ea | |||
| 15d26d4b06 | |||
| d8bd9bd7cd | |||
| dcdfba5ccd | |||
| 0204bdd38d | |||
| 392fd01b56 | |||
| 35844f3b73 | |||
| 7506b918d7 | |||
| cfba291f2c | |||
|  | 04438c09d3 | ||
| 2a54d1b909 | |||
|  | 628eeac5e0 | ||
| a2263b3fa1 | |||
| 74796d0fb0 | |||
| c19481e40a | |||
|  | 6eeb717b1a | ||
| beb7c462da | |||
|  | dbf363a9e8 | ||
| 64a2f7c9ed | |||
| f26d9739c8 | |||
| afa5edc1d8 | |||
| 42d6c9e672 | |||
| 2b22d4cb7c | |||
| c8e5d0eb37 | |||
| 2bf8ad5d6c | |||
| 11698a52e3 | |||
| 70955573e8 | |||
|  | 3df4043eb9 | ||
| 06e8264dde | |||
| b451d2c4a3 | |||
| 4f93150874 | |||
| 0566ab0910 | |||
|  | f4eeee1598 | ||
| 33cf16fc13 | |||
| 0a331aab37 | |||
| d43b739654 | |||
| c72432efae | |||
| 95975fae55 | |||
| 95a7efa138 | |||
| 45e193ff6d | |||
| dfc146ff3f | |||
| b41fcf66a9 | |||
|  | a8dd1b3548 | ||
| 2b99a480ac | |||
| 7633e587bb | |||
| fc61dfdf3a | |||
| f1a5b5c49e | |||
| ec685dcd47 | |||
| 631ae3eedd | |||
| 440a7837ac | |||
| e0abf34784 | |||
| 377ae9a9dc | |||
| 034dc30e30 | |||
| d615111a0f | |||
| ffb756c712 | |||
| 69daccb860 | |||
| 16435423cf | |||
| 697b4ab436 | |||
| 67d804e28e | |||
| cf41fa9574 | |||
| b8b325f7d7 | |||
| e97bd8c4ef | |||
| e28d7df533 | |||
| 4b20b1bc01 | |||
| b15733076c | |||
| 25be5c9ea3 | |||
| b035020c6f | |||
| 128101dc46 | |||
| 5f2711023e | |||
| bdf2ed4bbd | |||
| 1df542603e | |||
| 80bcc68ce5 | |||
| 154fc3e2f6 | |||
| e45af94c78 | |||
| 166a6fde20 | |||
| 631f047338 | |||
| a777588bb8 | |||
| ca78d112c2 | |||
| bcfd317d83 | |||
| 348740f073 | |||
| 0d74f0980f | |||
| be19dc00db | |||
| 643028ffd6 | |||
| ac4e2e5bf2 | |||
| 498572b96e | |||
| d2a61ce69b | |||
| a9c0567ee1 | |||
| 76cec5b5a8 | |||
| efe8a67697 | |||
| 26dfa9b028 | |||
| 50025044d3 | |||
| e6202a2e34 | |||
| b863bd967d | |||
| e65bcf7275 | |||
| e00ece4200 | |||
| 640fd71402 | |||
| aae50ca290 | |||
| 1fa483598b | |||
| e4b6a468f8 | |||
|  | 66c7758023 | ||
|  | 4750d2c24e | ||
|  | ca05e3d979 | ||
|  | a20f9b4f86 | ||
|  | c73c1eb8d5 | ||
|  | 8778bb0731 | ||
|  | c7d20eebc5 | ||
|  | b9e130c159 | ||
|  | 3e8bc94af3 | ||
|  | 0c914c9f9f | ||
|  | 580a60c939 | ||
|  | 4996ac3b7c | ||
|  | 2a23bf19cb | ||
|  | 650d2596d9 | ||
|  | 2bdd5a329e | ||
| 78d1776733 | |||
| 66dc603c85 | |||
| 3a8154ecce | |||
| c81828e04f | |||
|  | ec17dd7de2 | ||
| 76c076a5f3 | |||
|  | f0045edd6c | ||
|  | d00b76ffcd | ||
|  | 8991f0ef3f | ||
|  | d6f5eae0c9 | ||
|  | 821fce3dd8 | ||
|  | 1d33ae1e39 | ||
|  | 19af0feb57 | ||
|  | 1c09e9a692 | ||
|  | d72e748388 | ||
|  | ab850b7b70 | ||
|  | 3f9745d8cf | ||
|  | 473765366a | ||
|  | 6500c24a7f | ||
|  | 1d00457141 | ||
|  | eb0bf56cff | ||
|  | 7b8cd90cf1 | ||
|  | a27d92aba0 | ||
|  | 85bdfb9e21 | ||
|  | 4cffcf4de1 | ||
|  | b2587a688f | ||
|  | c9f0e9843b | ||
|  | b40ad9e445 | ||
|  | 3e10e47e29 | ||
|  | 2a1963e993 | ||
| 34c171659b | |||
| 2d8b960d9e | |||
| 831ae03431 | |||
| 45828174d1 | |||
| ed45f14a45 | |||
| fa67835690 | |||
| b434d38091 | |||
|  | 800a952532 | ||
| 9f355032a8 | |||
| 0bc6e62d4d | |||
| 46fb1c04b5 | |||
| 3b2c3d1464 | |||
|  | 0bd6038160 | ||
|  | baab8e94ce | ||
| e2deb55fdb | |||
|  | 2cdfb50058 | ||
| 39d701feb2 | |||
| 613ee8b186 | |||
| 56a1a488de | |||
| 3f789ad0f4 | |||
| 467bea7cde | |||
| 670b8eb82b | |||
| a9760b323f | |||
| 71a3a1924a | |||
| ecdc1e25bf | |||
| dd37427be1 | |||
| c8467df1b1 | |||
| 4c89a954fa | |||
| 7c1f3b114d | |||
| 36bc4dab24 | |||
| 4b30d92282 | |||
| 75fbec5489 | |||
| 912fdd6349 | |||
| 5832542978 | |||
| 5c3585a1ed | |||
| a2f1e20ddf | |||
| 4d67702a76 | |||
| 18e442db29 | |||
|  | deb3d92189 | ||
| a59ea7db31 | |||
| a738b0cac9 | 
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20240530-160003.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20240530-160003.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| kind: Feature | ||||
| body: | | ||||
|   Upgrade import of address list to the last version of compiled addresses of belgian-best-address | ||||
| time: 2024-05-30T16:00:03.440767606+02:00 | ||||
| custom: | ||||
|   Issue: "" | ||||
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20240531-190242.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20240531-190242.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| kind: Feature | ||||
| body: | | ||||
|   Upgrade CKEditor and refactor configuration with use of typescript | ||||
| time: 2024-05-31T19:02:42.776662753+02:00 | ||||
| custom: | ||||
|   Issue: "" | ||||
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20251007-155945.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20251007-155945.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| kind: Feature | ||||
| body: Admin interface for Motive entity | ||||
| time: 2025-10-07T15:59:45.597029709+02:00 | ||||
| custom: | ||||
|     Issue: "" | ||||
|     SchemaChange: No schema change | ||||
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20251022-111552.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20251022-111552.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| kind: Feature | ||||
| body: Add an admin interface for Motive entity | ||||
| time: 2025-10-22T11:15:52.13937955+02:00 | ||||
| custom: | ||||
|     Issue: "" | ||||
|     SchemaChange: Add columns or tables | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -234,15 +234,9 @@ This must be a decision made by a human, not by an AI. Every AI task must abort | ||||
|  | ||||
| #### Running Tests | ||||
|  | ||||
| The tests are run from the project's root (not from the bundle's root). | ||||
| The tests are run from the project's root (not from the bundle's root: so, do not change the directory to any bundle directory before running tests). | ||||
|  | ||||
| ```bash | ||||
| # Run all tests | ||||
| vendor/bin/phpunit | ||||
|  | ||||
| # Run tests for a specific bundle | ||||
| vendor/bin/phpunit --testsuite NameBundle | ||||
|  | ||||
| # Run a specific test file | ||||
| vendor/bin/phpunit path/to/TestFile.php | ||||
|  | ||||
|   | ||||
							
								
								
									
										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 | ||||
|   | ||||
| @@ -1,7 +1,12 @@ | ||||
| import { trans, setLocale, setLocaleFallbacks } from "./ux-translator"; | ||||
| import { | ||||
|   trans, | ||||
|   setLocale, | ||||
|   getLocale, | ||||
|   setLocaleFallbacks, | ||||
| } from "./ux-translator"; | ||||
|  | ||||
| setLocaleFallbacks({"en": "fr", "nl": "fr", "fr": "en"}); | ||||
| setLocale('fr'); | ||||
| setLocaleFallbacks({ en: "fr", nl: "fr", fr: "en" }); | ||||
| setLocale("fr"); | ||||
|  | ||||
| export { trans }; | ||||
| export * from '../var/translations'; | ||||
| export { trans, getLocale }; | ||||
| export * from "../var/translations"; | ||||
|   | ||||
| @@ -133,6 +133,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,7 @@ return [ | ||||
|     Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true], | ||||
|     Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true], | ||||
|     Chill\WopiBundle\ChillWopiBundle::class => ['all' => true], | ||||
|     Chill\TicketBundle\ChillTicketBundle::class => ['all' => true], | ||||
|     Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], | ||||
|     Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true], | ||||
| ]; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| chill_doc_store: | ||||
|     use_driver: openstack | ||||
|     use_driver: local_storage | ||||
|     local_storage: | ||||
|         storage_path: '%kernel.project_dir%/var/storage' | ||||
|     openstack: | ||||
|   | ||||
							
								
								
									
										5
									
								
								config/packages/chill_ticket.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								config/packages/chill_ticket.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| chill_ticket: | ||||
|     ticket: | ||||
|         person_per_ticket:    one # One of "one"; "many" | ||||
|         response_time_exceeded_delay: PT12H | ||||
|  | ||||
| @@ -14,6 +14,7 @@ doctrine_migrations: | ||||
|         'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations' | ||||
|         'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations' | ||||
|         'Chill\Migrations\Report': '@ChillReportBundle/migrations' | ||||
|         'Chill\Migrations\Ticket': '@ChillTicketBundle/migrations' | ||||
|     all_or_nothing: | ||||
|         true | ||||
|  | ||||
|   | ||||
							
								
								
									
										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. | ||||
|   | ||||
| @@ -79,12 +79,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> | ||||
|   | ||||
							
								
								
									
										8
									
								
								resources/ticket_motives_import/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								resources/ticket_motives_import/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| In this directory, you find an example of file for the command `chill:main:ticket_motives_import`. | ||||
|  | ||||
| This file contains a list of ticket motives to import into the system. Each entry is a dictionary with two keys: `code` and `label`. The `code` key contains the unique code for the ticket motive, and the `label` key contains the human-readable label for the ticket motive. | ||||
|  | ||||
| The `stored_objects` key contains the documents that will be associated with the tickets. They must be found in the same directory. | ||||
|  | ||||
| The command `chill:main:ticket_motives_import` uses this file to import the specified ticket motives into the system. | ||||
|  | ||||
							
								
								
									
										136
									
								
								resources/ticket_motives_import/motives.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								resources/ticket_motives_import/motives.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| - label: | ||||
|     fr: Appel famille pour annonce de décès | ||||
|   urgent: false | ||||
|   supplementary_informations: | ||||
|   - label: | ||||
|       fr: Date du décès | ||||
|   - label: | ||||
|       fr: lieu du décès (domicile ou hôpital) | ||||
|   - label: | ||||
|       fr: nom de l’hôpital | ||||
|   - label: | ||||
|       fr: service concerné | ||||
|   stored_objects: | ||||
|   - label: | ||||
|       fr: ☀️ De 07h à 21h | ||||
|     filename: 2_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🌙 De 21h à 07h du matin | ||||
|     filename: 3_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🗓️ Dimanches et jours fériés | ||||
|     filename: 4_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
| - label: | ||||
|     fr: 'Appel famille pour annonce absence : hospitalisation ou consultation' | ||||
|   urgent: false | ||||
|   supplementary_informations: | ||||
|   - label: | ||||
|       fr: Quel hôpital | ||||
|   - label: | ||||
|       fr: quel service | ||||
|   - label: | ||||
|       fr: pour quelles raisons | ||||
|   - label: | ||||
|       fr: 'consultation : date et heure' | ||||
|   - label: | ||||
|       fr: hospitalisation complète ou HDJ | ||||
|   stored_objects: | ||||
|   - label: | ||||
|       fr: ☀️ De 07h à 21h | ||||
|     filename: 5_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🌙 De 21h à 07h du matin | ||||
|     filename: 6_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🗓️ Dimanches et jours fériés | ||||
|     filename: 7_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
| - label: | ||||
|     fr: 'Appel famille pour annonce absence : interruption de prise en charge' | ||||
|   urgent: false | ||||
|   supplementary_informations: | ||||
|   - label: | ||||
|       fr: Pour quelles raisons ? Date | ||||
|   - label: | ||||
|       fr: durée | ||||
|   - label: | ||||
|       fr: accord médical ? | ||||
|   stored_objects: | ||||
|   - label: | ||||
|       fr: ☀️ De 07h à 21h | ||||
|     filename: 8_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🌙 De 21h à 07h du matin | ||||
|     filename: 9_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🗓️ Dimanches et jours fériés | ||||
|     filename: 10_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
| - label: | ||||
|     fr: 'Appel famille pour annonce absence : changement d’adresse' | ||||
|   urgent: false | ||||
|   supplementary_informations: | ||||
|   - label: | ||||
|       fr: Où | ||||
|   - label: | ||||
|       fr: Pourquoi ? Pour combien de temps ? Besoin d’un relais des soins ? Nouvelle adresse ? | ||||
|   stored_objects: | ||||
|   - label: | ||||
|       fr: ☀️ De 07h à 21h | ||||
|     filename: 11_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🌙 De 21h à 07h du matin | ||||
|     filename: 12_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🗓️ Dimanches et jours fériés | ||||
|     filename: 13_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
| - label: | ||||
|     fr: Appel famille pour altération de l’état général du patient | ||||
|   urgent: true | ||||
|   supplementary_informations: | ||||
|   - label: | ||||
|       fr: Recherche des symptômes | ||||
|   - label: | ||||
|       fr: Attentes par rapport à la demande | ||||
|   stored_objects: | ||||
|   - label: | ||||
|       fr: ☀️ De 07h à 21h | ||||
|     filename: 14_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🌙 De 21h à 07h du matin | ||||
|     filename: 15_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🗓️ Dimanches et jours fériés | ||||
|     filename: 16_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
| - label: | ||||
|     fr: Appel famille pour prise en charge de la douleur | ||||
|   urgent: true | ||||
|   supplementary_informations: | ||||
|   - label: | ||||
|       fr: Localisation douleur | ||||
|   - label: | ||||
|       fr: Horaire dernier passage | ||||
|   - label: | ||||
|       fr: Traitements en cours | ||||
|   stored_objects: | ||||
|   - label: | ||||
|       fr: ☀️ De 07h à 21h | ||||
|     filename: 17_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🌙 De 21h à 07h du matin | ||||
|     filename: 18_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🗓️ Dimanches et jours fériés | ||||
|     filename: 19_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
| - label: | ||||
|     fr: Appel famille pour information sur la date de prise en charge | ||||
|   urgent: false | ||||
|   supplementary_informations: [] | ||||
|   stored_objects: | ||||
|   - label: | ||||
|       fr: ☀️ De 07h à 21h | ||||
|     filename: 20_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🌙 De 21h à 07h du matin | ||||
|     filename: 21_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
|   - label: | ||||
|       fr: 🗓️ Dimanches et jours fériés | ||||
|     filename: 22_doc_20250402_Pelotons flux externes consolidés.pdf | ||||
							
								
								
									
										6
									
								
								resources/translation_override/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								resources/translation_override/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| In this directory, you find an example of file for the command `chill:main:override_translation`. | ||||
|  | ||||
| This file contains a list of translations to override in the translation catalogue. Each entry is a dictionary with two keys: `from` and `to`. The `from` key contains the original translation string, and the `to` key contains the replacement string. | ||||
|  | ||||
| The command `chill:main:override_translation` uses this file to generate a new translation catalogue with the specified overrides applied. | ||||
|  | ||||
							
								
								
									
										8
									
								
								resources/translation_override/overrides.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								resources/translation_override/overrides.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| - {from: "de l'usager", to: "du patient"} | ||||
| - {from: "l'usager", to: "le patient"} | ||||
| - {from: "L'usager", to: "Le patient"} | ||||
| - {from: "d'usagers", to: "de patients"} | ||||
| - {from: "usagers", to: "patients"} | ||||
| - {from: "Usagers", to: "Patients"} | ||||
| - {from: "usager", to: "patient"} | ||||
| - {from: "Usager", to: "Patient"} | ||||
| @@ -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> | ||||
|  | ||||
|   | ||||
| @@ -94,7 +94,7 @@ class StoredObject implements Document, TrackCreationInterface | ||||
|     /** | ||||
|      * @var Collection<int, StoredObjectVersion>&Selectable<int, StoredObjectVersion> | ||||
|      */ | ||||
|     #[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true)] | ||||
|     #[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true, fetch: 'EAGER')] | ||||
|     private Collection&Selectable $versions; | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -6,20 +6,20 @@ const algo = "AES-CBC"; | ||||
| const URL_POST = "/asyncupload/temp_url/generate/post"; | ||||
|  | ||||
| const keyDefinition = { | ||||
|     name: algo, | ||||
|     length: 256, | ||||
|   name: algo, | ||||
|   length: 256, | ||||
| }; | ||||
|  | ||||
| const createFilename = (): string => { | ||||
|     let text = ""; | ||||
|     const possible = | ||||
|         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | ||||
|   let text = ""; | ||||
|   const possible = | ||||
|     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | ||||
|  | ||||
|     for (let i = 0; i < 7; i++) { | ||||
|         text += possible.charAt(Math.floor(Math.random() * possible.length)); | ||||
|     } | ||||
|   for (let i = 0; i < 7; i++) { | ||||
|     text += possible.charAt(Math.floor(Math.random() * possible.length)); | ||||
|   } | ||||
|  | ||||
|     return text; | ||||
|   return text; | ||||
| }; | ||||
|  | ||||
| /** | ||||
| @@ -30,59 +30,59 @@ const createFilename = (): string => { | ||||
|  * @returns {Promise<StoredObject>} A Promise that resolves to the newly created StoredObject. | ||||
|  */ | ||||
| export const fetchNewStoredObject = async (): Promise<StoredObject> => { | ||||
|     return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null); | ||||
|   return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null); | ||||
| }; | ||||
|  | ||||
| export const uploadVersion = async ( | ||||
|     uploadFile: ArrayBuffer, | ||||
|     storedObject: StoredObject, | ||||
|   uploadFile: ArrayBuffer, | ||||
|   storedObject: StoredObject, | ||||
| ): Promise<string> => { | ||||
|     const params = new URLSearchParams(); | ||||
|     params.append("expires_delay", "180"); | ||||
|     params.append("submit_delay", "180"); | ||||
|     const asyncData: PostStoreObjectSignature = await makeFetch( | ||||
|         "GET", | ||||
|         `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` + | ||||
|             "?" + | ||||
|             params.toString(), | ||||
|     ); | ||||
|     const suffix = createFilename(); | ||||
|     const filename = asyncData.prefix + suffix; | ||||
|     const formData = new FormData(); | ||||
|     formData.append("redirect", asyncData.redirect); | ||||
|     formData.append("max_file_size", asyncData.max_file_size.toString()); | ||||
|     formData.append("max_file_count", asyncData.max_file_count.toString()); | ||||
|     formData.append("expires", asyncData.expires.toString()); | ||||
|     formData.append("signature", asyncData.signature); | ||||
|     formData.append(filename, new Blob([uploadFile]), suffix); | ||||
|   const params = new URLSearchParams(); | ||||
|   params.append("expires_delay", "180"); | ||||
|   params.append("submit_delay", "180"); | ||||
|   const asyncData: PostStoreObjectSignature = await makeFetch( | ||||
|     "GET", | ||||
|     `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` + | ||||
|       "?" + | ||||
|       params.toString(), | ||||
|   ); | ||||
|   const suffix = createFilename(); | ||||
|   const filename = asyncData.prefix + suffix; | ||||
|   const formData = new FormData(); | ||||
|   formData.append("redirect", asyncData.redirect); | ||||
|   formData.append("max_file_size", asyncData.max_file_size.toString()); | ||||
|   formData.append("max_file_count", asyncData.max_file_count.toString()); | ||||
|   formData.append("expires", asyncData.expires.toString()); | ||||
|   formData.append("signature", asyncData.signature); | ||||
|   formData.append(filename, new Blob([uploadFile]), suffix); | ||||
|  | ||||
|     const response = await window.fetch(asyncData.url, { | ||||
|         method: "POST", | ||||
|         body: formData, | ||||
|     }); | ||||
|   const response = await window.fetch(asyncData.url, { | ||||
|     method: "POST", | ||||
|     body: formData, | ||||
|   }); | ||||
|  | ||||
|     if (!response.ok) { | ||||
|         console.error("Error while sending file to store", response); | ||||
|         throw new Error(response.statusText); | ||||
|     } | ||||
|   if (!response.ok) { | ||||
|     console.error("Error while sending file to store", response); | ||||
|     throw new Error(response.statusText); | ||||
|   } | ||||
|  | ||||
|     return Promise.resolve(filename); | ||||
|   return Promise.resolve(filename); | ||||
| }; | ||||
|  | ||||
| export const encryptFile = async ( | ||||
|     originalFile: ArrayBuffer, | ||||
|   originalFile: ArrayBuffer, | ||||
| ): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => { | ||||
|     const iv = crypto.getRandomValues(new Uint8Array(16)); | ||||
|     const key = await window.crypto.subtle.generateKey(keyDefinition, true, [ | ||||
|         "encrypt", | ||||
|         "decrypt", | ||||
|     ]); | ||||
|     const exportedKey = await window.crypto.subtle.exportKey("jwk", key); | ||||
|     const encrypted = await window.crypto.subtle.encrypt( | ||||
|         { name: algo, iv: iv }, | ||||
|         key, | ||||
|         originalFile, | ||||
|     ); | ||||
|   const iv = crypto.getRandomValues(new Uint8Array(16)); | ||||
|   const key = await window.crypto.subtle.generateKey(keyDefinition, true, [ | ||||
|     "encrypt", | ||||
|     "decrypt", | ||||
|   ]); | ||||
|   const exportedKey = await window.crypto.subtle.exportKey("jwk", key); | ||||
|   const encrypted = await window.crypto.subtle.encrypt( | ||||
|     { name: algo, iv: iv }, | ||||
|     key, | ||||
|     originalFile, | ||||
|   ); | ||||
|  | ||||
|     return Promise.resolve([encrypted, iv, exportedKey]); | ||||
|   return Promise.resolve([encrypted, iv, exportedKey]); | ||||
| }; | ||||
|   | ||||
| @@ -2,9 +2,9 @@ import { fetchResults } from "ChillMainAssets/lib/api/apiMethods"; | ||||
| import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc"; | ||||
|  | ||||
| export function fetch_generic_docs_by_accompanying_period( | ||||
|     periodId: number, | ||||
|   periodId: number, | ||||
| ): Promise<GenericDocForAccompanyingPeriod[]> { | ||||
|     return fetchResults( | ||||
|         `/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`, | ||||
|     ); | ||||
|   return fetchResults( | ||||
|     `/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`, | ||||
|   ); | ||||
| } | ||||
|   | ||||
| @@ -6,117 +6,116 @@ import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vue | ||||
| const i18n = _createI18n({}); | ||||
|  | ||||
| const startApp = ( | ||||
|     divElement: HTMLDivElement, | ||||
|     collectionEntry: null | HTMLLIElement, | ||||
|   divElement: HTMLDivElement, | ||||
|   collectionEntry: null | HTMLLIElement, | ||||
| ): void => { | ||||
|     console.log("app started", divElement); | ||||
|   console.log("app started", divElement); | ||||
|  | ||||
|     const inputTitle = collectionEntry?.querySelector("input[type='text']"); | ||||
|   const inputTitle = collectionEntry?.querySelector("input[type='text']"); | ||||
|  | ||||
|     const input_stored_object: HTMLInputElement | null = | ||||
|         divElement.querySelector("input[data-stored-object]"); | ||||
|     if (null === input_stored_object) { | ||||
|         throw new Error("input to stored object not found"); | ||||
|     } | ||||
|   const input_stored_object: HTMLInputElement | null = divElement.querySelector( | ||||
|     "input[data-stored-object]", | ||||
|   ); | ||||
|   if (null === input_stored_object) { | ||||
|     throw new Error("input to stored object not found"); | ||||
|   } | ||||
|  | ||||
|     let existingDoc: StoredObject | null = null; | ||||
|     if (input_stored_object.value !== "") { | ||||
|         existingDoc = JSON.parse(input_stored_object.value); | ||||
|     } | ||||
|     const app_container = document.createElement("div"); | ||||
|     divElement.appendChild(app_container); | ||||
|   let existingDoc: StoredObject | null = null; | ||||
|   if (input_stored_object.value !== "") { | ||||
|     existingDoc = JSON.parse(input_stored_object.value); | ||||
|   } | ||||
|   const app_container = document.createElement("div"); | ||||
|   divElement.appendChild(app_container); | ||||
|  | ||||
|     const app = createApp({ | ||||
|         template: | ||||
|             '<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>', | ||||
|         data() { | ||||
|             return { | ||||
|                 existingDoc: existingDoc, | ||||
|                 inputTitle: inputTitle, | ||||
|             }; | ||||
|         }, | ||||
|         components: { | ||||
|             DropFileWidget, | ||||
|         }, | ||||
|         methods: { | ||||
|             addDocument: function ({ | ||||
|                 stored_object, | ||||
|                 stored_object_version, | ||||
|                 file_name, | ||||
|             }: { | ||||
|                 stored_object: StoredObject; | ||||
|                 stored_object_version: StoredObjectVersion; | ||||
|                 file_name: string; | ||||
|             }): void { | ||||
|                 stored_object.title = file_name; | ||||
|                 console.log("object added", stored_object); | ||||
|                 console.log("version added", stored_object_version); | ||||
|                 this.$data.existingDoc = stored_object; | ||||
|                 this.$data.existingDoc.currentVersion = stored_object_version; | ||||
|                 input_stored_object.value = JSON.stringify( | ||||
|                     this.$data.existingDoc, | ||||
|                 ); | ||||
|                 if (this.$data.inputTitle) { | ||||
|                     if (!this.$data.inputTitle?.value) { | ||||
|                         this.$data.inputTitle.value = file_name; | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             removeDocument: function (object: StoredObject): void { | ||||
|                 console.log("catch remove document", object); | ||||
|                 input_stored_object.value = ""; | ||||
|                 this.$data.existingDoc = undefined; | ||||
|                 console.log("collectionEntry", collectionEntry); | ||||
|   const app = createApp({ | ||||
|     template: | ||||
|       '<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>', | ||||
|     data() { | ||||
|       return { | ||||
|         existingDoc: existingDoc, | ||||
|         inputTitle: inputTitle, | ||||
|       }; | ||||
|     }, | ||||
|     components: { | ||||
|       DropFileWidget, | ||||
|     }, | ||||
|     methods: { | ||||
|       addDocument: function ({ | ||||
|         stored_object, | ||||
|         stored_object_version, | ||||
|         file_name, | ||||
|       }: { | ||||
|         stored_object: StoredObject; | ||||
|         stored_object_version: StoredObjectVersion; | ||||
|         file_name: string; | ||||
|       }): void { | ||||
|         stored_object.title = file_name; | ||||
|         console.log("object added", stored_object); | ||||
|         console.log("version added", stored_object_version); | ||||
|         this.$data.existingDoc = stored_object; | ||||
|         this.$data.existingDoc.currentVersion = stored_object_version; | ||||
|         input_stored_object.value = JSON.stringify(this.$data.existingDoc); | ||||
|         if (this.$data.inputTitle) { | ||||
|           if (!this.$data.inputTitle?.value) { | ||||
|             this.$data.inputTitle.value = file_name; | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       removeDocument: function (object: StoredObject): void { | ||||
|         console.log("catch remove document", object); | ||||
|         input_stored_object.value = ""; | ||||
|         this.$data.existingDoc = undefined; | ||||
|         console.log("collectionEntry", collectionEntry); | ||||
|  | ||||
|                 if (null !== collectionEntry) { | ||||
|                     console.log("will remove collection"); | ||||
|                     collectionEntry.remove(); | ||||
|                 } | ||||
|             }, | ||||
|         }, | ||||
|     }); | ||||
|         if (null !== collectionEntry) { | ||||
|           console.log("will remove collection"); | ||||
|           collectionEntry.remove(); | ||||
|         } | ||||
|       }, | ||||
|     }, | ||||
|   }); | ||||
|  | ||||
|     app.use(i18n).mount(app_container); | ||||
|   app.use(i18n).mount(app_container); | ||||
| }; | ||||
| window.addEventListener("collection-add-entry", (( | ||||
|     e: CustomEvent<CollectionEventPayload>, | ||||
|   e: CustomEvent<CollectionEventPayload>, | ||||
| ) => { | ||||
|     const detail = e.detail; | ||||
|     const divElement: null | HTMLDivElement = detail.entry.querySelector( | ||||
|         "div[data-stored-object]", | ||||
|     ); | ||||
|   const detail = e.detail; | ||||
|   const divElement: null | HTMLDivElement = detail.entry.querySelector( | ||||
|     "div[data-stored-object]", | ||||
|   ); | ||||
|  | ||||
|     if (null === divElement) { | ||||
|         throw new Error("div[data-stored-object] not found"); | ||||
|     } | ||||
|   if (null === divElement) { | ||||
|     throw new Error("div[data-stored-object] not found"); | ||||
|   } | ||||
|  | ||||
|     startApp(divElement, detail.entry); | ||||
|   startApp(divElement, detail.entry); | ||||
| }) as EventListener); | ||||
|  | ||||
| window.addEventListener("DOMContentLoaded", () => { | ||||
|     const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll( | ||||
|         "div[data-stored-object]", | ||||
|     ); | ||||
|   const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll( | ||||
|     "div[data-stored-object]", | ||||
|   ); | ||||
|  | ||||
|     upload_inputs.forEach((input: HTMLDivElement): void => { | ||||
|         // test for a parent to check if this is a collection entry | ||||
|         let collectionEntry: null | HTMLLIElement = null; | ||||
|         const parent = input.parentElement; | ||||
|         console.log("parent", parent); | ||||
|         if (null !== parent) { | ||||
|             const grandParent = parent.parentElement; | ||||
|             console.log("grandParent", grandParent); | ||||
|             if (null !== grandParent) { | ||||
|                 if ( | ||||
|                     grandParent.tagName.toLowerCase() === "li" && | ||||
|                     grandParent.classList.contains("entry") | ||||
|                 ) { | ||||
|                     collectionEntry = grandParent as HTMLLIElement; | ||||
|                 } | ||||
|             } | ||||
|   upload_inputs.forEach((input: HTMLDivElement): void => { | ||||
|     // test for a parent to check if this is a collection entry | ||||
|     let collectionEntry: null | HTMLLIElement = null; | ||||
|     const parent = input.parentElement; | ||||
|     console.log("parent", parent); | ||||
|     if (null !== parent) { | ||||
|       const grandParent = parent.parentElement; | ||||
|       console.log("grandParent", grandParent); | ||||
|       if (null !== grandParent) { | ||||
|         if ( | ||||
|           grandParent.tagName.toLowerCase() === "li" && | ||||
|           grandParent.classList.contains("entry") | ||||
|         ) { | ||||
|           collectionEntry = grandParent as HTMLLIElement; | ||||
|         } | ||||
|         startApp(input, collectionEntry); | ||||
|     }); | ||||
|       } | ||||
|     } | ||||
|     startApp(input, collectionEntry); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| export {}; | ||||
|   | ||||
| @@ -9,26 +9,26 @@ import ToastPlugin from "vue-toast-notification"; | ||||
| const i18n = _createI18n({}); | ||||
|  | ||||
| window.addEventListener("DOMContentLoaded", function (e) { | ||||
|     document | ||||
|         .querySelectorAll<HTMLDivElement>("div[data-download-button-single]") | ||||
|         .forEach((el) => { | ||||
|             const storedObject = JSON.parse( | ||||
|                 el.dataset.storedObject as string, | ||||
|             ) as StoredObject; | ||||
|             const title = el.dataset.title as string; | ||||
|             const app = createApp({ | ||||
|                 components: { DownloadButton }, | ||||
|                 data() { | ||||
|                     return { | ||||
|                         storedObject, | ||||
|                         title, | ||||
|                         classes: { btn: true, "btn-outline-primary": true }, | ||||
|                     }; | ||||
|                 }, | ||||
|                 template: | ||||
|                     '<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>', | ||||
|             }); | ||||
|   document | ||||
|     .querySelectorAll<HTMLDivElement>("div[data-download-button-single]") | ||||
|     .forEach((el) => { | ||||
|       const storedObject = JSON.parse( | ||||
|         el.dataset.storedObject as string, | ||||
|       ) as StoredObject; | ||||
|       const title = el.dataset.title as string; | ||||
|       const app = createApp({ | ||||
|         components: { DownloadButton }, | ||||
|         data() { | ||||
|           return { | ||||
|             storedObject, | ||||
|             title, | ||||
|             classes: { btn: true, "btn-outline-primary": true }, | ||||
|           }; | ||||
|         }, | ||||
|         template: | ||||
|           '<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>', | ||||
|       }); | ||||
|  | ||||
|             app.use(i18n).use(ToastPlugin).mount(el); | ||||
|         }); | ||||
|       app.use(i18n).use(ToastPlugin).mount(el); | ||||
|     }); | ||||
| }); | ||||
|   | ||||
| @@ -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, | ||||
| }; | ||||
|   | ||||
| @@ -43,11 +43,17 @@ class StoredObjectVersionNormalizer implements NormalizerInterface, NormalizerAw | ||||
|             'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, [...$context, UserNormalizer::AT_DATE => $object->getCreatedAt()]), | ||||
|         ]; | ||||
|  | ||||
|         if (in_array(self::WITH_POINT_IN_TIMES_CONTEXT, $context[AbstractNormalizer::GROUPS] ?? [], true)) { | ||||
|         $normalizationGroups = $context[AbstractNormalizer::GROUPS] ?? []; | ||||
|  | ||||
|         if (is_string($normalizationGroups)) { | ||||
|             $normalizationGroups = [$normalizationGroups]; | ||||
|         } | ||||
|  | ||||
|         if (in_array(self::WITH_POINT_IN_TIMES_CONTEXT, $normalizationGroups, true)) { | ||||
|             $data['point-in-times'] = $this->normalizer->normalize($object->getPointInTimes(), $format, $context); | ||||
|         } | ||||
|  | ||||
|         if (in_array(self::WITH_RESTORED_CONTEXT, $context[AbstractNormalizer::GROUPS] ?? [], true)) { | ||||
|         if (in_array(self::WITH_RESTORED_CONTEXT, $normalizationGroups, true)) { | ||||
|             $data['from-restored'] = $this->normalizer->normalize($object->getCreatedFrom(), $format, [AbstractNormalizer::GROUPS => ['read']]); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -14,7 +14,6 @@ namespace Chill\MainBundle; | ||||
| use Chill\MainBundle\Cron\CronJobInterface; | ||||
| use Chill\MainBundle\CRUD\CompilerPass\CRUDControllerCompilerPass; | ||||
| use Chill\MainBundle\DependencyInjection\CompilerPass\ACLFlagsCompilerPass; | ||||
| use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass; | ||||
| use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass; | ||||
| use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass; | ||||
| use Chill\MainBundle\DependencyInjection\CompilerPass\TimelineCompilerClass; | ||||
| @@ -70,7 +69,6 @@ class ChillMainBundle extends Bundle | ||||
|         $container->addCompilerPass(new TimelineCompilerClass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); | ||||
|         $container->addCompilerPass(new WidgetsCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); | ||||
|         $container->addCompilerPass(new NotificationCounterCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); | ||||
|         $container->addCompilerPass(new MenuCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); | ||||
|         $container->addCompilerPass(new ACLFlagsCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); | ||||
|         $container->addCompilerPass(new CRUDControllerCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,115 @@ | ||||
| <?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\MainBundle\Command; | ||||
|  | ||||
| use Symfony\Component\Console\Command\Command; | ||||
| use Symfony\Component\Console\Input\InputArgument; | ||||
| use Symfony\Component\Console\Input\InputInterface; | ||||
| use Symfony\Component\Console\Output\OutputInterface; | ||||
| use Symfony\Component\HttpKernel\KernelInterface; | ||||
| use Symfony\Component\Translation\MessageCatalogue; | ||||
| use Symfony\Component\Translation\Reader\TranslationReaderInterface; | ||||
| use Symfony\Component\Translation\Writer\TranslationWriterInterface; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
|  | ||||
| class OverrideTranslationCommand extends Command | ||||
| { | ||||
|     public function __construct( | ||||
|         private readonly TranslationReaderInterface $reader, | ||||
|         private readonly TranslationWriterInterface $writer, | ||||
|     ) { | ||||
|         $this->setName('chill:main:override_translation'); | ||||
|         parent::__construct(); | ||||
|     } | ||||
|  | ||||
|     protected function configure(): void | ||||
|     { | ||||
|         $this | ||||
|             ->setDescription('Generate a translation catalogue with translation remplacements based on replacements provided in a YAML file.') | ||||
|             ->addArgument('locale', InputArgument::REQUIRED, 'The locale to process (e.g. fr, en).') | ||||
|             ->addArgument('overrides', InputArgument::REQUIRED, 'Path to the overrides YAML file (list of {from, to}).'); | ||||
|     } | ||||
|  | ||||
|     protected function execute(InputInterface $input, OutputInterface $output): int | ||||
|     { | ||||
|         $locale = (string) $input->getArgument('locale'); | ||||
|         $overridesPath = (string) $input->getArgument('overrides'); | ||||
|  | ||||
|         $catalogue = $this->loadCatalogue($locale); | ||||
|         $overrides = $this->loadOverrides($overridesPath); | ||||
|  | ||||
|  | ||||
|         $toOverrideCatalogue = new MessageCatalogue($locale); | ||||
|         foreach ($catalogue->getDomains() as $domain) { | ||||
|             // hack: we have to replace the suffix ".intl-icu" by "+intl-ic" | ||||
|             $domain = str_replace('.intl-icu', '+intl-icu', $domain); | ||||
|             foreach ($catalogue->all($domain) as $key => $translation) { | ||||
|                 foreach ($overrides as $changes) { | ||||
|                     $from = $changes['from']; | ||||
|                     $to = $changes['to']; | ||||
|  | ||||
|                     if (is_string($translation) && str_contains($translation, $from)) { | ||||
|                         $newTranslation = strtr($translation, [$from => $to]); | ||||
|                         $toOverrideCatalogue->set($key, $newTranslation, $domain); | ||||
|                         $translation = $newTranslation; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /** @var KernelInterface $kernel */ | ||||
|         /* @phpstan-ignore-next-line */ | ||||
|         $kernel = $this->getApplication()->getKernel(); | ||||
|         $outputDir = rtrim($kernel->getProjectDir(), '/').'/translations'; | ||||
|         if (!is_dir($outputDir)) { | ||||
|             @mkdir($outputDir, 0775, true); | ||||
|         } | ||||
|  | ||||
|         // Writer expects the 'path' option to be a directory; it will create the proper file name | ||||
|         $this->writer->write($toOverrideCatalogue, 'yaml', ['path' => $outputDir]); | ||||
|  | ||||
|         $output->writeln(sprintf('Override catalogue written to %s (domain: messages, locale: %s).', $outputDir, $locale)); | ||||
|  | ||||
|         return Command::SUCCESS; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return list<array{from: string, to: string}> | ||||
|      */ | ||||
|     private function loadOverrides(string $path): array | ||||
|     { | ||||
|         return Yaml::parseFile($path); | ||||
|     } | ||||
|  | ||||
|     private function loadCatalogue(string $locale): MessageCatalogue | ||||
|     { | ||||
|         /** @var KernelInterface $kernel */ | ||||
|         /* @phpstan-ignore-next-line */ | ||||
|         $kernel = $this->getApplication()->getKernel(); | ||||
|  | ||||
|         // collect path for translations | ||||
|         $transPaths = []; | ||||
|         foreach ($kernel->getBundles() as $bundle) { | ||||
|             $bundleDir = $bundle->getPath(); | ||||
|             $transPaths[] = is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundle->getPath().'/translations'; | ||||
|         } | ||||
|  | ||||
|         $currentCatalogue = new MessageCatalogue($locale); | ||||
|         foreach ($transPaths as $path) { | ||||
|             if (is_dir($path)) { | ||||
|                 $this->reader->read($path, $currentCatalogue); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return $currentCatalogue; | ||||
|     } | ||||
| } | ||||
| @@ -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`, | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -5,58 +5,63 @@ export type fetchOption = Record<string, boolean | string | number | null>; | ||||
|  | ||||
| export type Params = Record<string, number | string>; | ||||
|  | ||||
| export interface Pagination { | ||||
|   first: number; | ||||
|   items_per_page: number; | ||||
|   more: boolean; | ||||
|   next: string | null; | ||||
|   previous: string | null; | ||||
| } | ||||
|  | ||||
| export interface PaginationResponse<T> { | ||||
|     pagination: { | ||||
|         more: boolean; | ||||
|         items_per_page: number; | ||||
|     }; | ||||
|     results: T[]; | ||||
|     count: number; | ||||
|   pagination: Pagination; | ||||
|   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 +71,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(); | ||||
| }; | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { TranslatableString } from "ChillMainAssets/types"; | ||||
| import { DateTime, TranslatableString } from "ChillMainAssets/types"; | ||||
| import { getLocale } from "translator"; | ||||
|  | ||||
| /** | ||||
|  * Localizes a translatable string object based on the current locale. | ||||
| @@ -8,34 +9,97 @@ import { TranslatableString } from "ChillMainAssets/types"; | ||||
|  * @param locale defaults to browser locale | ||||
|  * @returns The localized string or null if no translation is available | ||||
|  */ | ||||
| export function localizeString( | ||||
|     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]]; | ||||
|     } | ||||
|  | ||||
|     return ""; | ||||
| /** | ||||
|  * Prepends the current HTML lang code to the given URL. | ||||
|  * Example: If lang="fr" and url="/about", returns "/fr/about" | ||||
|  * | ||||
|  * @param url The URL to localize | ||||
|  * @returns The localized URL | ||||
|  */ | ||||
| export function localizedUrl(url: string): string { | ||||
|   const locale = getLocale(); | ||||
|   // Ensure url starts with a slash and does not already start with /{lang}/ | ||||
|   const normalizedUrl = url.startsWith("/") ? url : `/${url}`; | ||||
|   const langPrefix = `/${locale}`; | ||||
|   if (normalizedUrl.startsWith(langPrefix + "/")) { | ||||
|     return normalizedUrl; | ||||
|   } | ||||
|   return `${langPrefix}${normalizedUrl}`; | ||||
| } | ||||
|  | ||||
| export function localizeString( | ||||
|   translatableString: TranslatableString | null | undefined, | ||||
|   locale?: string, | ||||
| ): string { | ||||
|   if (!translatableString || Object.keys(translatableString).length === 0) { | ||||
|     return ""; | ||||
|   } | ||||
|  | ||||
|   const currentLocale = locale || getLocale(); | ||||
|  | ||||
|   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 ""; | ||||
| } | ||||
|  | ||||
| const datetimeFormats: Record< | ||||
|   string, | ||||
|   Record<string, Intl.DateTimeFormatOptions> | ||||
| > = { | ||||
|   fr: { | ||||
|     short: { | ||||
|       year: "numeric", | ||||
|       month: "numeric", | ||||
|       day: "numeric", | ||||
|     }, | ||||
|     text: { | ||||
|       year: "numeric", | ||||
|       month: "long", | ||||
|       day: "numeric", | ||||
|     }, | ||||
|     long: { | ||||
|       year: "numeric", | ||||
|       month: "numeric", | ||||
|       day: "numeric", | ||||
|       hour: "numeric", | ||||
|       minute: "numeric", | ||||
|       hour12: false, | ||||
|     }, | ||||
|     hoursOnly: { | ||||
|       hour: "numeric", | ||||
|       minute: "numeric", | ||||
|       hour12: false, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| export function localizeDateTimeFormat( | ||||
|   dateTime: DateTime, | ||||
|   format: keyof typeof datetimeFormats.fr = "short", | ||||
| ): string { | ||||
|   const locale = getLocale(); | ||||
|   const options = | ||||
|     datetimeFormats[locale]?.[format] || datetimeFormats.fr[format]; | ||||
|   return new Intl.DateTimeFormat(locale, options).format( | ||||
|     new Date(dateTime.datetime), | ||||
|   ); | ||||
| } | ||||
|  | ||||
| export default datetimeFormats; | ||||
|   | ||||
| @@ -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); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user