mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 09:18:24 +00:00 
			
		
		
		
	Compare commits
	
		
			255 Commits
		
	
	
		
			issue386_d
			...
			20-update-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | bddb6e615e | ||
|  | a18ea30c8f | ||
|  | 2ba240525c | ||
|  | 5b76338fb0 | ||
|  | ce8399945a | ||
|  | 1605bc5d08 | ||
|  | ebdcc5a07d | ||
|  | 0f31a49890 | ||
| 170ce2d51c | |||
| 971789d5cf | |||
| 72815e4b70 | |||
| 44183957b8 | |||
| 5f6cffa08a | |||
| 222dae3c32 | |||
| 08c13b8c98 | |||
| 1bc2500b28 | |||
| f648a9351b | |||
| ffe466a334 | |||
|  | 3978e7c959 | ||
|  | 2eb5c45a4d | ||
|  | fefe208260 | ||
| c14101714c | |||
| 8ec5636c57 | |||
| fba7060a91 | |||
| 6894fa7101 | |||
| 41fa1d9ca6 | |||
| f4bca2f410 | |||
| 6198891202 | |||
| c806c06279 | |||
| e04c02055c | |||
| ac5675933d | |||
| e7db71b0f3 | |||
|  | 1967fc4bed | ||
|  | befd5dac42 | ||
| 186b8847d9 | |||
| 90d0cbc3b1 | |||
| ac3d39b151 | |||
| efb5bd64b4 | |||
| 76a7b019eb | |||
| 230d73498e | |||
|  | a68a43adc0 | ||
|  | 5a75e38f33 | ||
| 1c02eb23fd | |||
| 1ba5df1280 | |||
| 73c4a5d39d | |||
| 0a58c43952 | |||
| a3b823d33f | |||
| de45555c5a | |||
| 0add020e57 | |||
| 84a76d2c76 | |||
| d88207132b | |||
| 932d0e86d9 | |||
| 671f1223b5 | |||
| 1a04d903fc | |||
| 125dd4d980 | |||
| 2ddab027ed | |||
| 1724400d7c | |||
| d1a0934bb1 | |||
| bc90664480 | |||
| 99dc7490cb | |||
| 5ac485e06e | |||
| dc184762d6 | |||
| 86e7b0f007 | |||
| fdafe7c82b | |||
| d285d84068 | |||
| da22532587 | |||
| b83ad77fd8 | |||
| 00ac6aa1b9 | |||
| 318c001904 | |||
| e2d406b97b | |||
|  | efff496f7a | ||
| fe4eaa92be | |||
| cb17a7e8a5 | |||
| ad4153a07e | |||
| e95093f144 | |||
| 59cdf07c7e | |||
| 292c9380ad | |||
| 701ad17299 | |||
| 7f41f14959 | |||
| fcd5fba13e | |||
| 2c57eab4d2 | |||
| 2cd51eed2e | |||
| 72e306b583 | |||
| 917dd49d07 | |||
| 71ca912749 | |||
| 4620e32b82 | |||
| 0cda5d637d | |||
| 646f39b9ed | |||
|  | 105fe16122 | ||
|  | 4d1a553474 | ||
| 5bfdee0c28 | |||
| ae33392ad0 | |||
| 0e0fe90277 | |||
| 792eafce72 | |||
| 616d01a6c9 | |||
|  | 21edc74ada | ||
| ab23290599 | |||
| 1a2e1eaf2a | |||
| feaee8a0b1 | |||
| 0a26e7f326 | |||
| ebf85d4e4d | |||
| 72cffd7a3d | |||
| 65418b17ce | |||
|  | 8432c215a3 | ||
|  | 87e1f8f077 | ||
|  | 33f393203e | ||
| 6aa1e136b4 | |||
| 10a9b6c909 | |||
| ce912e4405 | |||
| ab11d3e8b3 | |||
|  | 445fe0cee4 | ||
| 7c6c8de456 | |||
| 0378df42b6 | |||
| ad8d40cb1c | |||
| f4516f8369 | |||
| 5b25f82963 | |||
| 8720cc730e | |||
| 44d1535bfb | |||
| 8d663cdee6 | |||
| 7c8b08c3a7 | |||
| 6df570d96c | |||
| 3ce650eea6 | |||
| 6ef7d9b47b | |||
| af3847366b | |||
| d70f8aa712 | |||
| 14463dcd38 | |||
| b9c2d63a53 | |||
| 3fd4c6339a | |||
| 4d76de5f9f | |||
| 0a92ad905b | |||
| 7af7135971 | |||
| c70a4dc664 | |||
| dc0fae7549 | |||
| b38924cc3d | |||
| 58c4e37116 | |||
| 3656825d20 | |||
| 39a7cecd24 | |||
| 6d382f93e8 | |||
| 6e554e74ab | |||
| 4c125865cf | |||
| 12c68a4c9f | |||
| 66ac5c7435 | |||
| e4b46a188c | |||
| 7a78e8a6a7 | |||
| 4496836cb2 | |||
| 88d1fe24b4 | |||
| 0ea1708fe9 | |||
| a9b7cf93e9 | |||
| 5d36e56641 | |||
| f00dfad98e | |||
| 7943c22ae7 | |||
| d365fcea39 | |||
| 9f9f9d7881 | |||
| 20f814766c | |||
| ed00a882af | |||
| 5d7501d36e | |||
| ab5f9923a6 | |||
| 1a5e8b9d7b | |||
| f7f2bb0caa | |||
| bf7fcfa41a | |||
| 9056ea8449 | |||
| f0e349a2b0 | |||
| 2ccc6f1976 | |||
| cf041cf49e | |||
| 72d593e416 | |||
| 287dca07aa | |||
| 910f9a7e8e | |||
| 9457483a6b | |||
| 6d07168529 | |||
| 420f81ea24 | |||
| 416b62fc60 | |||
| ecae7dab44 | |||
| 126bd1a4eb | |||
| 450792e446 | |||
| ec155b1d67 | |||
| 02b2af7d51 | |||
| 0a6a2c968c | |||
| 4b188e2df6 | |||
| d6bf1988f6 | |||
| 94c9505c05 | |||
| 4a5a1440ff | |||
| b31cc460fa | |||
| 48c3432191 | |||
| 7b4405f6fe | |||
| ce3a74326d | |||
| 51ab013bd7 | |||
| 41a6366efe | |||
| c3c2fd30f0 | |||
| 1952e4aa5a | |||
| 1e1311b7c8 | |||
| fc1ed8b71e | |||
| 2144b247b3 | |||
| e4629ed599 | |||
| f27dca6fb5 | |||
| 8c27ec0720 | |||
| 8392e2e6bd | |||
| 963b0b1ed2 | |||
|  | 7513187a6d | ||
|  | 990fc0e484 | ||
| 8fce27a128 | |||
| 55c2c41cea | |||
| 0bd10b6dfa | |||
| 8cd01d4317 | |||
| 71293e3378 | |||
| cb58afa44a | |||
| 7b3e9cb490 | |||
| fb720a382f | |||
| a4bfa1cb16 | |||
| 502f2aceed | |||
|  | eb9cc0b63f | ||
|  | 7ee554b48d | ||
|  | 2e6d281bfc | ||
|  | 4b1bf7ce23 | ||
|  | 2d574fea2e | ||
| 0e2772336f | |||
| 320d11671a | |||
| dec26e2ba4 | |||
| 514d75fa5f | |||
| 0ac51da31a | |||
| 90d43b0bd3 | |||
|  | 25ee11e4ff | ||
|  | df661fc7c4 | ||
|  | 6b78c06dfb | ||
|  | d087f051f0 | ||
|  | b3edba3abe | ||
|  | 02d8ceba25 | ||
|  | 741043c177 | ||
| c42f62de4c | |||
| e23ef35b75 | |||
| eccc75aecf | |||
| 7e2fbf93f9 | |||
| 6ab8f95f7d | |||
| 1faa9812db | |||
| a786578cbe | |||
| c08bd76e4b | |||
| 20a4950c60 | |||
| 92788ccdc0 | |||
| 84993ca05b | |||
| 972657b38e | |||
| b26d0905a3 | |||
| 4ab0a28f7d | |||
| 52009bd7c6 | |||
| 7acd6c7fc3 | |||
| f26cba415b | |||
| 8a2e588190 | |||
| d63d9c2f4e | |||
| dcdee1d6e3 | |||
| dd589af1be | |||
| 2bdf116698 | |||
| 96dc711b4f | |||
| 001e2f9622 | |||
| 58ac4b01ef | |||
| 6e4bfd45c6 | |||
| ff0b0678a5 | |||
| 3b56cc818b | 
							
								
								
									
										45
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -11,9 +11,48 @@ and this project adheres to | ||||
| ## Unreleased | ||||
|  | ||||
| <!-- write down unreleased development here --> | ||||
| * renommer "dossier numéro" en "parcours numéro" dans les résultats de recherche | ||||
| * renomme date de début en date d'ouverture dans le formulaire parcours | ||||
|  | ||||
|  | ||||
| ## Test releases | ||||
|  | ||||
| ### test release 2021-01-31 | ||||
|  | ||||
| [fast_actions] improve fast-actions buttons override mechanism, fix https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/413 | ||||
| [homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc.   | ||||
| * [person] accompanying course: optimisation: do not fetch some resources for the banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/409) | ||||
| * [person] accompanying course: close modal when edit participation (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/420) | ||||
| * [person] accompanying course: treat validation error when editing on-the-fly entities (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/420) | ||||
| * [activity] show activity attendee (présence) in the activity list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/412) | ||||
| * [activity] admin: change validation rule for social action visible field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/413) | ||||
| * [parcours]: component added to change the opening date of a parcours (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/411) | ||||
| * [search]: listing of parcours display changed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/410) | ||||
| * [user]: page with accompanying periods to which is user is referent (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/408) | ||||
| * [person] age added to renderstring + renderbox/ vue component created to display person text (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/389) | ||||
| * [household member editor] allow to push to existing household | ||||
| * [workflow][notification] improve how notifications and workflows are 'attached' to entities: contextual list, counter, buttons and vue modal | ||||
|  | ||||
|  | ||||
| ### test release 2021-01-28 | ||||
|  | ||||
| * [person] improve filiations vis graph: disable physics, use chill colors for persons-households-course, increase label of relations, remove labels on household arrows and other improvements (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/286, https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/362) | ||||
| * [activity] Order activity by date and by id (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/364) | ||||
| * [main] increase length of 4 Address fields (change to TEXT, no size limits) (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/277) | ||||
| * [main] Add confidential option for address, in edit and view (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/165) | ||||
| * [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377) | ||||
| * [person] Add residential address entity, form and list for each person | ||||
| * [aside_activity]: dynamicUserPickerType used (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/399) | ||||
| * dispatching list | ||||
|  | ||||
|  | ||||
| ### test release 2021-01-26 | ||||
|  | ||||
| * [parcours] comments truncated if too long + link added (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/406) | ||||
| * [person]: possibility to add person resources (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/382) | ||||
| * [person ressources]: module added | ||||
|  | ||||
|  | ||||
| ### test release 2022-01-24 | ||||
|  | ||||
| * [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377) | ||||
| @@ -32,7 +71,6 @@ and this project adheres to | ||||
|   * add My workflow section with my opened subscriptions | ||||
|   * apply workflow on documents, accompanyingCourseWork and Evaluations | ||||
| * [wopi-link] a new vue component allow to open wopi link in a fullscreen chill-themed modal | ||||
| <!-- end --> | ||||
|  | ||||
| ### test release 2022-01-19 | ||||
| * vuejs: add dead information on all on-the-fly person render boxes, in vis graph and other templates (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/271) | ||||
| @@ -40,6 +78,7 @@ and this project adheres to | ||||
| * [main] location form type: fix unmapped address field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/246) | ||||
| * [activity] fix wrong import of js assets for adding and viewing documents in activity (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/83 & https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/176) | ||||
| * [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380) | ||||
| * [forms] dynamic picker types for user/person/thirdparty types created (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/386) | ||||
|  | ||||
| ### test release 2022-01-17 | ||||
|  | ||||
| @@ -49,10 +88,14 @@ and this project adheres to | ||||
| * [main] Add mainLocation field to User entity and add it in user form type | ||||
| * [course list in person context] show full username/label for ref | ||||
| * [accompanying period work] remove the possibility to  generate document from an accompanying period work | ||||
| * vuejs: add validation on required fields for AddPerson, Address and Location components | ||||
| * vuejs: treat 422 validation errors in locations and AddPerson components | ||||
| * [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380) | ||||
|  | ||||
| ## Test releases | ||||
| * vuejs: add validation on required fields for AddPerson, Address and Location components | ||||
| * vuejs: treat 422 validation errors in locations and AddPerson components | ||||
| * [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380) | ||||
|  | ||||
| ### test release 2022-01-12 | ||||
|  | ||||
|   | ||||
| @@ -52,9 +52,6 @@ | ||||
|         "twig/string-extra": "^3.3", | ||||
|         "twig/twig": "^3.0" | ||||
|     }, | ||||
|     "conflict": { | ||||
|         "symfony/symfony": "*" | ||||
|     }, | ||||
|     "require-dev": { | ||||
|         "doctrine/doctrine-fixtures-bundle": "^3.3", | ||||
|         "drupol/php-conventions": "^5", | ||||
| @@ -71,23 +68,17 @@ | ||||
|         "symfony/var-dumper": "^4.4", | ||||
|         "symfony/web-profiler-bundle": "^4.4" | ||||
|     }, | ||||
|     "config": { | ||||
|         "bin-dir": "bin", | ||||
|         "optimize-autoloader": true, | ||||
|         "sort-packages": true, | ||||
|         "vendor-dir": "tests/app/vendor", | ||||
|         "allow-plugins": { | ||||
|             "composer/package-versions-deprecated": true, | ||||
|             "phpstan/extension-installer": true, | ||||
|             "ergebnis/composer-normalize": true, | ||||
|             "phpro/grumphp": true | ||||
|         } | ||||
|     "conflict": { | ||||
|         "symfony/symfony": "*" | ||||
|     }, | ||||
|     "autoload": { | ||||
|         "psr-4": { | ||||
|             "Chill\\ActivityBundle\\": "src/Bundle/ChillActivityBundle", | ||||
|             "Chill\\AsideActivityBundle\\": "src/Bundle/ChillAsideActivityBundle/src", | ||||
|             "Chill\\BudgetBundle\\": "src/Bundle/ChillBudgetBundle", | ||||
|             "Chill\\CalendarBundle\\": "src/Bundle/ChillCalendarBundle", | ||||
|             "Chill\\CustomFieldsBundle\\": "src/Bundle/ChillCustomFieldsBundle", | ||||
|             "Chill\\DocGeneratorBundle\\": "src/Bundle/ChillDocGeneratorBundle", | ||||
|             "Chill\\DocStoreBundle\\": "src/Bundle/ChillDocStoreBundle", | ||||
|             "Chill\\EventBundle\\": "src/Bundle/ChillEventBundle", | ||||
|             "Chill\\FamilyMemberBundle\\": "src/Bundle/ChillFamilyMemberBundle", | ||||
| @@ -96,9 +87,6 @@ | ||||
|             "Chill\\ReportBundle\\": "src/Bundle/ChillReportBundle", | ||||
|             "Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle", | ||||
|             "Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle", | ||||
|             "Chill\\AsideActivityBundle\\": "src/Bundle/ChillAsideActivityBundle/src", | ||||
|             "Chill\\DocGeneratorBundle\\": "src/Bundle/ChillDocGeneratorBundle", | ||||
|             "Chill\\CalendarBundle\\": "src/Bundle/ChillCalendarBundle", | ||||
|             "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src" | ||||
|         } | ||||
|     }, | ||||
| @@ -111,10 +99,10 @@ | ||||
|     "config": { | ||||
|         "allow-plugins": { | ||||
|             "composer/package-versions-deprecated": true, | ||||
|             "phpstan/extension-installer": true, | ||||
|             "ergebnis/composer-normalize": true, | ||||
|             "ocramius/package-versions": true, | ||||
|             "phpro/grumphp": true, | ||||
|             "ocramius/package-versions": true | ||||
|             "phpstan/extension-installer": true | ||||
|         }, | ||||
|         "bin-dir": "bin", | ||||
|         "optimize-autoloader": true, | ||||
| @@ -123,8 +111,8 @@ | ||||
|     }, | ||||
|     "scripts": { | ||||
|         "auto-scripts": { | ||||
|             "cache:clear": "symfony-cmd", | ||||
|             "assets:install %PUBLIC_DIR%": "symfony-cmd" | ||||
|             "assets:install %PUBLIC_DIR%": "symfony-cmd", | ||||
|             "cache:clear": "symfony-cmd" | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,3 +0,0 @@ | ||||
| add npm/yarn dependency in package.json : | ||||
|  | ||||
| "select2-bootstrap-theme": "0.1.0-beta.10", | ||||
| @@ -80,11 +80,6 @@ parameters: | ||||
| 			count: 1 | ||||
| 			path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php | ||||
|  | ||||
| 		- | ||||
| 			message: "#^Foreach overwrites \\$action with its value variable\\.$#" | ||||
| 			count: 1 | ||||
| 			path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php | ||||
|  | ||||
| 		- | ||||
| 			message: "#^Foreach overwrites \\$action with its value variable\\.$#" | ||||
| 			count: 1 | ||||
|   | ||||
| @@ -30,36 +30,6 @@ parameters: | ||||
|             count: 2 | ||||
|             path: src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php | ||||
|  | ||||
|         - | ||||
|             message: "#^Parameter \\$action of method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkRepository\\:\\:buildQueryBySocialActionWithDescendants\\(\\) has invalid type Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\SocialAction\\.$#" | ||||
|             count: 1 | ||||
|             path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php | ||||
|  | ||||
|         - | ||||
|             message: "#^Parameter \\$action of method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkRepository\\:\\:countBySocialActionWithDescendants\\(\\) has invalid type Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\SocialAction\\.$#" | ||||
|             count: 1 | ||||
|             path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php | ||||
|  | ||||
|         - | ||||
|             message: "#^Undefined variable\\: \\$action$#" | ||||
|             count: 1 | ||||
|             path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php | ||||
|  | ||||
|         - | ||||
|             message: "#^Undefined variable\\: \\$limit$#" | ||||
|             count: 1 | ||||
|             path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php | ||||
|  | ||||
|         - | ||||
|             message: "#^Undefined variable\\: \\$offset$#" | ||||
|             count: 1 | ||||
|             path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php | ||||
|  | ||||
|         - | ||||
|             message: "#^Undefined variable\\: \\$orderBy$#" | ||||
|             count: 1 | ||||
|             path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php | ||||
|  | ||||
|         - | ||||
|             message: "#^Variable variables are not allowed\\.$#" | ||||
|             count: 4 | ||||
|   | ||||
| @@ -400,11 +400,6 @@ parameters: | ||||
| 			count: 1 | ||||
| 			path: src/Bundle/ChillPersonBundle/Form/Type/PersonPhoneType.php | ||||
|  | ||||
| 		- | ||||
| 			message: "#^Method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkRepository\\:\\:buildQueryBySocialActionWithDescendants\\(\\) has invalid return type Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\QueryBuilder\\.$#" | ||||
| 			count: 1 | ||||
| 			path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php | ||||
|  | ||||
| 		- | ||||
| 			message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" | ||||
| 			count: 3 | ||||
|   | ||||
| @@ -256,7 +256,7 @@ final class ActivityController extends AbstractController | ||||
|         if ($person instanceof Person) { | ||||
|             $this->denyAccessUnlessGranted(ActivityVoter::SEE, $person); | ||||
|             $activities = $this->activityACLAwareRepository | ||||
|                 ->findByPerson($person, ActivityVoter::SEE, 0, null); | ||||
|                 ->findByPerson($person, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']); | ||||
|  | ||||
|             $event = new PrivacyEvent($person, [ | ||||
|                 'element_class' => Activity::class, | ||||
| @@ -269,7 +269,7 @@ final class ActivityController extends AbstractController | ||||
|             $this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod); | ||||
|  | ||||
|             $activities = $this->activityACLAwareRepository | ||||
|                 ->findByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE); | ||||
|                 ->findByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']); | ||||
|  | ||||
|             $view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig'; | ||||
|         } | ||||
|   | ||||
| @@ -12,7 +12,6 @@ declare(strict_types=1); | ||||
| namespace Chill\ActivityBundle\Entity; | ||||
|  | ||||
| use Chill\ActivityBundle\Validator\Constraints as ActivityValidator; | ||||
| use Chill\DocStoreBundle\Entity\Document; | ||||
| use Chill\DocStoreBundle\Entity\StoredObject; | ||||
| use Chill\MainBundle\Entity\Center; | ||||
| use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; | ||||
| @@ -62,7 +61,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac | ||||
|  | ||||
|     /** | ||||
|      * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod") | ||||
|      * @Groups({"read", "docgen:read"}) | ||||
|      * @Groups({"read"}) | ||||
|      */ | ||||
|     private ?AccompanyingPeriod $accompanyingPeriod = null; | ||||
|  | ||||
| @@ -76,16 +75,19 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac | ||||
|  | ||||
|     /** | ||||
|      * @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityPresence") | ||||
|      * @Groups({"docgen:read"}) | ||||
|      */ | ||||
|     private ?ActivityPresence $attendee = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="comment_") | ||||
|      * @Groups({"docgen:read"}) | ||||
|      */ | ||||
|     private CommentEmbeddable $comment; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="datetime") | ||||
|      * @Groups({"docgen:read"}) | ||||
|      */ | ||||
|     private DateTime $date; | ||||
|  | ||||
| @@ -101,6 +103,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="boolean", options={"default": false}) | ||||
|      * @Groups({"docgen:read"}) | ||||
|      */ | ||||
|     private bool $emergency = false; | ||||
|  | ||||
| @@ -131,16 +134,19 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac | ||||
|  | ||||
|     /** | ||||
|      * @ORM\ManyToMany(targetEntity="Chill\ActivityBundle\Entity\ActivityReason") | ||||
|      * @Groups({"docgen:read"}) | ||||
|      */ | ||||
|     private Collection $reasons; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Scope") | ||||
|      * @Groups({"docgen:read"}) | ||||
|      */ | ||||
|     private ?Scope $scope = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="string", options={"default": ""}) | ||||
|      * @Groups({"docgen:read"}) | ||||
|      */ | ||||
|     private string $sentReceived = ''; | ||||
|  | ||||
| @@ -171,12 +177,13 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac | ||||
|  | ||||
|     /** | ||||
|      * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") | ||||
|      * @Groups({"docgen:read"}) | ||||
|      */ | ||||
|     private User $user; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\User") | ||||
|      * @Groups({"read"}) | ||||
|      * @Groups({"read", "docgen:read"}) | ||||
|      */ | ||||
|     private ?Collection $users = null; | ||||
|  | ||||
| @@ -303,6 +310,18 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac | ||||
|         return $this->documents; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @Groups({"docgen:read"}) | ||||
|      */ | ||||
|     public function getDurationMinute(): int | ||||
|     { | ||||
|         if (null === $this->durationTime) { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         return (int) round(($this->durationTime->getTimestamp() + $this->durationTime->getOffset()) / 60.0, 0); | ||||
|     } | ||||
|  | ||||
|     public function getDurationTime(): ?DateTime | ||||
|     { | ||||
|         return $this->durationTime; | ||||
| @@ -403,6 +422,18 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac | ||||
|         return $this->travelTime; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @Groups({"docgen:read"}) | ||||
|      */ | ||||
|     public function getTravelTimeMinute(): int | ||||
|     { | ||||
|         if (null === $this->travelTime) { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         return (int) round(($this->travelTime->getTimestamp() + $this->travelTime->getOffset()) / 60.0, 0); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @deprecated | ||||
|      */ | ||||
|   | ||||
| @@ -12,6 +12,7 @@ declare(strict_types=1); | ||||
| namespace Chill\ActivityBundle\Entity; | ||||
|  | ||||
| use Doctrine\ORM\Mapping as ORM; | ||||
| use Symfony\Component\Serializer\Annotation as Serializer; | ||||
|  | ||||
| /** | ||||
|  * Class ActivityPresence. | ||||
| @@ -31,11 +32,14 @@ class ActivityPresence | ||||
|      * @ORM\Id | ||||
|      * @ORM\Column(name="id", type="integer") | ||||
|      * @ORM\GeneratedValue(strategy="AUTO") | ||||
|      * @Serializer\Groups({"docgen:read"}) | ||||
|      */ | ||||
|     private ?int $id; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="json") | ||||
|      * @Serializer\Groups({"docgen:read"}) | ||||
|      * @Serializer\Context({"is-translatable": true}, groups={"docgen:read"}) | ||||
|      */ | ||||
|     private array $name = []; | ||||
|  | ||||
|   | ||||
| @@ -13,8 +13,10 @@ namespace Chill\ActivityBundle\Entity; | ||||
|  | ||||
| use Doctrine\ORM\Mapping as ORM; | ||||
| use InvalidArgumentException; | ||||
| use Symfony\Component\Serializer\Annotation as Serializer; | ||||
| use Symfony\Component\Serializer\Annotation\Groups; | ||||
| use Symfony\Component\Validator\Constraints as Assert; | ||||
| use Symfony\Component\Validator\Context\ExecutionContextInterface; | ||||
|  | ||||
| /** | ||||
|  * Class ActivityType. | ||||
| @@ -118,6 +120,7 @@ class ActivityType | ||||
|      * @ORM\Id | ||||
|      * @ORM\Column(name="id", type="integer") | ||||
|      * @ORM\GeneratedValue(strategy="AUTO") | ||||
|      * @Groups({"docgen:read"}) | ||||
|      */ | ||||
|     private ?int $id; | ||||
|  | ||||
| @@ -133,7 +136,8 @@ class ActivityType | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="json") | ||||
|      * @Groups({"read"}) | ||||
|      * @Groups({"read", "docgen:read"}) | ||||
|      * @Serializer\Context({"is-translatable": true}, groups={"docgen:read"}) | ||||
|      */ | ||||
|     private array $name = []; | ||||
|  | ||||
| @@ -190,7 +194,6 @@ class ActivityType | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="smallint", nullable=false, options={"default": 1}) | ||||
|      * @Assert\EqualTo(propertyPath="socialIssuesVisible", message="This parameter must be equal to social issue parameter") | ||||
|      */ | ||||
|     private int $socialActionsVisible = self::FIELD_INVISIBLE; | ||||
|  | ||||
| @@ -260,6 +263,23 @@ class ActivityType | ||||
|      */ | ||||
|     private int $userVisible = self::FIELD_REQUIRED; | ||||
|  | ||||
|     /** | ||||
|      * @Assert\Callback | ||||
|      * | ||||
|      * @param mixed $payload | ||||
|      */ | ||||
|     public function checkSocialActionsVisibility(ExecutionContextInterface $context, $payload) | ||||
|     { | ||||
|         if ($this->socialIssuesVisible !== $this->socialActionsVisible) { | ||||
|             if (!($this->socialIssuesVisible === 2 && $this->socialActionsVisible === 1)) { | ||||
|                 $context | ||||
|                     ->buildViolation('The socialActionsVisible value is not compatible with the socialIssuesVisible value') | ||||
|                     ->atPath('socialActionsVisible') | ||||
|                     ->addViolation(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get active | ||||
|      * return true if the type is active. | ||||
|   | ||||
| @@ -34,6 +34,8 @@ p.date-label { | ||||
|    font-size: 18pt; | ||||
| } | ||||
| div.dashboard, | ||||
| h4.badge-title, | ||||
| h3.badge-title, | ||||
| h2.badge-title { | ||||
|    ul.list-content { | ||||
|       font-size: 70%; | ||||
|   | ||||
| @@ -1,45 +1,48 @@ | ||||
| <template> | ||||
|     <teleport to="#add-persons" v-if="isComponentVisible"> | ||||
|    <teleport to="#add-persons" v-if="isComponentVisible"> | ||||
|  | ||||
|         <div class="flex-bloc concerned-groups" :class="getContext"> | ||||
|             <persons-bloc | ||||
|                 v-for="bloc in contextPersonsBlocs" | ||||
|                 v-bind:key="bloc.key" | ||||
|                 v-bind:bloc="bloc" | ||||
|                 v-bind:blocWidth="getBlocWidth" | ||||
|                 v-bind:setPersonsInBloc="setPersonsInBloc"> | ||||
|             </persons-bloc> | ||||
|         </div> | ||||
|         <div v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0"> | ||||
|             <ul class="list-suggest add-items inline"> | ||||
|                 <li v-for="(p, i) in suggestedEntities" @click="addSuggestedEntity(p)" :key="`suggestedEntities-${i}`"> | ||||
|                     <span>{{ p.text }}</span> | ||||
|                 </li> | ||||
|             </ul> | ||||
|         </div> | ||||
|       <div class="flex-bloc concerned-groups" :class="getContext"> | ||||
|          <persons-bloc | ||||
|                v-for="bloc in contextPersonsBlocs" | ||||
|                v-bind:key="bloc.key" | ||||
|                v-bind:bloc="bloc" | ||||
|                v-bind:blocWidth="getBlocWidth" | ||||
|                v-bind:setPersonsInBloc="setPersonsInBloc"> | ||||
|          </persons-bloc> | ||||
|       </div> | ||||
|       <div v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0"> | ||||
|          <ul class="list-suggest add-items inline"> | ||||
|                <li v-for="(p, i) in suggestedEntities" @click="addSuggestedEntity(p)" :key="`suggestedEntities-${i}`"> | ||||
|                   <person-text v-if="p.type === 'person'" :person="p"></person-text> | ||||
|                   <span v-else>{{ p.text }}</span> | ||||
|                </li> | ||||
|          </ul> | ||||
|       </div> | ||||
|  | ||||
|         <add-persons | ||||
|             buttonTitle="activity.add_persons" | ||||
|             modalTitle="activity.add_persons" | ||||
|             v-bind:key="addPersons.key" | ||||
|             v-bind:options="addPersonsOptions" | ||||
|             @addNewPersons="addNewPersons" | ||||
|             ref="addPersons"> | ||||
|         </add-persons> | ||||
|       <add-persons | ||||
|          buttonTitle="activity.add_persons" | ||||
|          modalTitle="activity.add_persons" | ||||
|          v-bind:key="addPersons.key" | ||||
|          v-bind:options="addPersonsOptions" | ||||
|          @addNewPersons="addNewPersons" | ||||
|          ref="addPersons"> | ||||
|       </add-persons> | ||||
|  | ||||
|     </teleport> | ||||
|    </teleport> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapState, mapGetters } from 'vuex'; | ||||
| import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue'; | ||||
| import PersonsBloc from './ConcernedGroups/PersonsBloc.vue'; | ||||
| import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue'; | ||||
|  | ||||
| export default { | ||||
|    name: "ConcernedGroups", | ||||
|    components: { | ||||
|       AddPersons, | ||||
|       PersonsBloc | ||||
|       PersonsBloc, | ||||
|       PersonText | ||||
|    }, | ||||
|    data() { | ||||
|       return { | ||||
|   | ||||
| @@ -1,21 +1,29 @@ | ||||
| <template> | ||||
|    <li> | ||||
|       <span :title="person.text"> | ||||
|          <span class="chill_denomination" @click.prevent="$emit('remove', person)">{{ textCutted }}</span> | ||||
|          <span class="chill_denomination" @click.prevent="$emit('remove', person)"> | ||||
|             <person-text :person="person" :isCut="true"></person-text> | ||||
|          </span> | ||||
|       </span> | ||||
|    </li> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue'; | ||||
|  | ||||
|  | ||||
| export default { | ||||
|    name: "PersonBadge", | ||||
|    props: ['person'], | ||||
|    computed: { | ||||
|       textCutted() { | ||||
|          let more = (this.person.text.length > 15) ?'…' : ''; | ||||
|          return this.person.text.slice(0,15) + more; | ||||
|       } | ||||
|    components: { | ||||
|       PersonText | ||||
|    }, | ||||
|    // computed: { | ||||
|    //    textCutted() { | ||||
|    //       let more = (this.person.text.length > 15) ?'…' : ''; | ||||
|    //       return this.person.text.slice(0,15) + more; | ||||
|    //    } | ||||
|    // }, | ||||
|    emits: ['remove'], | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -41,6 +41,17 @@ | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|  | ||||
|             {% if activity.attendee and t.attendeeVisible %} | ||||
|                 <div class="wl-row"> | ||||
|                     <div class="wl-col title"><h3>{{ 'Attendee'|trans }}</h3></div> | ||||
|                     <div class="wl-col list"> | ||||
|                         <p class="wl-item"> | ||||
|                             {{ activity.attendee.name|localize_translatable_string }} | ||||
|                         </p> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|  | ||||
|             {% if activity.sentReceived is not empty and t.sentReceivedVisible %} | ||||
|                 <div class="wl-row"> | ||||
|                     <div class="wl-col title"><h3>{{ 'Sent received'|trans }}</h3></div> | ||||
|   | ||||
| @@ -2,10 +2,12 @@ | ||||
|     {% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %} | ||||
|         {% if no_action is not defined or no_action == false %} | ||||
|             <li> | ||||
|                 <a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', { | ||||
|                 <a class="btn btn-misc" href="{{ chill_path_add_return_path('chill_main_notification_create', { | ||||
|                     'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity', | ||||
|                     'entityId': activity.id | ||||
|                 }) }}">{{ 'notification.Notify'|trans }}</a> | ||||
|                 }) }}"> | ||||
|                     <i class="fa fa-paper-plane fa-fw"></i> | ||||
|                     {{ 'notification.Notify'|trans }}</a> | ||||
|             </li> | ||||
|         {% endif %} | ||||
|         {% if context == 'person' and activity.accompanyingPeriod is not empty %} | ||||
|   | ||||
| @@ -48,7 +48,7 @@ | ||||
|                         <li class="associated-persons"> | ||||
|                             <span class="item-key">{{ 'Participants'|trans ~ ' : ' }}</span> | ||||
|                             {% for p in activity.personsAssociated %} | ||||
|                                 <span class="badge-person">{{ p|chill_entity_render_box }}</span> | ||||
|                                 <span class="badge-person">{{ p|chill_entity_render_box({'addAgeBadge': true}) }}</span> | ||||
|                             {% endfor %} | ||||
|                         </li> | ||||
|                     </ul> | ||||
|   | ||||
| @@ -165,11 +165,7 @@ | ||||
|                 <dt class="inline">{{ 'Attendee'|trans }}</dt> | ||||
|                 <dd> | ||||
|                     {% if entity.attendee is not null %} | ||||
|                         {% if entity.attendee %} | ||||
|                             {{ 'present'|trans|capitalize }} | ||||
|                         {% else %} | ||||
|                             {{ 'not present'|trans|capitalize }} | ||||
|                         {% endif %} | ||||
|                         {{ entity.attendee.name|localize_translatable_string }} | ||||
|                     {% else %} | ||||
|                         <span class="chill-no-data-statement">{{ 'None'|trans|capitalize }}</span> | ||||
|                     {% endif %} | ||||
| @@ -181,6 +177,13 @@ | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| <div class="notification notification-list"> | ||||
|     {% set notifications =  chill_list_notifications('Chill\\ActivityBundle\\Entity\\Activity', entity.id) %} | ||||
|     {% if notifications is not empty %} | ||||
|         {{ notifications|raw }} | ||||
|     {% endif %} | ||||
| </div> | ||||
|  | ||||
| {% set person_id = null %} | ||||
| {% if person %} | ||||
|     {% set person_id = person.id %} | ||||
| @@ -197,18 +200,21 @@ | ||||
|             {{ 'Back to the list'|trans }} | ||||
|         </a> | ||||
|     </li> | ||||
|     {% if is_granted('CHILL_ACTIVITY_UPDATE', entity) %} | ||||
|         <li> | ||||
|             <a class="btn btn-update" href="{{ path('chill_activity_activity_edit', { 'id': entity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"> | ||||
|             {{ 'Edit'|trans }} | ||||
|     <li> | ||||
|         <a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity', 'entityId': entity.id}) }}"> | ||||
|             {{ 'notification.Notify'|trans }} | ||||
|         </a> | ||||
|     </li> | ||||
|     {% if is_granted('CHILL_ACTIVITY_UPDATE', entity) %} | ||||
|         <li> | ||||
|             <a href="{{ path('chill_activity_activity_edit', { 'id': entity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}" | ||||
|                class="btn btn-update">{{ 'Edit'|trans }}</a> | ||||
|         </li> | ||||
|     {% endif %} | ||||
|     {% if is_granted('CHILL_ACTIVITY_DELETE', entity) %} | ||||
|     <li> | ||||
|         <a href="{{ path('chill_activity_activity_delete', { 'id': entity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}" class="btn btn-delete"> | ||||
|             {{ 'Delete'|trans }} | ||||
|         </a> | ||||
|         <a href="{{ path('chill_activity_activity_delete', { 'id': entity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}" | ||||
|            class="btn btn-delete" title="{{ 'Delete'|trans }}"></a> | ||||
|     </li> | ||||
|     {% endif %} | ||||
| </ul> | ||||
|   | ||||
| @@ -25,19 +25,5 @@ | ||||
| {% endblock content %} | ||||
|  | ||||
| {% block block_post_menu %} | ||||
|     <div class="post-menu pt-4"> | ||||
|  | ||||
|         <div class="d-grid gap-2"> | ||||
|             <a class="btn btn-primary" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity', 'entityId': entity.id}) }}"> | ||||
|                 <i class="fa fa-paper-plane fa-fw"></i> | ||||
|                 {{ 'notification.Notify'|trans }} | ||||
|             </a> | ||||
|         </div> | ||||
|  | ||||
|         {% set notifications =  chill_list_notifications('Chill\\ActivityBundle\\Entity\\Activity', entity.id) %} | ||||
|         {% if notifications is not empty %} | ||||
|             {{ notifications|raw }} | ||||
|         {% endif %} | ||||
|  | ||||
|     </div> | ||||
| <div class="post-menu pt-4"></div> | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -28,8 +28,7 @@ | ||||
|     <div class="post-menu pt-4"> | ||||
|  | ||||
|         <div class="d-grid gap-2"> | ||||
|             <a class="btn btn-primary" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity', 'entityId': entity.id}) }}"> | ||||
|                 <i class="fa fa-paper-plane fa-fw"></i> | ||||
|             <a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity', 'entityId': entity.id}) }}"> | ||||
|                 {{ 'notification.Notify'|trans }} | ||||
|             </a> | ||||
|         </div> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ use Chill\MainBundle\Templating\TranslatableStringHelperInterface; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use Chill\PersonBundle\Templating\Entity\PersonRender; | ||||
| use DateTime; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Symfony\Bridge\Doctrine\Form\Type\EntityType; | ||||
| use Symfony\Component\Form\Extension\Core\Type\CheckboxType; | ||||
| @@ -33,8 +32,6 @@ use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Symfony\Component\Serializer\Normalizer\NormalizerInterface; | ||||
| use Symfony\Contracts\Translation\TranslatorInterface; | ||||
|  | ||||
| use function array_key_exists; | ||||
|  | ||||
| class ActivityContext implements | ||||
|     DocGeneratorContextWithAdminFormInterface, | ||||
|     DocGeneratorContextWithPublicFormInterface | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\ActivityBundle\Tests\Aggregator; | ||||
| namespace Chill\ActivityBundle\Tests\Export\Aggregator; | ||||
|  | ||||
| use Chill\MainBundle\Test\Export\AbstractAggregatorTest; | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\ActivityBundle\Tests\Aggregator; | ||||
| namespace Chill\ActivityBundle\Tests\Export\Aggregator; | ||||
|  | ||||
| use Chill\MainBundle\Test\Export\AbstractAggregatorTest; | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\ActivityBundle\Tests\Aggregator; | ||||
| namespace Chill\ActivityBundle\Tests\Export\Aggregator; | ||||
|  | ||||
| use Chill\MainBundle\Test\Export\AbstractAggregatorTest; | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\ActivityBundle\Tests\Filter; | ||||
| namespace Chill\ActivityBundle\Tests\Export\Filter; | ||||
|  | ||||
| use Chill\MainBundle\Test\Export\AbstractFilterTest; | ||||
| use Doctrine\Common\Collections\ArrayCollection; | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\ActivityBundle\Tests\Filter; | ||||
| namespace Chill\ActivityBundle\Tests\Export\Filter; | ||||
|  | ||||
| use Chill\MainBundle\Test\Export\AbstractFilterTest; | ||||
| use DateTime; | ||||
|   | ||||
| @@ -29,9 +29,13 @@ use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; | ||||
| final class ActivityVoterTest extends KernelTestCase | ||||
| { | ||||
|     use PrepareActivityTrait; | ||||
|  | ||||
|     use PrepareCenterTrait; | ||||
|  | ||||
|     use PreparePersonTrait; | ||||
|  | ||||
|     use PrepareScopeTrait; | ||||
|  | ||||
|     use PrepareUserTrait; | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -20,3 +20,4 @@ For this type of activity, you must add at least one social action: Pour ce type | ||||
|  | ||||
| # admin | ||||
| This parameter must be equal to social issue parameter: Ce paramètre doit être égal au paramètre "Visibilité du champs Problématiques sociales" | ||||
| The socialActionsVisible value is not compatible with the socialIssuesVisible value: Cette valeur du paramètre "Visibilité du champs Actions sociales" n'est pas compatible avec la valeur du paramètre "Visibilité du champs Problématiques sociales" | ||||
| @@ -32,6 +32,8 @@ final class AsideActivityController extends CRUDController | ||||
|     { | ||||
|         $asideActivity = new AsideActivity(); | ||||
|  | ||||
|         $asideActivity->setAgent($this->getUser()); | ||||
|  | ||||
|         $duration = $request->query->get('duration', '300'); | ||||
|         $duration = DateTime::createFromFormat('U', $duration); | ||||
|         $asideActivity->setDuration($duration); | ||||
|   | ||||
| @@ -14,9 +14,9 @@ namespace Chill\AsideActivityBundle\Form; | ||||
| use Chill\AsideActivityBundle\Entity\AsideActivity; | ||||
| use Chill\AsideActivityBundle\Entity\AsideActivityCategory; | ||||
| use Chill\AsideActivityBundle\Templating\Entity\CategoryRender; | ||||
| use Chill\MainBundle\Entity\User; | ||||
| use Chill\MainBundle\Form\Type\ChillDateType; | ||||
| use Chill\MainBundle\Form\Type\ChillTextareaType; | ||||
| use Chill\MainBundle\Form\Type\PickUserDynamicType; | ||||
| use DateInterval; | ||||
| use DateTime; | ||||
| use DateTimeImmutable; | ||||
| @@ -68,22 +68,10 @@ final class AsideActivityFormType extends AbstractType | ||||
|         ]; | ||||
|  | ||||
|         $builder | ||||
|             ->add( | ||||
|                 'agent', | ||||
|                 EntityType::class, | ||||
|                 [ | ||||
|                     'label' => 'For agent', | ||||
|                     'required' => true, | ||||
|                     'class' => User::class, | ||||
|                     'data' => $this->storage->getToken()->getUser(), | ||||
|                     'query_builder' => static function (EntityRepository $er) { | ||||
|                         return $er->createQueryBuilder('u')->where('u.enabled = true'); | ||||
|                     }, | ||||
|                     'attr' => ['class' => 'select2 '], | ||||
|                     'placeholder' => 'Choose the agent for whom this activity is created', | ||||
|                     'choice_label' => 'username', | ||||
|                 ] | ||||
|             ) | ||||
|             ->add('agent', PickUserDynamicType::class, [ | ||||
|                 'label' => 'For agent', | ||||
|                 'required' => true, | ||||
|             ]) | ||||
|             ->add( | ||||
|                 'date', | ||||
|                 ChillDateType::class, | ||||
|   | ||||
| @@ -1,5 +1,15 @@ | ||||
| {% extends '@ChillMain/Admin/layout.html.twig' %} | ||||
|  | ||||
| {% block js %} | ||||
|     {{ parent() }} | ||||
|     {{ encore_entry_script_tags('mod_pickentity_type') }} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block css %} | ||||
|     {{ parent() }} | ||||
|     {{ encore_entry_link_tags('mod_pickentity_type') }} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block title %} | ||||
| {% include('@ChillMain/CRUD/_new_title.html.twig') %} | ||||
| {% endblock %} | ||||
| @@ -8,4 +18,5 @@ | ||||
| {% embed '@ChillMain/CRUD/_new_content.html.twig' %} | ||||
|     {% block content_form_actions_save_and_show %}{% endblock %} | ||||
| {% endembed %} | ||||
| {% endblock %} | ||||
| {% endblock %} | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\CustomFieldsBundle\Tests; | ||||
| namespace Chill\CustomFieldsBundle\Tests\CustomFields; | ||||
|  | ||||
| use Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice; | ||||
| use Chill\CustomFieldsBundle\Entity\CustomField; | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\CustomFieldsBundle\Tests; | ||||
| namespace Chill\CustomFieldsBundle\Tests\CustomFields; | ||||
|  | ||||
| use Chill\CustomFieldsBundle\CustomFields\CustomFieldNumber; | ||||
| use Chill\CustomFieldsBundle\Entity\CustomField; | ||||
|   | ||||
| @@ -9,10 +9,11 @@ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\CustomFieldsBundle\Tests; | ||||
| namespace Chill\CustomFieldsBundle\Tests\CustomFields; | ||||
|  | ||||
| use Chill\CustomFieldsBundle\CustomFields\CustomFieldText; | ||||
| use Chill\CustomFieldsBundle\Entity\CustomField; | ||||
| use Chill\CustomFieldsBundle\Tests\CustomFieldTestHelper; | ||||
| use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\CustomFieldsBundle\Tests; | ||||
| namespace Chill\CustomFieldsBundle\Tests\Routing; | ||||
|  | ||||
| use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; | ||||
| use Symfony\Component\HttpFoundation\Response; | ||||
|   | ||||
| @@ -17,7 +17,7 @@ | ||||
|                             <select class="form-select" v-model="template"> | ||||
|                                 <option disabled selected value="">{{ $t('choose_a_template') }}</option> | ||||
|                                 <template v-for="t in templates"> | ||||
|                                     <option  v-bind:value="t.id">{{ t.name.fr }}</option> | ||||
|                                     <option v-bind:value="t.id">{{ t.name.fr || 'Aucun nom défini' }}</option> | ||||
|                                 </template> | ||||
|                             </select> | ||||
|                             <button v-if="canGenerate" class="btn btn-update btn-sm change-icon" type="button" @click="generateDocument"><i class="fa fa-fw fa-cog"></i></button> | ||||
| @@ -48,6 +48,7 @@ export default { | ||||
|             required: true, | ||||
|         }, | ||||
|         templates: { | ||||
|             type: Array, | ||||
|             required: true, | ||||
|         }, | ||||
|         // beforeMove execute "something" before | ||||
| @@ -73,7 +74,11 @@ export default { | ||||
|             return true; | ||||
|         }, | ||||
|         getDescription() { | ||||
|             return this.templates.find(t => t.id === this.template).description || ''; | ||||
|             let desc = this.templates.find(t => t.id === this.template); | ||||
|             if (null === desc) { | ||||
|                 return ''; | ||||
|             } | ||||
|             return desc.description || ''; | ||||
|         }, | ||||
|     }, | ||||
|     methods: { | ||||
|   | ||||
| @@ -21,24 +21,28 @@ | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| {% set freezed = false %} | ||||
| {% for step in entity_workflow.stepsChained %} | ||||
|     {% if loop.last %} | ||||
|         {% if step.previous is not null and step.previous.freezeAfter == true %} | ||||
|             {% set freezed = true %} | ||||
|         {% endif %} | ||||
|     {% endif %} | ||||
| {% endfor %} | ||||
|  | ||||
| {% if display_action is defined and display_action == true %} | ||||
|     <ul class="record_actions"> | ||||
|         <li> | ||||
|             {{ m.download_button(document.object, document.title) }} | ||||
|         </li> | ||||
|         <li> | ||||
|              | ||||
|             {# | ||||
|                 data-button is optional ! | ||||
|                 OPTIONS: | ||||
|                 'changeIcon'    string | ||||
|         {% if not freezed %} | ||||
|             {% set button = { | ||||
|                 'changeIcon': 'fa-unlock', | ||||
|             } %}{# | ||||
|                 'changeClass'   string | ||||
|                 'noText'        boolean | ||||
|              | ||||
|             #}{% set button = { | ||||
|                 'changeIcon': 'fa-unlock', | ||||
|             } %} | ||||
|              | ||||
|             #} | ||||
|             {# vue component #} | ||||
|             <span | ||||
|                 data-module="wopi-link" | ||||
| @@ -47,6 +51,11 @@ | ||||
|                 data-doc-type="{{ document.object.type|e('html_attr') }}" | ||||
|                 data-button="{{ button|json_encode }}" | ||||
|             ></span> | ||||
|         {% else %} | ||||
|             <a class="btn btn-update change-icon disabled" href="#" title="{{ 'workflow.freezed document'|trans }}"> | ||||
|                 <i class="fa fa-lock me-2"></i>{{ 'Update document'|trans }} | ||||
|             </a> | ||||
|         {% endif %} | ||||
|         </li> | ||||
|     </ul> | ||||
| {% endif %} | ||||
| @@ -56,17 +56,18 @@ | ||||
|                 </a> | ||||
|             </li> | ||||
|         {% endif %} | ||||
|         {% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %} | ||||
|         {% if workflows_frame is not empty %} | ||||
|             <li> | ||||
|                 {{ workflows_frame|raw }} | ||||
|             </li> | ||||
|         {% endif %} | ||||
|     </ul> | ||||
| </div> | ||||
| {% endblock %} | ||||
|   | ||||
| {% block block_post_menu %} | ||||
|     <div class="post-menu pt-4"> | ||||
|         {% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %} | ||||
|         {% if workflows_frame is not empty %} | ||||
|             {{ workflows_frame|raw }} | ||||
|         {% endif %} | ||||
|     </div> | ||||
|     <div class="post-menu pt-4"></div> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block js %} | ||||
|   | ||||
| @@ -13,13 +13,19 @@ namespace Chill\MainBundle\Controller; | ||||
|  | ||||
| use Chill\MainBundle\Entity\Notification; | ||||
| use Chill\MainBundle\Entity\User; | ||||
| use Chill\MainBundle\Pagination\PaginatorFactory; | ||||
| use Chill\MainBundle\Repository\NotificationRepository; | ||||
| use Chill\MainBundle\Security\Authorization\NotificationVoter; | ||||
| use Chill\MainBundle\Serializer\Model\Collection; | ||||
| use Chill\MainBundle\Serializer\Model\Counter; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use RuntimeException; | ||||
| use Symfony\Component\HttpFoundation\JsonResponse; | ||||
| use Symfony\Component\HttpFoundation\Request; | ||||
| use Symfony\Component\Routing\Annotation\Route; | ||||
| use Symfony\Component\Security\Core\Exception\AccessDeniedException; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
| use Symfony\Component\Serializer\SerializerInterface; | ||||
| use UnexpectedValueException; | ||||
|  | ||||
| /** | ||||
| @@ -29,12 +35,26 @@ class NotificationApiController | ||||
| { | ||||
|     private EntityManagerInterface $entityManager; | ||||
|  | ||||
|     private NotificationRepository $notificationRepository; | ||||
|  | ||||
|     private PaginatorFactory $paginatorFactory; | ||||
|  | ||||
|     private Security $security; | ||||
|  | ||||
|     public function __construct(EntityManagerInterface $entityManager, Security $security) | ||||
|     { | ||||
|     private SerializerInterface $serializer; | ||||
|  | ||||
|     public function __construct( | ||||
|         EntityManagerInterface $entityManager, | ||||
|         NotificationRepository $notificationRepository, | ||||
|         PaginatorFactory $paginatorFactory, | ||||
|         Security $security, | ||||
|         SerializerInterface $serializer | ||||
|     ) { | ||||
|         $this->entityManager = $entityManager; | ||||
|         $this->notificationRepository = $notificationRepository; | ||||
|         $this->paginatorFactory = $paginatorFactory; | ||||
|         $this->security = $security; | ||||
|         $this->serializer = $serializer; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -53,6 +73,37 @@ class NotificationApiController | ||||
|         return $this->markAs('unread', $notification); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @Route("/my/unread") | ||||
|      */ | ||||
|     public function myUnreadNotifications(Request $request): JsonResponse | ||||
|     { | ||||
|         $total = $this->notificationRepository->countUnreadByUser($this->security->getUser()); | ||||
|  | ||||
|         if ($request->query->getBoolean('countOnly')) { | ||||
|             return new JsonResponse( | ||||
|                 $this->serializer->serialize(new Counter($total), 'json', ['groups' => ['read']]), | ||||
|                 JsonResponse::HTTP_OK, | ||||
|                 [], | ||||
|                 true | ||||
|             ); | ||||
|         } | ||||
|         $paginator = $this->paginatorFactory->create($total); | ||||
|         $notifications = $this->notificationRepository->findUnreadByUser( | ||||
|             $this->security->getUser(), | ||||
|             $paginator->getItemsPerPage(), | ||||
|             $paginator->getCurrentPageFirstItemNumber() | ||||
|         ); | ||||
|         $collection = new Collection($notifications, $paginator); | ||||
|  | ||||
|         return new JsonResponse( | ||||
|             $this->serializer->serialize($collection, 'json', ['groups' => ['read']]), | ||||
|             JsonResponse::HTTP_OK, | ||||
|             [], | ||||
|             true | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     private function markAs(string $target, Notification $notification): JsonResponse | ||||
|     { | ||||
|         if (!$this->security->isGranted(NotificationVoter::NOTIFICATION_TOGGLE_READ_STATUS, $notification)) { | ||||
|   | ||||
| @@ -41,6 +41,11 @@ class Configuration implements ConfigurationInterface | ||||
|  | ||||
|         $rootNode | ||||
|             ->children() | ||||
|             ->scalarNode('phonenumber_default_country_code') | ||||
|             ->cannotBeEmpty() | ||||
|             ->isRequired() | ||||
|             ->defaultValue('+32') | ||||
|             ->end() // end of scalar 'phonenumber_default_country_code' | ||||
|             ->scalarNode('installation_name') | ||||
|             ->cannotBeEmpty() | ||||
|             ->defaultValue('Chill') | ||||
|   | ||||
| @@ -42,10 +42,16 @@ class Address | ||||
|      */ | ||||
|     private $buildingName; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="boolean") | ||||
|      * @Groups({"write"}) | ||||
|      */ | ||||
|     private bool $confidential = false; | ||||
|  | ||||
|     /** | ||||
|      * @var string|null | ||||
|      * | ||||
|      * @ORM\Column(type="string", length=16, nullable=true) | ||||
|      * @ORM\Column(type="string", length=255, nullable=true) | ||||
|      * @Groups({"write"}) | ||||
|      */ | ||||
|     private $corridor; | ||||
| @@ -78,7 +84,7 @@ class Address | ||||
|     /** | ||||
|      * @var string|null | ||||
|      * | ||||
|      * @ORM\Column(type="string", length=16, nullable=true) | ||||
|      * @ORM\Column(type="string", length=255, nullable=true) | ||||
|      * @Groups({"write"}) | ||||
|      */ | ||||
|     private $flat; | ||||
| @@ -86,7 +92,7 @@ class Address | ||||
|     /** | ||||
|      * @var string|null | ||||
|      * | ||||
|      * @ORM\Column(type="string", length=16, nullable=true) | ||||
|      * @ORM\Column(type="string", length=255, nullable=true) | ||||
|      * @Groups({"write"}) | ||||
|      */ | ||||
|     private $floor; | ||||
| @@ -143,7 +149,7 @@ class Address | ||||
|     /** | ||||
|      * @var string|null | ||||
|      * | ||||
|      * @ORM\Column(type="string", length=16, nullable=true) | ||||
|      * @ORM\Column(type="string", length=255, nullable=true) | ||||
|      * @Groups({"write"}) | ||||
|      */ | ||||
|     private $steps; | ||||
| @@ -192,6 +198,7 @@ class Address | ||||
|         return (new Address()) | ||||
|             ->setAddressReference($original->getAddressReference()) | ||||
|             ->setBuildingName($original->getBuildingName()) | ||||
|             ->setConfidential($original->getConfidential()) | ||||
|             ->setCorridor($original->getCorridor()) | ||||
|             ->setCustoms($original->getCustoms()) | ||||
|             ->setDistribution($original->getDistribution()) | ||||
| @@ -229,6 +236,11 @@ class Address | ||||
|         return $this->buildingName; | ||||
|     } | ||||
|  | ||||
|     public function getConfidential(): bool | ||||
|     { | ||||
|         return $this->confidential; | ||||
|     } | ||||
|  | ||||
|     public function getCorridor(): ?string | ||||
|     { | ||||
|         return $this->corridor; | ||||
| @@ -369,6 +381,13 @@ class Address | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setConfidential(bool $confidential): self | ||||
|     { | ||||
|         $this->confidential = $confidential; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setCorridor(?string $corridor): self | ||||
|     { | ||||
|         $this->corridor = $corridor; | ||||
|   | ||||
| @@ -20,10 +20,9 @@ use Doctrine\ORM\Mapping as ORM; | ||||
| class CommentEmbeddable | ||||
| { | ||||
|     /** | ||||
|      * @var string | ||||
|      * @ORM\Column(type="text", nullable=true) | ||||
|      */ | ||||
|     private $comment; | ||||
|     private ?string $comment = null; | ||||
|  | ||||
|     /** | ||||
|      * @var DateTime | ||||
| @@ -39,10 +38,7 @@ class CommentEmbeddable | ||||
|      */ | ||||
|     private $userId; | ||||
|  | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getComment() | ||||
|     public function getComment(): ?string | ||||
|     { | ||||
|         return $this->comment; | ||||
|     } | ||||
| @@ -68,9 +64,6 @@ class CommentEmbeddable | ||||
|         return empty($this->getComment()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param string $comment | ||||
|      */ | ||||
|     public function setComment(?string $comment) | ||||
|     { | ||||
|         $this->comment = $comment; | ||||
|   | ||||
| @@ -64,7 +64,7 @@ class Location implements TrackCreationInterface, TrackUpdateInterface | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="string", length=255, nullable=true) | ||||
|      * @Serializer\Groups({"read", "write"}) | ||||
|      * @Serializer\Groups({"read", "write", "docgen:read"}) | ||||
|      */ | ||||
|     private ?string $email = null; | ||||
|  | ||||
|   | ||||
							
								
								
									
										166
									
								
								src/Bundle/ChillMainBundle/Entity/ResidentialAddress.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								src/Bundle/ChillMainBundle/Entity/ResidentialAddress.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,166 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * 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. | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\MainBundle\Entity; | ||||
|  | ||||
| use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; | ||||
| use Chill\MainBundle\Repository\ResidentialAddressRepository; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use Chill\ThirdPartyBundle\Entity\ThirdParty; | ||||
| use DateTimeImmutable; | ||||
| use Doctrine\ORM\Mapping as ORM; | ||||
|  | ||||
| /** | ||||
|  * @ORM\Entity(repositoryClass=ResidentialAddressRepository::class) | ||||
|  * @ORM\Table(name="chill_main_residential_address") | ||||
|  */ | ||||
| class ResidentialAddress | ||||
| { | ||||
|     /** | ||||
|      * @ORM\ManyToOne(targetEntity=Address::class) | ||||
|      * @ORM\JoinColumn(nullable=true) | ||||
|      */ | ||||
|     private ?Address $address = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="residentialAddressComment_") | ||||
|      */ | ||||
|     private CommentEmbeddable $comment; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="datetime_immutable", nullable=true) | ||||
|      */ | ||||
|     private ?DateTimeImmutable $endDate = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\ManyToOne(targetEntity=Person::class) | ||||
|      * @ORM\JoinColumn(nullable=true) | ||||
|      */ | ||||
|     private ?Person $hostPerson = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\ManyToOne(targetEntity=ThirdParty::class) | ||||
|      * @ORM\JoinColumn(nullable=true) | ||||
|      */ | ||||
|     private ?ThirdParty $hostThirdParty = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Id | ||||
|      * @ORM\GeneratedValue | ||||
|      * @ORM\Column(type="integer") | ||||
|      */ | ||||
|     private $id; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\ManyToOne(targetEntity=Person::class) | ||||
|      * @ORM\JoinColumn(nullable=false) | ||||
|      */ | ||||
|     private Person $person; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="datetime_immutable") | ||||
|      */ | ||||
|     private ?DateTimeImmutable $startDate = null; | ||||
|  | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->comment = new CommentEmbeddable(); | ||||
|     } | ||||
|  | ||||
|     public function getAddress(): ?Address | ||||
|     { | ||||
|         return $this->address; | ||||
|     } | ||||
|  | ||||
|     public function getComment(): CommentEmbeddable | ||||
|     { | ||||
|         return $this->comment; | ||||
|     } | ||||
|  | ||||
|     public function getEndDate(): ?DateTimeImmutable | ||||
|     { | ||||
|         return $this->endDate; | ||||
|     } | ||||
|  | ||||
|     public function getHostPerson(): ?Person | ||||
|     { | ||||
|         return $this->hostPerson; | ||||
|     } | ||||
|  | ||||
|     public function getHostThirdParty(): ?ThirdParty | ||||
|     { | ||||
|         return $this->hostThirdParty; | ||||
|     } | ||||
|  | ||||
|     public function getId(): ?int | ||||
|     { | ||||
|         return $this->id; | ||||
|     } | ||||
|  | ||||
|     public function getPerson(): ?Person | ||||
|     { | ||||
|         return $this->person; | ||||
|     } | ||||
|  | ||||
|     public function getStartDate(): ?DateTimeImmutable | ||||
|     { | ||||
|         return $this->startDate; | ||||
|     } | ||||
|  | ||||
|     public function setAddress(?Address $address): self | ||||
|     { | ||||
|         $this->address = $address; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setComment(CommentEmbeddable $comment): self | ||||
|     { | ||||
|         $this->comment = $comment; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setEndDate(?DateTimeImmutable $endDate): self | ||||
|     { | ||||
|         $this->endDate = $endDate; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setHostPerson(?Person $hostPerson): self | ||||
|     { | ||||
|         $this->hostPerson = $hostPerson; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setHostThirdParty(?ThirdParty $hostThirdParty): self | ||||
|     { | ||||
|         $this->hostThirdParty = $hostThirdParty; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setPerson(?Person $person): self | ||||
|     { | ||||
|         $this->person = $person; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setStartDate(DateTimeImmutable $startDate): self | ||||
|     { | ||||
|         $this->startDate = $startDate; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
| } | ||||
| @@ -25,6 +25,7 @@ use Iterator; | ||||
| use RuntimeException; | ||||
| use Symfony\Component\Serializer\Annotation as Serializer; | ||||
| use function count; | ||||
| use function is_array; | ||||
|  | ||||
| /** | ||||
|  * @ORM\Entity | ||||
| @@ -37,6 +38,7 @@ use function count; | ||||
| class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface | ||||
| { | ||||
|     use TrackCreationTrait; | ||||
|  | ||||
|     use TrackUpdateTrait; | ||||
|  | ||||
|     /** | ||||
| @@ -50,7 +52,6 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface | ||||
|      * @ORM\Id | ||||
|      * @ORM\GeneratedValue | ||||
|      * @ORM\Column(type="integer") | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private ?int $id = null; | ||||
|  | ||||
| @@ -72,6 +73,11 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface | ||||
|      */ | ||||
|     private Collection $steps; | ||||
|  | ||||
|     /** | ||||
|      * @var null|array|EntityWorkflowStep[] | ||||
|      */ | ||||
|     private ?array $stepsChainedCache = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\ManyToMany(targetEntity=User::class) | ||||
|      * @ORM\JoinTable(name="chill_main_workflow_entity_subscriber_to_final") | ||||
| @@ -129,10 +135,6 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface | ||||
|         if (!$this->steps->contains($step)) { | ||||
|             $this->steps[] = $step; | ||||
|             $step->setEntityWorkflow($this); | ||||
|  | ||||
|             if ($this->isFinalize()) { | ||||
|                 $step->setFinalizeAfter(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return $this; | ||||
| @@ -253,27 +255,33 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface | ||||
|  | ||||
|     public function getStepsChained(): array | ||||
|     { | ||||
|         if (is_array($this->stepsChainedCache)) { | ||||
|             return $this->stepsChainedCache; | ||||
|         } | ||||
|  | ||||
|         $iterator = $this->steps->getIterator(); | ||||
|         $previous = $next = $current = null; | ||||
|         $current = null; | ||||
|         $steps = []; | ||||
|  | ||||
|         $iterator->rewind(); | ||||
|  | ||||
|         while ($iterator->valid()) { | ||||
|         do { | ||||
|             $previous = $current; | ||||
|             $steps[] = $current = $iterator->current(); | ||||
|             $current = $iterator->current(); | ||||
|             $steps[] = $current; | ||||
|  | ||||
|             $current->setPrevious($previous); | ||||
|  | ||||
|             $iterator->next(); | ||||
|  | ||||
|             if ($iterator->valid()) { | ||||
|                 $next = $iterator->current(); | ||||
|                 $current->setNext($iterator->current()); | ||||
|             } else { | ||||
|                 $next = null; | ||||
|                 $current->setNext(null); | ||||
|             } | ||||
|         } while ($iterator->valid()); | ||||
|  | ||||
|             $current->setNext($next); | ||||
|         } | ||||
|         $this->stepsChainedCache = $steps; | ||||
|  | ||||
|         return $steps; | ||||
|     } | ||||
| @@ -308,7 +316,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface | ||||
|         return $this->workflowName; | ||||
|     } | ||||
|  | ||||
|     public function isFinalize(): bool | ||||
|     public function isFinal(): bool | ||||
|     { | ||||
|         $steps = $this->getStepsChained(); | ||||
|  | ||||
| @@ -320,7 +328,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface | ||||
|         /** @var EntityWorkflowStep $last */ | ||||
|         $last = end($steps); | ||||
|  | ||||
|         return $last->getPrevious()->isFinalizeAfter(); | ||||
|         return $last->isFinal(); | ||||
|     } | ||||
|  | ||||
|     public function isFreeze(): bool | ||||
|   | ||||
| @@ -24,6 +24,7 @@ use Doctrine\ORM\Mapping as ORM; | ||||
| class EntityWorkflowComment implements TrackCreationInterface, TrackUpdateInterface | ||||
| { | ||||
|     use TrackCreationTrait; | ||||
|  | ||||
|     use TrackUpdateTrait; | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -53,11 +53,6 @@ class EntityWorkflowStep | ||||
|      */ | ||||
|     private ?EntityWorkflow $entityWorkflow = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="boolean", options={"default": false}) | ||||
|      */ | ||||
|     private bool $finalizeAfter = false; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="boolean", options={"default": false}) | ||||
|      */ | ||||
| @@ -70,6 +65,11 @@ class EntityWorkflowStep | ||||
|      */ | ||||
|     private ?int $id = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="boolean", options={"default": false}) | ||||
|      */ | ||||
|     private bool $isFinal = false; | ||||
|  | ||||
|     /** | ||||
|      * filled by @see{EntityWorkflow::getStepsChained}. | ||||
|      */ | ||||
| @@ -187,9 +187,9 @@ class EntityWorkflowStep | ||||
|         return $this->transitionByEmail; | ||||
|     } | ||||
|  | ||||
|     public function isFinalizeAfter(): bool | ||||
|     public function isFinal(): bool | ||||
|     { | ||||
|         return $this->finalizeAfter; | ||||
|         return $this->isFinal; | ||||
|     } | ||||
|  | ||||
|     public function isFreezeAfter(): bool | ||||
| @@ -244,16 +244,16 @@ class EntityWorkflowStep | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setFinalizeAfter(bool $finalizeAfter): EntityWorkflowStep | ||||
|     public function setFreezeAfter(bool $freezeAfter): EntityWorkflowStep | ||||
|     { | ||||
|         $this->finalizeAfter = $finalizeAfter; | ||||
|         $this->freezeAfter = $freezeAfter; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setFreezeAfter(bool $freezeAfter): EntityWorkflowStep | ||||
|     public function setIsFinal(bool $isFinal): EntityWorkflowStep | ||||
|     { | ||||
|         $this->freezeAfter = $freezeAfter; | ||||
|         $this->isFinal = $isFinal; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|   | ||||
| @@ -12,14 +12,18 @@ declare(strict_types=1); | ||||
| namespace Chill\MainBundle\Form\Type\DataTransformer; | ||||
| 
 | ||||
| use Chill\MainBundle\Entity\User; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use Chill\ThirdPartyBundle\Entity\ThirdParty; | ||||
| use Symfony\Component\Form\DataTransformerInterface; | ||||
| use Symfony\Component\Form\Exception\TransformationFailedException; | ||||
| use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; | ||||
| use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; | ||||
| use Symfony\Component\Serializer\SerializerInterface; | ||||
| use UnexpectedValueException; | ||||
| 
 | ||||
| use function array_key_exists; | ||||
| 
 | ||||
| class UserToJsonTransformer implements DataTransformerInterface | ||||
| class EntityToJsonTransformer implements DataTransformerInterface | ||||
| { | ||||
|     private DenormalizerInterface $denormalizer; | ||||
| 
 | ||||
| @@ -27,11 +31,14 @@ class UserToJsonTransformer implements DataTransformerInterface | ||||
| 
 | ||||
|     private SerializerInterface $serializer; | ||||
| 
 | ||||
|     public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, bool $multiple) | ||||
|     private string $type; | ||||
| 
 | ||||
|     public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, bool $multiple, string $type) | ||||
|     { | ||||
|         $this->denormalizer = $denormalizer; | ||||
|         $this->serializer = $serializer; | ||||
|         $this->multiple = $multiple; | ||||
|         $this->type = $type; | ||||
|     } | ||||
| 
 | ||||
|     public function reverseTransform($value) | ||||
| @@ -49,6 +56,10 @@ class UserToJsonTransformer implements DataTransformerInterface | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         if ('' === $value) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return $this->denormalizeOne($denormalized); | ||||
|     } | ||||
| 
 | ||||
| @@ -66,7 +77,7 @@ class UserToJsonTransformer implements DataTransformerInterface | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     private function denormalizeOne(array $item): User | ||||
|     private function denormalizeOne(array $item) | ||||
|     { | ||||
|         if (!array_key_exists('type', $item)) { | ||||
|             throw new TransformationFailedException('the key "type" is missing on element'); | ||||
| @@ -76,10 +87,30 @@ class UserToJsonTransformer implements DataTransformerInterface | ||||
|             throw new TransformationFailedException('the key "id" is missing on element'); | ||||
|         } | ||||
| 
 | ||||
|         switch ($this->type) { | ||||
|             case 'user': | ||||
|                 $class = User::class; | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case 'person': | ||||
|                 $class = Person::class; | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case 'thirdparty': | ||||
|                 $class = ThirdParty::class; | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             default: | ||||
|                 throw new UnexpectedValueException('This type is not supported'); | ||||
|         } | ||||
| 
 | ||||
|         return | ||||
|             $this->denormalizer->denormalize( | ||||
|                 ['type' => $item['type'], 'id' => $item['id']], | ||||
|                 User::class, | ||||
|                 $class, | ||||
|                 'json', | ||||
|                 [AbstractNormalizer::GROUPS => ['read']], | ||||
|             ); | ||||
| @@ -12,7 +12,7 @@ declare(strict_types=1); | ||||
| namespace Chill\MainBundle\Form\Type; | ||||
|  | ||||
| use Chill\MainBundle\Entity\User; | ||||
| use Chill\MainBundle\Form\Type\DataTransformer\UserToJsonTransformer; | ||||
| use Chill\MainBundle\Form\Type\DataTransformer\EntityToJsonTransformer; | ||||
| use Symfony\Component\Form\AbstractType; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Symfony\Component\Form\FormInterface; | ||||
| @@ -38,7 +38,7 @@ class PickUserDynamicType extends AbstractType | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder, array $options) | ||||
|     { | ||||
|         $builder->addViewTransformer(new UserToJsonTransformer($this->denormalizer, $this->serializer, $options['multiple'])); | ||||
|         $builder->addViewTransformer(new EntityToJsonTransformer($this->denormalizer, $this->serializer, $options['multiple'], 'user')); | ||||
|     } | ||||
|  | ||||
|     public function buildView(FormView $view, FormInterface $form, array $options) | ||||
| @@ -58,6 +58,6 @@ class PickUserDynamicType extends AbstractType | ||||
|  | ||||
|     public function getBlockPrefix() | ||||
|     { | ||||
|         return 'pick_user_dynamic'; | ||||
|         return 'pick_entity_dynamic'; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,73 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * 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. | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\MainBundle\Form\Type; | ||||
|  | ||||
| use Chill\MainBundle\Entity\ResidentialAddress; | ||||
| use Chill\PersonBundle\Form\Type\PickPersonDynamicType; | ||||
| use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType; | ||||
| use Symfony\Component\Form\AbstractType; | ||||
| use Symfony\Component\Form\Extension\Core\Type\DateType; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Symfony\Component\OptionsResolver\OptionsResolver; | ||||
|  | ||||
| final class ResidentialAddressType extends AbstractType | ||||
| { | ||||
|     public function buildForm(FormBuilderInterface $builder, array $options) | ||||
|     { | ||||
|         $builder | ||||
|             ->add('startDate', DateType::class, [ | ||||
|                 'required' => true, | ||||
|                 'input' => 'datetime_immutable', | ||||
|                 'widget' => 'single_text', | ||||
|             ]) | ||||
|             ->add('endDate', DateType::class, [ | ||||
|                 'required' => false, | ||||
|                 'input' => 'datetime_immutable', | ||||
|                 'widget' => 'single_text', | ||||
|             ]) | ||||
|             ->add('comment', CommentType::class, [ | ||||
|                 'required' => false, | ||||
|             ]); | ||||
|  | ||||
|         if ('person' === $options['kind']) { | ||||
|             $builder | ||||
|                 ->add('hostPerson', PickPersonDynamicType::class, [ | ||||
|                     'label' => 'Person', | ||||
|                 ]); | ||||
|         } | ||||
|  | ||||
|         if ('thirdparty' === $options['kind']) { | ||||
|             $builder | ||||
|                 ->add('hostThirdParty', PickThirdpartyDynamicType::class, [ | ||||
|                     'label' => 'Third party', | ||||
|                 ]); | ||||
|         } | ||||
|  | ||||
|         if ('address' === $options['kind']) { | ||||
|             $builder | ||||
|                 ->add('address', PickAddressType::class, [ | ||||
|                     'required' => false, | ||||
|                     'label' => 'Address', | ||||
|                     'use_valid_from' => false, | ||||
|                     'use_valid_to' => false, | ||||
|                 ]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function configureOptions(OptionsResolver $resolver) | ||||
|     { | ||||
|         $resolver->setDefaults([ | ||||
|             'data_class' => ResidentialAddress::class, | ||||
|             'kind' => null, | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
| @@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow; | ||||
| use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep; | ||||
| use Chill\MainBundle\Form\Type\ChillTextareaType; | ||||
| use Chill\MainBundle\Form\Type\PickUserDynamicType; | ||||
| use Chill\MainBundle\Templating\TranslatableStringHelperInterface; | ||||
| use Chill\MainBundle\Workflow\EntityWorkflowManager; | ||||
| use LogicException; | ||||
| use Symfony\Component\Form\AbstractType; | ||||
| @@ -24,6 +25,7 @@ use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Symfony\Component\OptionsResolver\OptionsResolver; | ||||
| use Symfony\Component\Workflow\Registry; | ||||
| use Symfony\Component\Workflow\Transition; | ||||
| use function array_key_exists; | ||||
|  | ||||
| class WorkflowStepType extends AbstractType | ||||
| { | ||||
| @@ -31,10 +33,13 @@ class WorkflowStepType extends AbstractType | ||||
|  | ||||
|     private Registry $registry; | ||||
|  | ||||
|     public function __construct(EntityWorkflowManager $entityWorkflowManager, Registry $registry) | ||||
|     private TranslatableStringHelperInterface $translatableStringHelper; | ||||
|  | ||||
|     public function __construct(EntityWorkflowManager $entityWorkflowManager, Registry $registry, TranslatableStringHelperInterface $translatableStringHelper) | ||||
|     { | ||||
|         $this->entityWorkflowManager = $entityWorkflowManager; | ||||
|         $this->registry = $registry; | ||||
|         $this->translatableStringHelper = $translatableStringHelper; | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder, array $options) | ||||
| @@ -42,6 +47,7 @@ class WorkflowStepType extends AbstractType | ||||
|         /** @var \Chill\MainBundle\Entity\Workflow\EntityWorkflow $entityWorkflow */ | ||||
|         $entityWorkflow = $options['entity_workflow']; | ||||
|         $handler = $this->entityWorkflowManager->getHandler($entityWorkflow); | ||||
|         $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); | ||||
|  | ||||
|         if (true === $options['transition']) { | ||||
|             if (null === $options['entity_workflow']) { | ||||
| @@ -53,20 +59,49 @@ class WorkflowStepType extends AbstractType | ||||
|                 ->getEnabledTransitions($entityWorkflow); | ||||
|  | ||||
|             $choices = array_combine( | ||||
|                 array_map(static function (Transition $transition) { return $transition->getName(); }, $transitions), | ||||
|                 array_map( | ||||
|                     static function (Transition $transition) { | ||||
|                         return $transition->getName(); | ||||
|                     }, | ||||
|                     $transitions | ||||
|                 ), | ||||
|                 $transitions | ||||
|             ); | ||||
|  | ||||
|             $builder | ||||
|                 ->add('transition', ChoiceType::class, [ | ||||
|                     'label' => 'workflow.Transition', | ||||
|                     'label' => 'workflow.Transition to apply', | ||||
|                     'mapped' => false, | ||||
|                     'multiple' => false, | ||||
|                     'expanded' => true, | ||||
|                     'choices' => $choices, | ||||
|                     'choice_label' => static function (Transition $transition) { | ||||
|                         return implode(', ', $transition->getTos()); | ||||
|                     }, | ||||
|                     'choice_label' => function (Transition $transition) use ($workflow) { | ||||
|                             $meta = $workflow->getMetadataStore()->getTransitionMetadata($transition); | ||||
|  | ||||
|                             if (array_key_exists('label', $meta)) { | ||||
|                                 return $this->translatableStringHelper->localize($meta['label']); | ||||
|                             } | ||||
|  | ||||
|                             return $transition->getName(); | ||||
|                         }, | ||||
|                     'choice_attr' => static function (Transition $transition) use ($workflow) { | ||||
|                             $toFinal = true; | ||||
|  | ||||
|                             foreach ($transition->getTos() as $to) { | ||||
|                                 $meta = $workflow->getMetadataStore()->getPlaceMetadata($to); | ||||
|  | ||||
|                                 if ( | ||||
|                                     !array_key_exists('isFinal', $meta) || false === $meta['isFinal'] | ||||
|                                 ) { | ||||
|                                     $toFinal = false; | ||||
|                                 } | ||||
|                             } | ||||
|  | ||||
|                             return [ | ||||
|                                 'data-is-transition' => 'data-is-transition', | ||||
|                                 'data-to-final' => $toFinal ? '1' : '0', | ||||
|                             ]; | ||||
|                         }, | ||||
|                 ]) | ||||
|                 ->add('future_dest_users', PickUserDynamicType::class, [ | ||||
|                     'label' => 'workflow.dest for next steps', | ||||
| @@ -88,11 +123,6 @@ class WorkflowStepType extends AbstractType | ||||
|         } | ||||
|  | ||||
|         $builder | ||||
|             ->add('finalizeAfter', CheckboxType::class, [ | ||||
|                 'required' => false, | ||||
|                 'label' => 'workflow.Finalize', | ||||
|                 'help' => 'workflow.The workflow will be finalized', | ||||
|             ]) | ||||
|             ->add('comment', ChillTextareaType::class, [ | ||||
|                 'required' => false, | ||||
|                 'label' => 'Comment', | ||||
|   | ||||
| @@ -11,17 +11,27 @@ declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\Templating; | ||||
|  | ||||
| use Chill\MainBundle\Entity\NotificationComment; | ||||
| use Chill\MainBundle\Form\NotificationCommentType; | ||||
| use Chill\MainBundle\Notification\NotificationPresence; | ||||
| use Symfony\Component\Form\FormFactoryInterface; | ||||
| use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | ||||
| use Twig\Environment; | ||||
| use Twig\Extension\RuntimeExtensionInterface; | ||||
|  | ||||
| class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface | ||||
| { | ||||
|     private FormFactoryInterface $formFactory; | ||||
|  | ||||
|     private NotificationPresence $notificationPresence; | ||||
|  | ||||
|     public function __construct(NotificationPresence $notificationPresence) | ||||
|     private UrlGeneratorInterface $urlGenerator; | ||||
|  | ||||
|     public function __construct(FormFactoryInterface $formFactory, NotificationPresence $notificationPresence, UrlGeneratorInterface $urlGenerator) | ||||
|     { | ||||
|         $this->formFactory = $formFactory; | ||||
|         $this->notificationPresence = $notificationPresence; | ||||
|         $this->urlGenerator = $urlGenerator; | ||||
|     } | ||||
|  | ||||
|     public function counterNotificationFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string | ||||
| @@ -47,8 +57,24 @@ class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface | ||||
|             return ''; | ||||
|         } | ||||
|  | ||||
|         $appendCommentForms = []; | ||||
|  | ||||
|         foreach ($notifications as $notification) { | ||||
|             $appendComment = new NotificationComment(); | ||||
|             $appendCommentForms[$notification->getId()] = $this->formFactory->create( | ||||
|                 NotificationCommentType::class, | ||||
|                 $appendComment, | ||||
|                 [ | ||||
|                     'action' => $this->urlGenerator->generate( | ||||
|                         'chill_main_notification_show', | ||||
|                         ['id' => $notification->getId()] | ||||
|                     ), | ||||
|                 ] | ||||
|             )->createView(); | ||||
|         } | ||||
|  | ||||
|         return $environment->render('@ChillMain/Notification/extension_list_notifications_for.html.twig', [ | ||||
|             'notifications' => $notifications, | ||||
|             'notifications' => $notifications, 'appendCommentForms' => $appendCommentForms, | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -193,6 +193,29 @@ final class NotificationRepository implements ObjectRepository | ||||
|         return $this->repository->findOneBy($criteria, $orderBy); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return array|Notification[] | ||||
|      */ | ||||
|     public function findUnreadByUser(User $user, int $limit = 20, int $offset = 0): array | ||||
|     { | ||||
|         $rsm = new Query\ResultSetMappingBuilder($this->em); | ||||
|         $rsm->addRootEntityFromClassMetadata(Notification::class, 'cmn'); | ||||
|  | ||||
|         $sql = 'SELECT ' . $rsm->generateSelectClause(['cmn' => 'cmn']) . ' ' . | ||||
|         'FROM chill_main_notification cmn ' . | ||||
|         'WHERE ' . | ||||
|               'EXISTS (select 1 FROM chill_main_notification_addresses_unread cmnau WHERE cmnau.user_id = :userId and cmnau.notification_id = cmn.id) ' . | ||||
|         'ORDER BY cmn.date DESC ' . | ||||
|         'LIMIT :limit OFFSET :offset'; | ||||
|  | ||||
|         $nq = $this->em->createNativeQuery($sql, $rsm) | ||||
|             ->setParameter('userId', $user->getId()) | ||||
|             ->setParameter('limit', $limit) | ||||
|             ->setParameter('offset', $offset); | ||||
|  | ||||
|         return $nq->getResult(); | ||||
|     } | ||||
|  | ||||
|     public function getClassName() | ||||
|     { | ||||
|         return Notification::class; | ||||
|   | ||||
| @@ -0,0 +1,59 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * 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. | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\MainBundle\Repository; | ||||
|  | ||||
| use Chill\MainBundle\Entity\ResidentialAddress; | ||||
| use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; | ||||
| use Doctrine\Persistence\ManagerRegistry; | ||||
|  | ||||
| /** | ||||
|  * @method ResidentialAddress|null find($id, $lockMode = null, $lockVersion = null) | ||||
|  * @method ResidentialAddress|null findOneBy(array $criteria, array $orderBy = null) | ||||
|  * @method ResidentialAddress[]    findAll() | ||||
|  * @method ResidentialAddress[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) | ||||
|  */ | ||||
| class ResidentialAddressRepository extends ServiceEntityRepository | ||||
| { | ||||
|     public function __construct(ManagerRegistry $registry) | ||||
|     { | ||||
|         parent::__construct($registry, ResidentialAddress::class); | ||||
|     } | ||||
|  | ||||
|     // /** | ||||
|     //  * @return ResidentialAddress[] Returns an array of ResidentialAddress objects | ||||
|     //  */ | ||||
|     /* | ||||
|     public function findByExampleField($value) | ||||
|     { | ||||
|         return $this->createQueryBuilder('r') | ||||
|             ->andWhere('r.exampleField = :val') | ||||
|             ->setParameter('val', $value) | ||||
|             ->orderBy('r.id', 'ASC') | ||||
|             ->setMaxResults(10) | ||||
|             ->getQuery() | ||||
|             ->getResult() | ||||
|         ; | ||||
|     } | ||||
|      */ | ||||
|  | ||||
|     /* | ||||
|     public function findOneBySomeField($value): ?ResidentialAddress | ||||
|     { | ||||
|         return $this->createQueryBuilder('r') | ||||
|             ->andWhere('r.exampleField = :val') | ||||
|             ->setParameter('val', $value) | ||||
|             ->getQuery() | ||||
|             ->getOneOrNullResult() | ||||
|         ; | ||||
|     } | ||||
|      */ | ||||
| } | ||||
| @@ -474,6 +474,7 @@ div.workflow { | ||||
| // Override bootstrap popover styles | ||||
| div.popover { | ||||
|     box-shadow: 0 0 10px -5px $dark; | ||||
|     z-index: 9999; | ||||
|     .popover-arrow {} | ||||
|     .popover-header {} | ||||
|     .popover-body {} | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
|         display: block; | ||||
|         top: calc(50% - 7px); | ||||
|         right: 10px; | ||||
|         line-height: 11px; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -62,14 +63,19 @@ ul.list-suggest { | ||||
|             & span:hover { | ||||
|                 color: $chill-l-gray; | ||||
|             } | ||||
|             .person-text { | ||||
|                 span { | ||||
|                     padding-left: 0px; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     &.remove-items { | ||||
|         li { | ||||
|             position: relative; | ||||
|             span { | ||||
|             & > span { | ||||
|                 display: block; | ||||
|                 padding-right: .75rem; | ||||
|                 padding-right: 1.75rem; | ||||
|                 @include remove_link; | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -21,7 +21,8 @@ $chill-theme-buttons: ( | ||||
|    "download":      $gray-300, | ||||
|    "cancel":        $gray-300, | ||||
|    "choose":        $gray-300, | ||||
|    "notify":        $gray-300, | ||||
|    "notify":        $chill-blue, | ||||
|    "search":        $gray-300, | ||||
|    "unlink":        $chill-red, | ||||
|    "tpchild":       $chill-pink, | ||||
| ); | ||||
| @@ -80,6 +81,7 @@ $chill-theme-buttons: ( | ||||
|    &.btn-notify::before, | ||||
|    &.btn-tpchild::before, | ||||
|    &.btn-download::before, | ||||
|    &.btn-search::before, | ||||
|    &.btn-cancel::before { | ||||
|       font: normal normal normal 14px/1 ForkAwesome; | ||||
|       margin-right: 0.5em; | ||||
| @@ -108,6 +110,7 @@ $chill-theme-buttons: ( | ||||
|    &.btn-notify::before    { content: "\f1d8"; } // fa-paper-plane | ||||
|    &.btn-tpchild::before   { content: "\f007"; } // fa-user | ||||
|    &.btn-download::before  { content: "\f019"; } // fa-download | ||||
|    &.btn-search::before    { content: "\f002"; } // fa-search | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -133,3 +136,18 @@ $chill-theme-buttons: ( | ||||
| .btn-sm, .btn-group-sm > .btn { | ||||
|    min-width: 36px; | ||||
| } | ||||
|  | ||||
| // Homepage special fast action buttons | ||||
| div.sticky-buttons { | ||||
|     position: fixed; | ||||
|     bottom: 3em; | ||||
|     right: 2em; | ||||
|     .btn-circle { | ||||
|         width: 50px; height: 50px; | ||||
|         border-radius: 50%; | ||||
|         text-align: center; | ||||
|         padding: 0.45rem 0.7rem; | ||||
|         display: block; | ||||
|         margin-bottom: 0.5rem; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -39,6 +39,7 @@ var ShowHide = function(options) { | ||||
|                 contents.push(el); | ||||
|             } | ||||
|             container_content.push(contents); | ||||
|             // console.log('container content', container_content); | ||||
|         } | ||||
|  | ||||
|         // attach the listener on each input | ||||
|   | ||||
| @@ -1,21 +1,19 @@ | ||||
| require('./blur.scss'); | ||||
|  | ||||
| var toggleBlur =  function(e){ | ||||
|  | ||||
|     var btn = e.target; | ||||
|  | ||||
|     btn.previousElementSibling.classList.toggle("blur"); | ||||
|     btn.classList.toggle("fa-eye"); | ||||
|     btn.classList.toggle("fa-eye-slash"); | ||||
|  | ||||
| } | ||||
|  | ||||
| var infos = document.getElementsByClassName("confidential"); | ||||
| for(var i=0; i < infos.length; i++){ | ||||
|     infos[i].insertAdjacentHTML('beforeend', '<i class="fa fa-eye toggle" aria-hidden="true"></i>'); | ||||
| } | ||||
|  | ||||
| var toggles = document.getElementsByClassName("toggle"); | ||||
| for(var i=0; i < toggles.length; i++){ | ||||
|     toggles[i].addEventListener("click", toggleBlur); | ||||
| } | ||||
| document.querySelectorAll('.confidential').forEach(function (el) { | ||||
|     let i = document.createElement('i'); | ||||
|     const classes = ['fa', 'fa-eye', 'toggle']; | ||||
|     i.classList.add(...classes); | ||||
|     el.appendChild(i); | ||||
|     const toggleBlur = function(e) { | ||||
|         for (let child of el.children) { | ||||
|             if (!child.classList.contains('toggle')) { | ||||
|                 child.classList.toggle('blur'); | ||||
|             } | ||||
|         } | ||||
|         i.classList.toggle('fa-eye'); | ||||
|         i.classList.toggle('fa-eye-slash'); | ||||
|     } | ||||
|     i.addEventListener('click', toggleBlur); | ||||
|     toggleBlur(); | ||||
| }); | ||||
|   | ||||
| @@ -1,49 +1,34 @@ | ||||
| import { createApp } from "vue"; | ||||
| import PickWorkflowVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue'; | ||||
| import ListWorkflowVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflow.vue'; | ||||
| import ListWorkflowModalVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue'; | ||||
| import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n"; | ||||
|  | ||||
| // pick workflow | ||||
| document.querySelectorAll('[data-pick-workflow]') | ||||
|     .forEach(function(el) { | ||||
|         const app = { | ||||
|             components: { | ||||
|                 PickWorkflowVue | ||||
|             }, | ||||
|             template: | ||||
|                 '<pick-workflow-vue ' + | ||||
|                     ':relatedEntityClass="relatedEntityClass" ' + | ||||
|                     ':relatedEntityId="relatedEntityId" ' + | ||||
|                     ':workflowsAvailables="workflowsAvailables" ' + | ||||
|                 '></pick-workflow-vue>', | ||||
|             data() { | ||||
|                 return { | ||||
|                     relatedEntityClass: el.dataset.relatedEntityClass, | ||||
|                     relatedEntityId: Number.parseInt(el.dataset.relatedEntityId), | ||||
|                     workflowsAvailables: JSON.parse(el.dataset.workflowsAvailables), | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|         createApp(app).mount(el); | ||||
|     }) | ||||
| ; | ||||
| const i18n = _createI18n({}); | ||||
|  | ||||
| // list workflow | ||||
| document.querySelectorAll('[data-list-workflows]') | ||||
|     .forEach(function (el) { | ||||
|         const app = { | ||||
|             components: { | ||||
|                 ListWorkflowVue, | ||||
|                 ListWorkflowModalVue, | ||||
|             }, | ||||
|             template: | ||||
|                 '<list-workflow-vue ' + | ||||
|                 '<list-workflow-modal-vue ' + | ||||
|                     ':workflows="workflows" ' + | ||||
|                 '></list-workflow-vue>', | ||||
|                     ':allowCreate="allowCreate" ' + | ||||
|                     ':relatedEntityClass="relatedEntityClass" ' + | ||||
|                     ':relatedEntityId="relatedEntityId" ' + | ||||
|                     ':workflowsAvailables="workflowsAvailables" ' + | ||||
|                 '></list-workflow-modal-vue>', | ||||
|             data() { | ||||
|                 return { | ||||
|                     workflows: JSON.parse(el.dataset.workflows), | ||||
|                     allowCreate: el.dataset.allowCreate === "1", | ||||
|                     relatedEntityClass: el.dataset.relatedEntityClass, | ||||
|                     relatedEntityId: Number.parseInt(el.dataset.relatedEntityId), | ||||
|                     workflowsAvailables: JSON.parse(el.dataset.workflowsAvailables), | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|         createApp(app).mount(el); | ||||
|         createApp(app).use(i18n).mount(el); | ||||
|     }) | ||||
| ; | ||||
| @@ -5,18 +5,27 @@ import { appMessages } from 'ChillMainAssets/vuejs/PickEntity/i18n'; | ||||
|  | ||||
| const i18n = _createI18n(appMessages); | ||||
|  | ||||
| window.addEventListener('DOMContentLoaded', function(e) { | ||||
| let appsOnPage = new Map(); | ||||
|  | ||||
|     let apps = document.querySelectorAll('[data-module="pick-dynamic"]'); | ||||
| function loadDynamicPicker(element) { | ||||
|  | ||||
|     let apps = element.querySelectorAll('[data-module="pick-dynamic"]'); | ||||
|  | ||||
|     apps.forEach(function(el) { | ||||
|  | ||||
|         const | ||||
|             isMultiple = parseInt(el.dataset.multiple) === 1, | ||||
|             input = document.querySelector('[data-input-uniqid="'+ el.dataset.uniqid +'"]'), | ||||
|             picked = isMultiple ? JSON.parse(input.value) : [JSON.parse(input.value)]; | ||||
|             uniqId = el.dataset.uniqid, | ||||
|             input = element.querySelector('[data-input-uniqid="'+ el.dataset.uniqid +'"]'), | ||||
|             picked = (isMultiple) ? (JSON.parse(input.value)) : ((input.value === '[]') ? (null) : ([JSON.parse(input.value)])); | ||||
|  | ||||
|         createApp({ | ||||
|         if (!isMultiple) { | ||||
|             if (input.value === '[]'){ | ||||
|                 input.value = null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         const app = createApp({ | ||||
|             template: '<pick-entity ' + | ||||
|                 ':multiple="multiple" ' + | ||||
|                 ':types="types" ' + | ||||
| @@ -65,5 +74,36 @@ window.addEventListener('DOMContentLoaded', function(e) { | ||||
|         }) | ||||
|         .use(i18n) | ||||
|         .mount(el); | ||||
|  | ||||
|         appsOnPage.set(uniqId, app); | ||||
|     }); | ||||
| }); | ||||
| } | ||||
|  | ||||
|  | ||||
| document.addEventListener('show-hide-show', function(e) { | ||||
|     console.log('creation event caught') | ||||
|     loadDynamicPicker(e.detail.container) | ||||
| }) | ||||
|  | ||||
| document.addEventListener('show-hide-hide', function(e) { | ||||
|     console.log('hiding event caught') | ||||
|     e.detail.container.querySelectorAll('[data-module="pick-dynamic"]').forEach((el) => { | ||||
|         let uniqId = el.dataset.uniqid; | ||||
|         console.log(uniqId); | ||||
|         if (appsOnPage.has(uniqId)) { | ||||
|             appsOnPage.get(uniqId).unmount(); | ||||
|             console.log('App has been unmounted') | ||||
|             appsOnPage.delete(uniqId); | ||||
|         } | ||||
|     }) | ||||
| }) | ||||
|  | ||||
| document.addEventListener('DOMContentLoaded', function(e) { | ||||
|     console.log('loaded event', e) | ||||
|     loadDynamicPicker(document) | ||||
| }) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,16 @@ | ||||
| import { createApp } from 'vue'; | ||||
| import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'; | ||||
| import { appMessages } from 'ChillMainAssets/vuejs/HomepageWidget/js/i18n'; | ||||
| import { store } from 'ChillMainAssets/vuejs/HomepageWidget/js/store'; | ||||
| import App from 'ChillMainAssets/vuejs/HomepageWidget/App'; | ||||
|  | ||||
| const i18n = _createI18n(appMessages); | ||||
|  | ||||
| const app = createApp({ | ||||
|     template: `<app></app>`, | ||||
| }) | ||||
| .use(store) | ||||
| .use(i18n) | ||||
| .component('app', App) | ||||
| .mount('#homepage_widget') | ||||
| ; | ||||
| @@ -562,6 +562,7 @@ export default { | ||||
|          this.entity.loaded.cities = []; | ||||
|          this.entity.loaded.countries = []; | ||||
|  | ||||
|          this.entity.selected.confidential = this.context.edit ? this.entity.address.confidential : false; | ||||
|          this.entity.selected.isNoAddress = (this.context.edit && this.entity.address.text === '') ? true : false; | ||||
|  | ||||
|          this.entity.selected.country = this.context.edit ? this.entity.address.country : {}; | ||||
| @@ -593,6 +594,7 @@ export default { | ||||
|       { | ||||
|          console.log('apply changes'); | ||||
|          let newAddress = { | ||||
|             'confidential': this.entity.selected.confidential, | ||||
|             'isNoAddress': this.entity.selected.isNoAddress, | ||||
|             'street': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.street, | ||||
|             'streetNumber': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.streetNumber, | ||||
|   | ||||
| @@ -6,7 +6,6 @@ | ||||
|             <input class="form-control" | ||||
|             type="text" | ||||
|             name="floor" | ||||
|             maxlength=16 | ||||
|             :placeholder="$t('floor')" | ||||
|             v-model="floor"/> | ||||
|             <label for="floor">{{ $t('floor') }}</label> | ||||
| @@ -15,7 +14,6 @@ | ||||
|             <input class="form-control" | ||||
|             type="text" | ||||
|             name="corridor" | ||||
|             maxlength=16 | ||||
|             :placeholder="$t('corridor')" | ||||
|             v-model="corridor"/> | ||||
|             <label for="corridor">{{ $t('corridor') }}</label> | ||||
| @@ -24,7 +22,6 @@ | ||||
|             <input class="form-control" | ||||
|             type="text" | ||||
|             name="steps" | ||||
|             maxlength=16 | ||||
|             :placeholder="$t('steps')" | ||||
|             v-model="steps"/> | ||||
|             <label for="steps">{{ $t('steps') }}</label> | ||||
| @@ -33,7 +30,6 @@ | ||||
|             <input class="form-control" | ||||
|             type="text" | ||||
|             name="flat" | ||||
|             maxlength=16 | ||||
|             :placeholder="$t('flat')" | ||||
|             v-model="flat"/> | ||||
|             <label for="flat">{{ $t('flat') }}</label> | ||||
|   | ||||
| @@ -17,12 +17,22 @@ | ||||
|       <div class="row my-3"> | ||||
|          <div class="col-lg-6"> | ||||
|  | ||||
|             <div class="form-check"> | ||||
|                <input type="checkbox" | ||||
|                   class="form-check-input" | ||||
|                   id="isConfidential" | ||||
|                   v-model="isConfidential" | ||||
|                   :value="valueConfidential" /> | ||||
|                <label class="form-check-label" for="isConfidential"> | ||||
|                   {{ $t('isConfidential') }} | ||||
|                </label> | ||||
|             </div> | ||||
|             <div class="form-check"> | ||||
|                <input type="checkbox" | ||||
|                   class="form-check-input" | ||||
|                   id="isNoAddress" | ||||
|                   v-model="isNoAddress" | ||||
|                   v-bind:value="value" /> | ||||
|                   :value="value" /> | ||||
|                <label class="form-check-label" for="isNoAddress"> | ||||
|                   {{ $t('isNoAddress') }} | ||||
|                </label> | ||||
| @@ -118,7 +128,8 @@ export default { | ||||
|    emits: ['getCities', 'getReferenceAddresses'], | ||||
|    data() { | ||||
|       return { | ||||
|          value: false | ||||
|          value: false, | ||||
|          valueConfidential: false, | ||||
|       } | ||||
|    }, | ||||
|    computed: { | ||||
| @@ -134,6 +145,14 @@ export default { | ||||
|       addressMap() { | ||||
|          return this.entity.addressMap; | ||||
|       }, | ||||
|       isConfidential: { | ||||
|          set(value) { | ||||
|             this.entity.selected.confidential = value; | ||||
|          }, | ||||
|          get() { | ||||
|             return this.entity.selected.confidential; | ||||
|          } | ||||
|       }, | ||||
|       isNoAddress: { | ||||
|          set(value) { | ||||
|             console.log('isNoAddress value', value); | ||||
|   | ||||
| @@ -18,6 +18,7 @@ const addressMessages = { | ||||
|         other_address: 'Autre adresse', | ||||
|         create_address: 'Adresse inconnue. Cliquez ici pour créer une nouvelle adresse', | ||||
|         isNoAddress: 'Pas d\'adresse complète', | ||||
|         isConfidential: 'Adresse confidentielle', | ||||
|         street: 'Nom de rue', | ||||
|         streetNumber: 'Numéro', | ||||
|         floor: 'Étage', | ||||
|   | ||||
| @@ -0,0 +1,140 @@ | ||||
| <template> | ||||
|     | ||||
|    <h2>{{ $t('main_title') }}</h2> | ||||
|     | ||||
|    <ul class="nav nav-tabs"> | ||||
|       <li class="nav-item"> | ||||
|          <a class="nav-link" | ||||
|             :class="{'active': activeTab === 'MyCustoms'}" | ||||
|             @click="selectTab('MyCustoms')"> | ||||
|             <i class="fa fa-dashboard"></i> | ||||
|          </a> | ||||
|       </li> | ||||
|       <li class="nav-item"> | ||||
|          <a class="nav-link" | ||||
|             :class="{'active': activeTab === 'MyNotifications'}" | ||||
|             @click="selectTab('MyNotifications')"> | ||||
|             {{ $t('my_notifications.tab') }} | ||||
|             <tab-counter :count="state.notifications.count"></tab-counter> | ||||
|          </a> | ||||
|       </li> | ||||
|       <li class="nav-item"> | ||||
|          <a class="nav-link" | ||||
|             :class="{'active': activeTab === 'MyAccompanyingCourses'}" | ||||
|             @click="selectTab('MyAccompanyingCourses')"> | ||||
|             {{ $t('my_accompanying_courses.tab') }} | ||||
|             <tab-counter :count="state.accompanyingCourses.count"></tab-counter> | ||||
|          </a> | ||||
|       </li> | ||||
|       <li class="nav-item"> | ||||
|          <a class="nav-link" | ||||
|             :class="{'active': activeTab === 'MyWorks'}" | ||||
|             @click="selectTab('MyWorks')"> | ||||
|             {{ $t('my_works.tab') }} | ||||
|             <tab-counter :count="state.works.count"></tab-counter> | ||||
|          </a> | ||||
|       </li> | ||||
|       <li class="nav-item"> | ||||
|          <a class="nav-link" | ||||
|             :class="{'active': activeTab === 'MyEvaluations'}" | ||||
|             @click="selectTab('MyEvaluations')"> | ||||
|             {{ $t('my_evaluations.tab') }} | ||||
|             <tab-counter :count="state.evaluations.count"></tab-counter> | ||||
|          </a> | ||||
|       </li> | ||||
|       <li class="nav-item"> | ||||
|          <a class="nav-link" | ||||
|             :class="{'active': activeTab === 'MyTasks'}" | ||||
|             @click="selectTab('MyTasks')"> | ||||
|             {{ $t('my_tasks.tab') }} | ||||
|             <tab-counter :count="state.tasks.warning.count"></tab-counter> | ||||
|             <tab-counter :count="state.tasks.alert.count"></tab-counter> | ||||
|          </a> | ||||
|       </li> | ||||
|       <li class="nav-item loading ms-auto py-2" v-if="loading"> | ||||
|          <i class="fa fa-circle-o-notch fa-spin fa-lg text-chill-gray" :title="$t('loading')"></i> | ||||
|       </li> | ||||
|    </ul> | ||||
|     | ||||
|    <div class="my-4"> | ||||
|       <my-customs | ||||
|          v-if="activeTab === 'MyCustoms'"> | ||||
|       </my-customs> | ||||
|       <my-works | ||||
|          v-else-if="activeTab === 'MyWorks'"> | ||||
|       </my-works> | ||||
|       <my-evaluations | ||||
|          v-else-if="activeTab === 'MyEvaluations'"> | ||||
|       </my-evaluations> | ||||
|       <my-tasks | ||||
|          v-else-if="activeTab === 'MyTasks'"> | ||||
|       </my-tasks> | ||||
|       <my-accompanying-courses | ||||
|          v-else-if="activeTab === 'MyAccompanyingCourses'"> | ||||
|       </my-accompanying-courses> | ||||
|       <my-notifications | ||||
|          v-else-if="activeTab === 'MyNotifications'"> | ||||
|       </my-notifications> | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import MyCustoms from './MyCustoms'; | ||||
| import MyWorks from './MyWorks'; | ||||
| import MyEvaluations from './MyEvaluations'; | ||||
| import MyTasks from './MyTasks'; | ||||
| import MyAccompanyingCourses from './MyAccompanyingCourses'; | ||||
| import MyNotifications from './MyNotifications'; | ||||
| import TabCounter from './TabCounter'; | ||||
| import { mapState } from "vuex"; | ||||
|  | ||||
| export default { | ||||
|    name: "App", | ||||
|    components: { | ||||
|       MyCustoms, | ||||
|       MyWorks, | ||||
|       MyEvaluations, | ||||
|       MyTasks, | ||||
|       MyAccompanyingCourses, | ||||
|       MyNotifications, | ||||
|       TabCounter, | ||||
|    }, | ||||
|    data() { | ||||
|       return { | ||||
|          activeTab: 'MyCustoms' | ||||
|       } | ||||
|    }, | ||||
|    computed: { | ||||
|       ...mapState([ | ||||
|          'loading', | ||||
|       ]), | ||||
|       // just to see all in devtool : | ||||
|       ...mapState({ | ||||
|          state: (state) => state, | ||||
|       }), | ||||
|    }, | ||||
|    methods: { | ||||
|       selectTab(tab) { | ||||
|          this.$store.dispatch('getByTab', { tab: tab }); | ||||
|          this.activeTab = tab; | ||||
|       } | ||||
|    }, | ||||
|    mounted() { | ||||
|       for (const m of [ | ||||
|          'MyNotifications', | ||||
|          'MyAccompanyingCourses', | ||||
|          'MyWorks', | ||||
|          'MyEvaluations', | ||||
|          'MyTasks', | ||||
|       ]) { | ||||
|          this.$store.dispatch('getByTab', { tab: m, param: "countOnly=1" }); | ||||
|       } | ||||
|    } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| a.nav-link { | ||||
|    cursor: pointer; | ||||
| } | ||||
| </style> | ||||
| @@ -0,0 +1,60 @@ | ||||
| <template> | ||||
|    <div class="alert alert-light">{{ $t('my_accompanying_courses.description') }}</div> | ||||
|    <span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span> | ||||
|    <tab-table v-else> | ||||
|       <template v-slot:thead> | ||||
|          <th scope="col">id</th> | ||||
|          <th scope="col">Ouvert le</th> | ||||
|          <th scope="col">Usagers concernés</th> | ||||
|          <th scope="col"></th> | ||||
|       </template> | ||||
|       <template v-slot:tbody> | ||||
|          <tr v-for="(c, i) in accompanyingCourses.results" :key="`course-${i}`"> | ||||
|             <td>{{ c.id}}</td> | ||||
|             <td>{{ $d(c.openingDate.datetime, 'long') }}</td> | ||||
|             <td>{{ c.participations.length }}</td> | ||||
|             <td> | ||||
|                <a class="btn btn-sm btn-show" :href="getUrl(c)"> | ||||
|                   {{ $t('show_entity', { entity: $t('the_course') }) }} | ||||
|                </a> | ||||
|             </td> | ||||
|          </tr> | ||||
|       </template> | ||||
|    </tab-table> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapState, mapGetters } from "vuex"; | ||||
| import TabTable from "./TabTable"; | ||||
|  | ||||
| export default { | ||||
|    name: "MyAccompanyingCourses", | ||||
|    components: { | ||||
|       TabTable | ||||
|    }, | ||||
|    computed: { | ||||
|       ...mapState([ | ||||
|          'accompanyingCourses', | ||||
|       ]), | ||||
|       ...mapGetters([ | ||||
|          'isAccompanyingCoursesLoaded', | ||||
|       ]), | ||||
|       noResults() { | ||||
|          if (!this.isAccompanyingCoursesLoaded) { | ||||
|             return false; | ||||
|          } else { | ||||
|             return this.accompanyingCourses.count === 0; | ||||
|          } | ||||
|       }, | ||||
|    }, | ||||
|    methods: { | ||||
|       getUrl(c) { | ||||
|          return `/fr/parcours/${c.id}` | ||||
|       } | ||||
|    } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
|  | ||||
| </style> | ||||
| @@ -0,0 +1,77 @@ | ||||
| <template> | ||||
|    <span v-if="noResults" class="chill-no-data-statement">{{ $t('no_dashboard') }}</span> | ||||
|    <div v-else id="dashboards" class="row g-3" data-masonry='{"percentPosition": true }'> | ||||
|        | ||||
|       <div class="mbloc col col-sm-6 col-lg-4"> | ||||
|          <div class="custom1"> | ||||
|             <ul class="list-unstyled"> | ||||
|                <li v-if="counter.notifications > 0"> | ||||
|                   <b>{{ counter.notifications }}</b> {{ $t('counter.unread_notifications') }} | ||||
|                </li> | ||||
|                <li v-if="counter.accompanyingCourses > 0"> | ||||
|                   <b>{{ counter.accompanyingCourses }}</b> {{ $t('counter.assignated_courses') }} | ||||
|                </li> | ||||
|                <li v-if="counter.works > 0"> | ||||
|                   <b>{{ counter.works }}</b> {{ $t('counter.assignated_actions') }} | ||||
|                </li> | ||||
|                <li v-if="counter.evaluations > 0"> | ||||
|                   <b>{{ counter.evaluations }}</b> {{ $t('counter.assignated_evaluations') }} | ||||
|                </li> | ||||
|                <li v-if="counter.tasksAlert > 0"> | ||||
|                   <b>{{ counter.tasksAlert }}</b> {{ $t('counter.alert_tasks') }} | ||||
|                </li> | ||||
|                <li v-if="counter.tasksWarning > 0"> | ||||
|                   <b>{{ counter.tasksWarning }}</b> {{ $t('counter.warning_tasks') }} | ||||
|                </li> | ||||
|             </ul> | ||||
|          </div> | ||||
|       </div> | ||||
|        | ||||
|       <!-- | ||||
|       <div class="mbloc col col-sm-6 col-lg-4"> | ||||
|          <div class="custom2"> | ||||
|             Mon dashboard personnalisé | ||||
|          </div> | ||||
|       </div> | ||||
|       <div class="mbloc col col-sm-6 col-lg-4"> | ||||
|          <div class="custom3"> | ||||
|             Mon dashboard personnalisé | ||||
|          </div> | ||||
|       </div> | ||||
|       <div class="mbloc col col-sm-6 col-lg-4"> | ||||
|          <div class="custom4"> | ||||
|             Mon dashboard personnalisé | ||||
|          </div> | ||||
|       </div> | ||||
|       --> | ||||
|  | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapGetters } from "vuex"; | ||||
| import Masonry from 'masonry-layout/masonry'; | ||||
|  | ||||
| export default { | ||||
|    name: "MyCustoms", | ||||
|    computed: { | ||||
|       ...mapGetters(['counter']), | ||||
|       noResults() { | ||||
|          return false | ||||
|       }, | ||||
|    }, | ||||
|    mounted() { | ||||
|       const elem = document.querySelector('#dashboards'); | ||||
|       const masonry = new Masonry(elem, {}); | ||||
|    } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| div.custom4, | ||||
| div.custom3, | ||||
| div.custom2 { | ||||
|    font-style: italic; | ||||
|    color: var(--bs-chill-gray); | ||||
| } | ||||
| </style> | ||||
| @@ -0,0 +1,57 @@ | ||||
| <template> | ||||
|    <div class="alert alert-light">{{ $t('my_evaluations.description') }}</div> | ||||
|    <span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span> | ||||
|    <tab-table v-else> | ||||
|       <template v-slot:thead> | ||||
|          <th scope="col">id</th> | ||||
|          <th scope="col"></th> | ||||
|       </template> | ||||
|       <template v-slot:tbody> | ||||
|          <tr v-for="(e, i) in evaluations.results" :key="`evaluation-${i}`"> | ||||
|             <td>{{ e.id}}</td> | ||||
|             <td> | ||||
|                <a class="btn btn-sm btn-show" :href="getUrl(e)"> | ||||
|                   {{ $t('show_entity', { entity: $t('the_evaluation') }) }} | ||||
|                </a> | ||||
|             </td> | ||||
|          </tr> | ||||
|       </template> | ||||
|    </tab-table> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapState, mapGetters } from "vuex"; | ||||
| import TabTable from "./TabTable"; | ||||
|  | ||||
| export default { | ||||
|    name: "MyEvaluations", | ||||
|    components: { | ||||
|       TabTable | ||||
|    }, | ||||
|    computed: { | ||||
|       ...mapState([ | ||||
|          'evaluations', | ||||
|       ]), | ||||
|       ...mapGetters([ | ||||
|          'isEvaluationsLoaded', | ||||
|       ]), | ||||
|       noResults() { | ||||
|          if (!this.isEvaluationsLoaded) { | ||||
|             return false; | ||||
|          } else { | ||||
|             return this.evaluations.count === 0; | ||||
|          } | ||||
|       } | ||||
|    }, | ||||
|    methods: { | ||||
|       getUrl(e) { | ||||
|          let anchor = '#evaluations'; | ||||
|          return `/fr/person/accompanying-period/work/${e.id}/edit${anchor}` | ||||
|       } | ||||
|    }, | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
|  | ||||
| </style> | ||||
| @@ -0,0 +1,96 @@ | ||||
| <template> | ||||
|    <div class="alert alert-light">{{ $t('my_notifications.description') }}</div> | ||||
|    <span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span> | ||||
|    <tab-table v-else> | ||||
|       <template v-slot:thead> | ||||
|          <th scope="col">{{ $t('Date') }}</th> | ||||
|          <th scope="col">{{ $t('Subject') }}</th> | ||||
|          <th scope="col">{{ $t('From') }}</th> | ||||
|          <th scope="col"></th> | ||||
|       </template> | ||||
|       <template v-slot:tbody> | ||||
|          <tr v-for="(n, i) in notifications.results" :key="`notify-${i}`"> | ||||
|             <td>{{ $d(n.date.datetime, 'long') }}</td> | ||||
|             <td> | ||||
|                <span class="unread"> | ||||
|                   <i class="fa fa-envelope-o"></i> | ||||
|                   <a :href="getNotificationUrl(n)">{{ n.title }}</a> | ||||
|                </span> | ||||
|             </td> | ||||
|             <td>{{ n.sender.text }}</td> | ||||
|             <td> | ||||
|                <a class="btn btn-sm btn-show" | ||||
|                   :href="getEntityUrl(n)"> | ||||
|                   {{ $t('show_entity', { entity: getEntityName(n) }) }} | ||||
|                </a> | ||||
|             </td> | ||||
|          </tr> | ||||
|       </template> | ||||
|    </tab-table> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapState, mapGetters } from "vuex"; | ||||
| import TabTable from "./TabTable"; | ||||
| import { appMessages } from 'ChillMainAssets/vuejs/HomepageWidget/js/i18n'; | ||||
|  | ||||
|  | ||||
| export default { | ||||
|    name: "MyNotifications", | ||||
|    components: { | ||||
|       TabTable | ||||
|    }, | ||||
|    computed: { | ||||
|       ...mapState([ | ||||
|          'notifications', | ||||
|       ]), | ||||
|       ...mapGetters([ | ||||
|          'isNotificationsLoaded', | ||||
|       ]), | ||||
|       noResults() { | ||||
|          if (!this.isNotificationsLoaded) { | ||||
|             return false; | ||||
|          } else { | ||||
|             return this.notifications.count === 0; | ||||
|          } | ||||
|       } | ||||
|    }, | ||||
|    methods: { | ||||
|       getNotificationUrl(n) { | ||||
|          return `/fr/notification/${n.id}/show` | ||||
|       }, | ||||
|       getEntityName(n) { | ||||
|          switch (n.relatedEntityClass) { | ||||
|             case 'Chill\\ActivityBundle\\Entity\\Activity': | ||||
|                return appMessages.fr.the_activity; | ||||
|             case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod': | ||||
|                return appMessages.fr.the_course; | ||||
|             default: | ||||
|                throw 'notification type unknown'; | ||||
|          } | ||||
|       }, | ||||
|       getEntityUrl(n) { | ||||
|          switch (n.relatedEntityClass) { | ||||
|             case 'Chill\\ActivityBundle\\Entity\\Activity': | ||||
|                return `/fr/activity/${n.relatedEntityId}/show` | ||||
|             case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod': | ||||
|                return `/fr/parcours/${n.relatedEntityId}` | ||||
|             default: | ||||
|                throw 'notification type unknown'; | ||||
|          } | ||||
|       } | ||||
|    } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| span.unread { | ||||
|    font-weight: bold; | ||||
|    i { | ||||
|       margin-right: 0.5em; | ||||
|    } | ||||
|    a { | ||||
|       text-decoration: unset; | ||||
|    } | ||||
| } | ||||
| </style> | ||||
| @@ -0,0 +1,85 @@ | ||||
| <template> | ||||
|     | ||||
|    <div class="alert alert-light">{{ $t('my_tasks.description_alert') }}</div> | ||||
|    <span v-if="noResultsWarning" class="chill-no-data-statement">{{ $t('no_data') }}</span> | ||||
|    <tab-table v-else> | ||||
|       <template v-slot:thead> | ||||
|          <th scope="col">id</th> | ||||
|          <th scope="col"></th> | ||||
|       </template> | ||||
|       <template v-slot:tbody> | ||||
|          <tr v-for="(t, i) in tasks.warning" :key="`task-warning-${i}`"> | ||||
|             <td>{{ t.id}}</td> | ||||
|             <td> | ||||
|                <a class="btn btn-sm btn-show" :href="getUrl(t)"> | ||||
|                   {{ $t('show_entity', { entity: $t('the_task') }) }} | ||||
|                </a> | ||||
|             </td> | ||||
|          </tr> | ||||
|       </template> | ||||
|    </tab-table> | ||||
|     | ||||
|    <div class="alert alert-light">{{ $t('my_tasks.description_warning') }}</div> | ||||
|    <span v-if="noResultsAlert" class="chill-no-data-statement">{{ $t('no_data') }}</span> | ||||
|    <tab-table v-else> | ||||
|       <template v-slot:thead> | ||||
|          <th scope="col">id</th> | ||||
|          <th scope="col"></th> | ||||
|       </template> | ||||
|       <template v-slot:tbody> | ||||
|          <tr v-for="(t, i) in tasks.alert" :key="`task-alert-${i}`"> | ||||
|             <td>{{ t.id}}</td> | ||||
|             <td> | ||||
|                <a class="btn btn-sm btn-show" :href="getUrl(t)"> | ||||
|                   {{ $t('show_entity', { entity: $t('the_task') }) }} | ||||
|                </a> | ||||
|             </td> | ||||
|          </tr> | ||||
|       </template> | ||||
|    </tab-table> | ||||
|     | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapState, mapGetters } from "vuex"; | ||||
| import TabTable from "./TabTable"; | ||||
|  | ||||
| export default { | ||||
|    name: "MyTasks", | ||||
|    components: { | ||||
|       TabTable | ||||
|    }, | ||||
|    computed: { | ||||
|       ...mapState([ | ||||
|          'tasks', | ||||
|       ]), | ||||
|       ...mapGetters([ | ||||
|          'isTasksWarningLoaded', | ||||
|          'isTasksAlertLoaded', | ||||
|       ]), | ||||
|       noResultsAlert() { | ||||
|          if (!this.isTasksAlertLoaded) { | ||||
|             return false; | ||||
|          } else { | ||||
|             return this.tasks.alert.count === 0; | ||||
|          } | ||||
|       }, | ||||
|       noResultsWarning() { | ||||
|          if (!this.isTasksWarningLoaded) { | ||||
|             return false; | ||||
|          } else { | ||||
|             return this.tasks.warning.count === 0; | ||||
|          } | ||||
|       } | ||||
|    }, | ||||
|    methods: { | ||||
|       getUrl(t) { | ||||
|          return `/fr/task/single-task/${t.id}/show` | ||||
|       } | ||||
|    }, | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
|  | ||||
| </style> | ||||
| @@ -0,0 +1,79 @@ | ||||
| <template> | ||||
| <div class="accompanying_course_work"> | ||||
|    <div class="alert alert-light">{{ $t('my_works.description') }}</div> | ||||
|    <span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span> | ||||
|    <tab-table v-else> | ||||
|       <template v-slot:thead> | ||||
|          <th scope="col">{{ $t('StartDate') }}</th> | ||||
|          <th scope="col">{{ $t('SocialAction') }}</th> | ||||
|          <th scope="col"></th> | ||||
|       </template> | ||||
|       <template v-slot:tbody> | ||||
|          <tr v-for="(w, i) in works.results" :key="`works-${i}`"> | ||||
|             <td>{{ $d(w.startDate.datetime, 'short') }}</td> | ||||
|             <td> | ||||
|                <h4 class="badge-title"> | ||||
|                   <span class="title_label"></span> | ||||
|                   <span class="title_action"> | ||||
|                      {{ w.socialAction.text }} | ||||
|                   </span> | ||||
|                </h4> | ||||
|             </td> | ||||
|             <td> | ||||
|                <div class="btn-group" role="group" aria-label="Actions"> | ||||
|                   <a class="btn btn-sm btn-update" :href="getUrl(w)"> | ||||
|                      {{ $t('show_entity', { entity: $t('the_action') }) }} | ||||
|                   </a> | ||||
|                   <a class="btn btn-sm btn-show" :href="getUrl(w.accompanyingPeriod)"> | ||||
|                      {{ $t('show_entity', { entity: $t('the_course') }) }} | ||||
|                   </a> | ||||
|                </div> | ||||
|             </td> | ||||
|          </tr> | ||||
|       </template> | ||||
|    </tab-table> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapState, mapGetters } from "vuex"; | ||||
| import TabTable from "./TabTable"; | ||||
|  | ||||
| export default { | ||||
|    name: "MyWorks", | ||||
|    components: { | ||||
|       TabTable | ||||
|    }, | ||||
|    computed: { | ||||
|       ...mapState([ | ||||
|          'works', | ||||
|       ]), | ||||
|       ...mapGetters([ | ||||
|          'isWorksLoaded', | ||||
|       ]), | ||||
|       noResults() { | ||||
|          if (!this.isWorksLoaded) { | ||||
|             return false; | ||||
|          } else { | ||||
|             return this.works.count === 0; | ||||
|          } | ||||
|       } | ||||
|    }, | ||||
|    methods: { | ||||
|       getUrl(e) { | ||||
|          switch (e.type) { | ||||
|             case 'accompanying_period_work': | ||||
|                return `/fr/person/accompanying-period/work/${e.id}/edit` | ||||
|             case 'accompanying_period': | ||||
|                return `/fr/parcours/${e.id}` | ||||
|             default: | ||||
|                throw 'entity type unknown'; | ||||
|          } | ||||
|       } | ||||
|    }, | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
|  | ||||
| </style> | ||||
| @@ -0,0 +1,18 @@ | ||||
| <template> | ||||
|    <span v-if="isCounterAvailable" | ||||
|          class="badge rounded-pill bg-danger counter"> | ||||
|       {{ count }} | ||||
|    </span> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|    name: "TabCounter", | ||||
|    props: ['count'], | ||||
|    computed: { | ||||
|       isCounterAvailable() { | ||||
|          return (typeof this.count !== 'undefined' && this.count > 0 ) | ||||
|       } | ||||
|    } | ||||
| } | ||||
| </script> | ||||
| @@ -0,0 +1,21 @@ | ||||
| <template> | ||||
|    <table class="table table-striped table-hover"> | ||||
|       <thead> | ||||
|          <tr> | ||||
|             <slot name="thead"></slot> | ||||
|          </tr> | ||||
|       </thead> | ||||
|       <tbody> | ||||
|          <slot name="tbody"></slot> | ||||
|       </tbody> | ||||
|    </table> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|    name: "TabTable", | ||||
|    props: [] | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped></style> | ||||
| @@ -0,0 +1,54 @@ | ||||
| const appMessages = { | ||||
|     fr: { | ||||
|         main_title: "Vue d'ensemble", | ||||
|         my_works: { | ||||
|             tab: "Mes actions", | ||||
|             description: "Liste des actions d'accompagnement dont je suis référent et qui arrivent à échéance.", | ||||
|         }, | ||||
|         my_evaluations: { | ||||
|             tab: "Mes évaluations", | ||||
|             description: "Liste des évaluations dont je suis référent et qui arrivent à échéance.", | ||||
|         }, | ||||
|         my_tasks: { | ||||
|             tab: "Mes tâches", | ||||
|             description_alert: "Liste des tâches auxquelles je suis assigné et dont la date de rappel est dépassée.", | ||||
|             description_warning: "Liste des tâches auxquelles je suis assigné et dont la date d'échéance est dépassée.", | ||||
|         }, | ||||
|         my_accompanying_courses: { | ||||
|             tab: "Mes parcours", | ||||
|             description: "Liste des parcours d'accompagnement que l'on vient de m'attribuer.", | ||||
|         }, | ||||
|         my_notifications: { | ||||
|             tab: "Mes notifications", | ||||
|             description: "Liste des notifications reçues et non lues.", | ||||
|         }, | ||||
|         Date: "Date", | ||||
|         From: "De", | ||||
|         Subject: "Objet", | ||||
|         Entity: "Associé à", | ||||
|         show_entity: "Voir {entity}", | ||||
|         the_activity: "l'échange", | ||||
|         the_course: "le parcours", | ||||
|         the_action: "l'action", | ||||
|         the_evaluation: "l'évaluation", | ||||
|         the_task: "la tâche", | ||||
|         StartDate: "Date d'ouverture", | ||||
|         SocialAction: "Action d'accompagnement", | ||||
|         no_data: "Aucun résultats", | ||||
|         no_dashboard: "Pas de tableaux de bord", | ||||
|         counter: { | ||||
|             unread_notifications: "notifications non lues", | ||||
|             assignated_courses: "parcours récents assignés", | ||||
|             assignated_actions: "actions assignées", | ||||
|             assignated_evaluations: "évaluations assignées", | ||||
|             alert_tasks: "tâches en rappel", | ||||
|             warning_tasks: "tâches à échéances", | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | ||||
| Object.assign(appMessages.fr); | ||||
|  | ||||
| export { | ||||
|     appMessages | ||||
| }; | ||||
| @@ -0,0 +1,200 @@ | ||||
| import 'es6-promise/auto'; | ||||
| import { createStore } from 'vuex'; | ||||
| import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; | ||||
| import MyCustoms from "../MyCustoms"; | ||||
| import MyWorks from "../MyWorks"; | ||||
| import MyEvaluations from "../MyEvaluations"; | ||||
| import MyTasks from "../MyTasks"; | ||||
| import MyAccompanyingCourses from "../MyAccompanyingCourses"; | ||||
| import MyNotifications from "../MyNotifications"; | ||||
|  | ||||
| const debug = process.env.NODE_ENV !== 'production'; | ||||
|  | ||||
| const isEmpty = (obj) => { | ||||
|     return obj | ||||
|         && Object.keys(obj).length <= 1 | ||||
|         && Object.getPrototypeOf(obj) === Object.prototype; | ||||
| }; | ||||
|  | ||||
| const store = createStore({ | ||||
|     strict: debug, | ||||
|     state: { | ||||
|         works: {}, | ||||
|         evaluations: {}, | ||||
|         tasks: { | ||||
|             warning: {}, | ||||
|             alert: {} | ||||
|         }, | ||||
|         accompanyingCourses: {}, | ||||
|         notifications: {}, | ||||
|         errorMsg: [], | ||||
|         loading: false | ||||
|     }, | ||||
|     getters: { | ||||
|         isWorksLoaded(state) { | ||||
|             return !isEmpty(state.works); | ||||
|         }, | ||||
|         isEvaluationsLoaded(state) { | ||||
|             return !isEmpty(state.evaluations); | ||||
|         }, | ||||
|         isTasksWarningLoaded(state) { | ||||
|             return !isEmpty(state.tasks.warning); | ||||
|         }, | ||||
|         isTasksAlertLoaded(state) { | ||||
|             return !isEmpty(state.tasks.alert); | ||||
|         }, | ||||
|         isAccompanyingCoursesLoaded(state) { | ||||
|             return !isEmpty(state.accompanyingCourses); | ||||
|         }, | ||||
|         isNotificationsLoaded(state) { | ||||
|             return !isEmpty(state.notifications); | ||||
|         }, | ||||
|         counter(state) { | ||||
|             return { | ||||
|                 works: state.works.count, | ||||
|                 evaluations: state.evaluations.count, | ||||
|                 tasksWarning: state.tasks.warning.count, | ||||
|                 tasksAlert: state.tasks.alert.count, | ||||
|                 accompanyingCourses: state.accompanyingCourses.count, | ||||
|                 notifications: state.notifications.count, | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     mutations: { | ||||
|         addWorks(state, works) { | ||||
|             console.log('addWorks', works); | ||||
|             state.works = works; | ||||
|         }, | ||||
|         addEvaluations(state, evaluations) { | ||||
|             console.log('addEvaluations', evaluations); | ||||
|             state.evaluations = evaluations; | ||||
|         }, | ||||
|         addTasksWarning(state, tasks) { | ||||
|             console.log('addTasksWarning', tasks); | ||||
|             state.tasks.warning = tasks; | ||||
|         }, | ||||
|         addTasksAlert(state, tasks) { | ||||
|             console.log('addTasksAlert', tasks); | ||||
|             state.tasks.alert = tasks; | ||||
|         }, | ||||
|         addCourses(state, courses) { | ||||
|             console.log('addCourses', courses); | ||||
|             state.accompanyingCourses = courses; | ||||
|         }, | ||||
|         addNotifications(state, notifications) { | ||||
|             console.log('addNotifications', notifications); | ||||
|             state.notifications = notifications; | ||||
|         }, | ||||
|         setLoading(state, bool) { | ||||
|             state.loading = bool; | ||||
|         }, | ||||
|         catchError(state, error) { | ||||
|             state.errorMsg.push(error); | ||||
|         } | ||||
|     }, | ||||
|     actions: { | ||||
|         getByTab({ commit, getters }, { tab, param }) { | ||||
|             switch (tab) { | ||||
|                 case 'MyCustoms': | ||||
|                     break; | ||||
|                 case 'MyWorks': | ||||
|                     if (!getters.isWorksLoaded) { | ||||
|                         commit('setLoading', true); | ||||
|                         const url = `/api/1.0/person/accompanying-period/work/my-near-end${'?'+ param}`; | ||||
|                         makeFetch('GET', url) | ||||
|                             .then((response) => { | ||||
|                                 commit('addWorks', response); | ||||
|                                 commit('setLoading', false); | ||||
|                             }) | ||||
|                             .catch((error) => { | ||||
|                                 commit('catchError', error); | ||||
|                                 throw error; | ||||
|                             }) | ||||
|                         ; | ||||
|                     } | ||||
|                     break; | ||||
|                 case 'MyEvaluations': | ||||
|                     if (!getters.isEvaluationsLoaded) { | ||||
|                         commit('setLoading', true); | ||||
|                         const url = `/api/1.0/person/accompanying-period/work/evaluation/my-near-end${'?'+ param}`; | ||||
|                         makeFetch('GET', url) | ||||
|                             .then((response) => { | ||||
|                                 commit('addEvaluations', response); | ||||
|                                 commit('setLoading', false); | ||||
|                             }) | ||||
|                             .catch((error) => { | ||||
|                                 commit('catchError', error); | ||||
|                                 throw error; | ||||
|                             }) | ||||
|                         ; | ||||
|                     } | ||||
|                     break; | ||||
|                 case 'MyTasks': | ||||
|                     if (!(getters.isTasksWarningLoaded && getters.isTasksAlertLoaded)) { | ||||
|                         commit('setLoading', true); | ||||
|                         const | ||||
|                             urlWarning = `/api/1.0/task/single-task/list/my?f[q]=&f[checkboxes][status][]=warning&f[checkboxes][states][]=new&f[checkboxes][states][]=in_progress${'&'+ param}`, | ||||
|                             urlAlert   = `/api/1.0/task/single-task/list/my?f[q]=&f[checkboxes][status][]=alert&f[checkboxes][states][]=new&f[checkboxes][states][]=in_progress${'&'+ param}` | ||||
|                         ; | ||||
|                         makeFetch('GET', urlWarning) | ||||
|                             .then((response) => { | ||||
|                                 commit('addTasksWarning', response); | ||||
|                                 commit('setLoading', false); | ||||
|                             }) | ||||
|                             .catch((error) => { | ||||
|                                 commit('catchError', error); | ||||
|                                 throw error; | ||||
|                             }) | ||||
|                         ; | ||||
|                         makeFetch('GET', urlAlert) | ||||
|                             .then((response) => { | ||||
|                                 commit('addTasksAlert', response); | ||||
|                                 commit('setLoading', false); | ||||
|                             }) | ||||
|                             .catch((error) => { | ||||
|                                 commit('catchError', error); | ||||
|                                 throw error; | ||||
|                             }) | ||||
|                         ; | ||||
|                     } | ||||
|                     break; | ||||
|                 case 'MyAccompanyingCourses': | ||||
|                     if (!getters.isAccompanyingCoursesLoaded) { | ||||
|                         commit('setLoading', true); | ||||
|                         const url = `/api/1.0/person/accompanying-course/list/by-recent-attributions${'?'+ param}`; | ||||
|                         makeFetch('GET', url) | ||||
|                             .then((response) => { | ||||
|                                 commit('addCourses', response); | ||||
|                                 commit('setLoading', false); | ||||
|                             }) | ||||
|                             .catch((error) => { | ||||
|                                 commit('catchError', error); | ||||
|                                 throw error; | ||||
|                             }) | ||||
|                         ; | ||||
|                     } | ||||
|                     break; | ||||
|                 case 'MyNotifications': | ||||
|                     if (!getters.isNotificationsLoaded) { | ||||
|                         commit('setLoading', true); | ||||
|                         const url = `/api/1.0/main/notification/my/unread${'?'+ param}`; | ||||
|                         makeFetch('GET', url) | ||||
|                             .then((response) => { | ||||
|                                 commit('addNotifications', response); | ||||
|                                 commit('setLoading', false); | ||||
|                             }) | ||||
|                             .catch((error) => { | ||||
|                                 commit('catchError', error); | ||||
|                                 throw error; | ||||
|                             }) | ||||
|                         ; | ||||
|                     } | ||||
|                     break; | ||||
|                 default: | ||||
|                     throw 'tab '+ tab; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| export { store }; | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
|     <ul class="list-suggest remove-items"> | ||||
|     <ul class="list-suggest remove-items" v-if="picked.length"> | ||||
|         <li v-for="p in picked" @click="removeEntity(p)" :key="p.type+p.id"> | ||||
|             <span class="chill_denomination">{{ p.text }}</span> | ||||
|         </li> | ||||
| @@ -79,11 +79,15 @@ export default { | ||||
|             ); | ||||
|             this.$refs.addPersons.resetSearch(); // to cast child method | ||||
|             modal.showModal = false; | ||||
|             console.log(this.picked) | ||||
|         }, | ||||
|         removeEntity(entity) { | ||||
|             console.log('remove entity', entity); | ||||
|             this.$emit('removeEntity', entity); | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         console.log(this.picked); | ||||
|     } | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -24,6 +24,11 @@ | ||||
|         {{ $t('user')}} | ||||
|     </span> | ||||
|  | ||||
|     <span v-if="entity.type === 'household'" class="badge rounded-pill bg-user"> | ||||
|         {{ $t('household')}} | ||||
|     </span> | ||||
|  | ||||
|  | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| @@ -40,7 +45,8 @@ export default { | ||||
|                     company: "Personne morale", | ||||
|                     contact: "Personne physique", | ||||
|                 }, | ||||
|                 user: 'TMS' | ||||
|                 user: 'TMS', | ||||
|                 household: 'Ménage', | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,22 +1,26 @@ | ||||
| <template> | ||||
|     <div class="confidential" v-on:click="toggleBlur"> | ||||
|         <div class="confidential-content blur"> | ||||
|     <div :class="classes"> | ||||
|         <div class="confidential-content" :class="{ 'blur': isBlurred }"> | ||||
|             <slot name="confidential-content"></slot> | ||||
|         </div> | ||||
|         <i class="fa fa-eye toggle" aria-hidden="true"></i> | ||||
|         <div> | ||||
|             <i class="fa fa-eye toggle" aria-hidden="true" @click="toggleBlur"></i> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|     name: "Confidential", | ||||
|     data() { | ||||
|         return { | ||||
|             isBlurred: true, | ||||
|         }; | ||||
|     }, | ||||
|     methods : { | ||||
|         toggleBlur: function(e){ | ||||
|             if(e.target.matches('.toggle')){ | ||||
|                 e.target.previousElementSibling.classList.toggle("blur"); | ||||
|                 e.target.classList.toggle("fa-eye"); | ||||
|                 e.target.classList.toggle("fa-eye-slash"); | ||||
|             } | ||||
|         toggleBlur() { | ||||
|             console.log('toggle blur'); | ||||
|             this.isBlurred = !this.isBlurred; | ||||
|         }, | ||||
|     } | ||||
| } | ||||
| @@ -39,4 +43,4 @@ export default { | ||||
|         -moz-filter: blur(5px); | ||||
|         filter: blur(5px); | ||||
|     } | ||||
| </style> | ||||
| </style> | ||||
|   | ||||
| @@ -1,26 +1,58 @@ | ||||
| <template> | ||||
|  | ||||
|    <component :is="component" class="chill-entity entity-address my-3"> | ||||
|  | ||||
|       <component :is="component" class="address" :class="multiline"> | ||||
|          <div v-if="isMultiline === true"> | ||||
|             <p v-for="(l, i) in address.lines" :key="`line-${i}`"> | ||||
|                {{ l }} | ||||
|             </p> | ||||
|  | ||||
|          <div v-if="isConfidential"> | ||||
|             <confidential> | ||||
|                <template v-slot:confidential-content> | ||||
|                   <div v-if="isMultiline === true"> | ||||
|                      <p v-for="(l, i) in address.lines" :key="`line-${i}`"> | ||||
|                         {{ l }} | ||||
|                      </p> | ||||
|                   </div> | ||||
|                   <div v-else> | ||||
|                      <p v-if="address.text" | ||||
|                         class="street"> | ||||
|                         {{ address.text }} | ||||
|                      </p> | ||||
|                      <p v-if="address.postcode" | ||||
|                         class="postcode"> | ||||
|                         {{ address.postcode.code }} {{ address.postcode.name }} | ||||
|                      </p> | ||||
|                      <p v-if="address.country" | ||||
|                         class="country"> | ||||
|                         {{ address.country.name.fr }} | ||||
|                      </p> | ||||
|                   </div> | ||||
|                </template> | ||||
|  | ||||
|             </confidential> | ||||
|          </div> | ||||
|          <div v-else> | ||||
|             <p v-if="address.text" | ||||
|                class="street"> | ||||
|                {{ address.text }} | ||||
|             </p> | ||||
|             <p v-if="address.postcode" | ||||
|                class="postcode"> | ||||
|                {{ address.postcode.code }} {{ address.postcode.name }} | ||||
|             </p> | ||||
|             <p v-if="address.country" | ||||
|                class="country"> | ||||
|                {{ address.country.name.fr }} | ||||
|             </p> | ||||
|  | ||||
|          <div v-if="!isConfidential"> | ||||
|             <div v-if="isMultiline === true"> | ||||
|                <p v-for="(l, i) in address.lines" :key="`line-${i}`"> | ||||
|                   {{ l }} | ||||
|                </p> | ||||
|             </div> | ||||
|             <div v-else> | ||||
|                <p v-if="address.text" | ||||
|                   class="street"> | ||||
|                   {{ address.text }} | ||||
|                </p> | ||||
|                <p v-if="address.postcode" | ||||
|                   class="postcode"> | ||||
|                   {{ address.postcode.code }} {{ address.postcode.name }} | ||||
|                </p> | ||||
|                <p v-if="address.country" | ||||
|                   class="country"> | ||||
|                   {{ address.country.name.fr }} | ||||
|                </p> | ||||
|             </div> | ||||
|          </div> | ||||
|  | ||||
|       </component> | ||||
|  | ||||
|       <!-- <div v-if="isMultiline === true" class="address-more"> | ||||
| @@ -78,8 +110,14 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|  | ||||
| import Confidential from 'ChillMainAssets/vuejs/_components/Confidential.vue'; | ||||
|  | ||||
| export default { | ||||
|    name: 'AddressRenderBox', | ||||
|    components: { | ||||
|       Confidential | ||||
|    }, | ||||
|    props: { | ||||
|       address: { | ||||
|          type: Object | ||||
| @@ -100,6 +138,9 @@ export default { | ||||
|       multiline() { | ||||
|          //console.log(this.isMultiline, typeof this.isMultiline); | ||||
|          return this.isMultiline === true ? "multiline" : ""; | ||||
|       }, | ||||
|       isConfidential() { | ||||
|          return this.address.confidential; | ||||
|       } | ||||
|    } | ||||
| }; | ||||
|   | ||||
| @@ -1,26 +1,86 @@ | ||||
| <template> | ||||
| <div class="list-group my-2 workflow workflow-box"> | ||||
|    <div class="list-group-item"> | ||||
|       <h4>Workflow associés</h4> | ||||
|    <div class="flex-table workflow" id="workflow-list"> | ||||
|       <div v-for="(w, i) in workflows" :key="`workflow-${i}`" | ||||
|          class="item-bloc"> | ||||
|  | ||||
|          <div> | ||||
|             <div class="item-row col"> | ||||
|                <h2>Workflow</h2> | ||||
|                <div class="flex-grow-1 ms-3 h3"> | ||||
|                   <div class="visually-hidden"> | ||||
|                      {{ w.relatedEntityClass }} | ||||
|                      {{ w.relatedEntityId }} | ||||
|                   </div> | ||||
|                </div> | ||||
|             </div> | ||||
|  | ||||
|             <div class="breadcrumb"> | ||||
|                <template v-for="(step, j) in w.steps" :key="`step-${j}`"> | ||||
|                   <span class="mx-2" | ||||
|                      tabindex="0" | ||||
|                      data-bs-trigger="focus hover" | ||||
|                      data-bs-toggle="popover" | ||||
|                      data-bs-placement="bottom" | ||||
|                      data-bs-custom-class="workflow-transition" | ||||
|                      :title="getPopTitle(step)" | ||||
|                      :data-bs-content="getPopContent(step)"> | ||||
|  | ||||
|                      <i v-if="step.currentStep.name === 'initial'" | ||||
|                         class="fa fa-circle me-1 text-chill-yellow"> | ||||
|                      </i> | ||||
|                      <i v-if="step.isFreezed" | ||||
|                         class="fa fa-snowflake-o fa-sm me-1"> | ||||
|                      </i> | ||||
|                      {{ step.currentStep.text }} | ||||
|                   </span> | ||||
|                   <span v-if="j !== Object.keys(w.steps).length - 1"> | ||||
|                      → | ||||
|                   </span> | ||||
|                </template> | ||||
|             </div> | ||||
|          </div> | ||||
|  | ||||
|          <div class="item-row"> | ||||
|             <div class="item-col flex-grow-1"> | ||||
|                <p v-if="isUserSubscribedToStep(w)"> | ||||
|                   <i class="fa fa-check fa-fw"></i> | ||||
|                   {{ $t('you_subscribed_to_all_steps') }} | ||||
|                </p> | ||||
|                <p v-if="isUserSubscribedToFinal(w)"> | ||||
|                   <i class="fa fa-check fa-fw"></i> | ||||
|                   {{ $t('you_subscribed_to_final_step') }} | ||||
|                </p> | ||||
|             </div> | ||||
|             <div class="item-col"> | ||||
|                <ul class="record_actions"> | ||||
|                   <li> | ||||
|                      <a :href="goToUrl(w)" class="btn btn-sm btn-show" :title="$t('action.show')"></a> | ||||
|                   </li> | ||||
|                </ul> | ||||
|             </div> | ||||
|          </div> | ||||
|  | ||||
|       </div> | ||||
|    </div> | ||||
|    <div class="list-group-item" v-for="w in workflows"> | ||||
|       {{ w.id }} | ||||
|       <ul class="record_actions"> | ||||
|          <li> | ||||
|             <a class="btn btn-sm btn-outline-primary" | ||||
|                title="voir" | ||||
|                :href="goToUrl(w)"> | ||||
|                <i class="fa fa-eye fa-fw"></i> | ||||
|             </a> | ||||
|          </li> | ||||
|       </ul> | ||||
|    </div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Popover from 'bootstrap/js/src/popover'; | ||||
|  | ||||
| const i18n = { | ||||
|    messages: { | ||||
|       fr: { | ||||
|          you_subscribed_to_all_steps: "Vous recevrez une notification à chaque étape", | ||||
|          you_subscribed_to_final_step: "Vous recevrez une notification à l'étape finale", | ||||
|          by: "Par", | ||||
|          at: "Le" | ||||
|       } | ||||
|    } | ||||
| } | ||||
|  | ||||
| export default { | ||||
|    name: "ListWorkflow", | ||||
|    i18n: i18n, | ||||
|    props: { | ||||
|       workflows: { | ||||
|          type: Array, | ||||
| @@ -30,7 +90,42 @@ export default { | ||||
|    methods: { | ||||
|       goToUrl(w) { | ||||
|          return `/fr/main/workflow/${w.id}/show`; | ||||
|       } | ||||
|       }, | ||||
|       getPopTitle(step) { | ||||
|          if (step.transitionPrevious != null) { | ||||
|             let freezed = step.isFreezed ? `<i class="fa fa-snowflake-o fa-sm me-1"></i>` : ``; | ||||
|             return `${freezed}${step.currentStep.text}`; | ||||
|          } | ||||
|       }, | ||||
|       getPopContent(step) { | ||||
|          if (step.transitionPrevious != null) { | ||||
|             return `<ul class="small_in_title"> | ||||
|               <li><span class="item-key">${i18n.messages.fr.by} : </span><b>${step.transitionPreviousBy.text}</b></li> | ||||
|               <li><span class="item-key">${i18n.messages.fr.at} : </span><b>${this.formatDate(step.transitionPreviousAt.datetime)}</b></li> | ||||
|               </ul>` | ||||
|             ; | ||||
|          } | ||||
|       }, | ||||
|       formatDate(datetime) { | ||||
|          return datetime.split('T')[0] +' '+ datetime.split('T')[1].substring(0,5) | ||||
|       }, | ||||
|       isUserSubscribedToStep(w) { | ||||
|          // todo | ||||
|          return false; | ||||
|       }, | ||||
|       isUserSubscribedToFinal(w) { | ||||
|          // todo | ||||
|          return false; | ||||
|       }, | ||||
|    }, | ||||
|    mounted() { | ||||
|       const triggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); | ||||
|       const popoverList = triggerList.map(function (el) { | ||||
|          //console.log('popover', el) | ||||
|          return new Popover(el, { | ||||
|             html: true, | ||||
|          }); | ||||
|       }); | ||||
|    } | ||||
| } | ||||
| </script> | ||||
| </script> | ||||
|   | ||||
| @@ -0,0 +1,111 @@ | ||||
| <template> | ||||
|     | ||||
|    <button v-if="hasWorkflow" | ||||
|       class="btn btn-primary" | ||||
|       @click="openModal"> | ||||
|       <b>{{ countWorkflows }}</b> | ||||
|       <template v-if="countWorkflows > 1">{{ $t('workflows') }}</template> | ||||
|       <template v-else>{{ $t('workflow') }}</template> | ||||
|    </button> | ||||
|     | ||||
|    <pick-workflow v-else-if="allowCreate" | ||||
|       :relatedEntityClass="this.relatedEntityClass" | ||||
|       :relatedEntityId="this.relatedEntityId" | ||||
|       :workflowsAvailables="workflowsAvailables" | ||||
|    ></pick-workflow> | ||||
|     | ||||
|    <teleport to="body"> | ||||
|       <modal v-if="modal.showModal" | ||||
|          :modalDialogClass="modal.modalDialogClass" | ||||
|          @close="modal.showModal = false"> | ||||
|           | ||||
|          <template v-slot:header> | ||||
|             <h2 class="modal-title">{{ $t('workflow_list') }}</h2> | ||||
|          </template> | ||||
|           | ||||
|          <template v-slot:body> | ||||
|             <list-workflow-vue | ||||
|                :workflows="workflows" | ||||
|             ></list-workflow-vue> | ||||
|          </template> | ||||
|           | ||||
|          <template v-slot:footer> | ||||
|             <pick-workflow v-if="allowCreate" | ||||
|                :relatedEntityClass="this.relatedEntityClass" | ||||
|                :relatedEntityId="this.relatedEntityId" | ||||
|                :workflowsAvailables="workflowsAvailables" | ||||
|             ></pick-workflow> | ||||
|          </template> | ||||
|           | ||||
|       </modal> | ||||
|    </teleport> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Modal from 'ChillMainAssets/vuejs/_components/Modal'; | ||||
| import PickWorkflow from 'ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue'; | ||||
| import ListWorkflowVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflow.vue'; | ||||
|  | ||||
| export default { | ||||
|    name: "ListWorkflowModal", | ||||
|    components: { | ||||
|       Modal, | ||||
|       PickWorkflow, | ||||
|       ListWorkflowVue | ||||
|    }, | ||||
|    props: { | ||||
|       workflows: { | ||||
|          type: Array, | ||||
|          required: true, | ||||
|       }, | ||||
|       allowCreate: { | ||||
|          type: Boolean, | ||||
|          required: true, | ||||
|       }, | ||||
|       relatedEntityClass: { | ||||
|          type: String, | ||||
|          required: true, | ||||
|       }, | ||||
|       relatedEntityId: { | ||||
|          type: Number, | ||||
|          required: false, | ||||
|       }, | ||||
|       workflowsAvailables: { | ||||
|          type: Array, | ||||
|          required: true, | ||||
|       } | ||||
|    }, | ||||
|    data() { | ||||
|       return { | ||||
|          modal: { | ||||
|             showModal: false, | ||||
|             modalDialogClass: "modal-dialog-scrollable modal-xl" | ||||
|          }, | ||||
|       } | ||||
|    }, | ||||
|    computed: { | ||||
|       countWorkflows() { | ||||
|          return this.workflows.length; | ||||
|       }, | ||||
|       hasWorkflow() { | ||||
|          return this.countWorkflows > 0; | ||||
|       } | ||||
|    }, | ||||
|    methods: { | ||||
|       openModal() { | ||||
|          this.modal.showModal = true; | ||||
|       }, | ||||
|    }, | ||||
|    i18n: { | ||||
|       messages: { | ||||
|          fr: { | ||||
|             workflow_list: "Liste des workflows associés", | ||||
|             workflow: " workflow associé", | ||||
|             workflows: " workflows associés", | ||||
|          } | ||||
|       } | ||||
|    } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped></style> | ||||
| @@ -61,14 +61,15 @@ const messages = { | ||||
|             woman: "Née le" | ||||
|          }, | ||||
|          deathdate: "Date de décès", | ||||
|          years_old: "ans", | ||||
|          household_without_address: "Le ménage de l'usager est sans adresse", | ||||
|          no_data: "Aucune information renseignée", | ||||
|          type: { | ||||
|             thirdparty: "Tiers", | ||||
|             person: "Usager" | ||||
|          }, | ||||
|          holder: "Titulaire" | ||||
|          holder: "Titulaire", | ||||
|          years_old: "an | {n} an | {n} ans", | ||||
|  | ||||
|       } | ||||
|    } | ||||
| }; | ||||
|   | ||||
| @@ -59,7 +59,7 @@ | ||||
|     must be shown in such list | ||||
| #} | ||||
| {%- if render == 'list' -%} | ||||
|     <li class="chill-entity entity-address"> | ||||
|     <li class="chill-entity entity-address {% if address.confidential %}confidential{% endif %}"> | ||||
|         {% if options['with_picto'] %} | ||||
|             <i class="fa fa-li fa-map-marker"></i> | ||||
|         {% endif %} | ||||
| @@ -68,7 +68,7 @@ | ||||
| {%- endif -%} | ||||
|  | ||||
| {%- if render == 'inline' -%} | ||||
|     <span class="chill-entity entity-address"> | ||||
|     <span class="chill-entity entity-address {% if address.confidential %}confidential{% endif %}"> | ||||
|         {% if options['with_picto'] %} | ||||
|             <i class="fa fa-fw fa-map-marker"></i> | ||||
|         {% endif %} | ||||
| @@ -77,7 +77,7 @@ | ||||
| {%- endif -%} | ||||
|  | ||||
| {%- if render == 'bloc' -%} | ||||
|     <div class="chill-entity entity-address"> | ||||
|     <div class="chill-entity entity-address {% if address.confidential %}confidential{% endif %}"> | ||||
|         {% if options['has_no_address'] == true and address.isNoAddress == true %} | ||||
|             {% if address.postCode is not empty %} | ||||
|             <div class="address{% if options['multiline'] %} multiline{% endif %}{% if options['with_delimiter'] %} delimiter{% endif %}"> | ||||
|   | ||||
| @@ -216,7 +216,7 @@ | ||||
|     {% endif %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block pick_user_dynamic_widget %} | ||||
| {% block pick_entity_dynamic_widget %} | ||||
|     <input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %} data-input-uniqid="{{ form.vars['uniqid'] }}"/> | ||||
|     <div data-module="pick-dynamic" data-types="{{ form.vars['types']|json_encode }}" data-multiple="{{ form.vars['multiple'] }}" data-uniqid="{{ form.vars['uniqid'] }}"></div> | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -0,0 +1,3 @@ | ||||
| <div class="sticky-buttons"> | ||||
|     {# Override this file to add fast actions buttons #} | ||||
| </div> | ||||
| @@ -0,0 +1,15 @@ | ||||
| <div class="col-10 mt-5"> | ||||
|      | ||||
|     {# vue component #} | ||||
|     <div id="homepage_widget"></div> | ||||
|      | ||||
|     {% include '@ChillMain/Homepage/fast_actions.html.twig' %} | ||||
| </div> | ||||
|  | ||||
| {% block css %} | ||||
|     {{ encore_entry_link_tags('page_homepage_widget') }} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block js %} | ||||
|     {{ encore_entry_script_tags('page_homepage_widget') }} | ||||
| {% endblock %} | ||||
| @@ -0,0 +1,77 @@ | ||||
| {% import '@ChillPerson/AccompanyingCourse/Comment/macro_showItem.html.twig' as m %} | ||||
|  | ||||
| {% macro recordAction(comment) %} | ||||
|     {% if is_granted('CHILL_MAIN_NOTIFICATION_COMMENT_EDIT', comment) %} | ||||
|         <li> | ||||
|             <a href="{{ chill_path_forward_return_path('chill_main_notification_show', { | ||||
|                 '_fragment': 'comment-' ~ comment.id, | ||||
|                 'edit': comment.id, | ||||
|                 'id': comment.notification.id | ||||
|             }) }}" class="btn btn-edit" title="{{ 'Edit'|trans }}" | ||||
|             ></a> | ||||
|         </li> | ||||
|     {% endif %} | ||||
| {% endmacro %} | ||||
|  | ||||
| <div class="notification-comment-list my-5"> | ||||
|     <h2 class="chill-blue">{{ 'notification.comments_list'|trans }}</h2> | ||||
|      | ||||
|     {% if notification.comments|length > 0 %} | ||||
|         <div class="flex-table"> | ||||
|             {% for comment in notification.comments %} | ||||
|                  | ||||
|                 {% if editedCommentForm is null or editedCommentId != comment.id %} | ||||
|                     {{ m.show_comment(comment, { | ||||
|                         'recordAction': _self.recordAction(comment) | ||||
|                     }) }} | ||||
|                 {% else %} | ||||
|                     <div class="item-bloc"> | ||||
|                         <div class="item-row row"> | ||||
|                             <a id="comment-{{ comment.id }}"></a> | ||||
|                              | ||||
|                             {{ form_start(editedCommentForm) }} | ||||
|                             {{ form_errors(editedCommentForm) }} | ||||
|                             {{ form_widget(editedCommentForm.content) }} | ||||
|                             <input type="hidden" name="form" value="edit" /> | ||||
|                             <ul class="record_actions"> | ||||
|                                 <li class="cancel"> | ||||
|                                     <a href="{{ chill_path_forward_return_path('chill_main_notification_show', { | ||||
|                                         '_fragment': 'comment-' ~ comment.id, | ||||
|                                         'id': notification.id }) }}" class="btn btn-cancel"> | ||||
|                                         {{ 'cancel'|trans }} | ||||
|                                     </a> | ||||
|                                 </li> | ||||
|                                 <li> | ||||
|                                     <button class="btn btn-save" type="submit">{{ 'Save'|trans }}</button> | ||||
|                                 </li> | ||||
|                             </ul> | ||||
|                             {{ form_end(editedCommentForm) }} | ||||
|                          | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 {% endif %} | ||||
|              | ||||
|             {% endfor %} | ||||
|         </div> | ||||
|     {% else %} | ||||
|         <span class="chill-no-data-statement">{{ 'No comments'|trans }}</span> | ||||
|     {% endif %} | ||||
|      | ||||
|     {% if appendCommentForm is not null %} | ||||
|         <div class="new-comment my-5"> | ||||
|             <h2 class="chill-blue mb-4">{{ 'Write a new comment'|trans }}</h2> | ||||
|              | ||||
|             {{ form_start(appendCommentForm) }} | ||||
|             {{ form_errors(appendCommentForm) }} | ||||
|             {{ form_widget(appendCommentForm.content) }} | ||||
|             <input type="hidden" name="form" value="append" /> | ||||
|             <ul class="record_actions"> | ||||
|                 <li> | ||||
|                     <button class="btn btn-create" type="submit">{{ 'notification.append_comment'|trans }}</button> | ||||
|                 </li> | ||||
|             </ul> | ||||
|             {{ form_end(appendCommentForm) }} | ||||
|          | ||||
|         </div> | ||||
|     {% endif %} | ||||
| </div> | ||||
| @@ -52,9 +52,11 @@ | ||||
|  | ||||
| {% macro content(c) %} | ||||
|     <div class="item-row separator"> | ||||
|         <div class="mx-3 flex-grow-1"> | ||||
|             {% include c.data.template with c.data.template_data %} | ||||
|         </div> | ||||
|         {% if c.data is defined %} | ||||
|             <div class="mx-3 flex-grow-1"> | ||||
|                 {% include c.data.template with c.data.template_data %} | ||||
|             </div> | ||||
|         {% endif %} | ||||
|     </div> | ||||
|     <div class="item-row"> | ||||
|         <div class="notification-content"> | ||||
| @@ -68,34 +70,44 @@ | ||||
|     </div> | ||||
|     {% if c.action_button is not defined or c.action_button != false %} | ||||
|         <div class="item-row separator"> | ||||
|             <ul class="record_actions"> | ||||
|                 <li> | ||||
|                     {# Vue component #} | ||||
|                     <span class="notification_toggle_read_status" | ||||
|                         data-notification-id="{{ c.notification.id }}" | ||||
|                         data-notification-current-is-read="{{ c.notification.isReadBy(app.user) }}" | ||||
|                         data-container="notification-status" | ||||
|                     ></span> | ||||
|                 </li> | ||||
|                 {% if is_granted('CHILL_MAIN_NOTIFICATION_UPDATE', c.notification) %} | ||||
|             <div class="item-col item-meta"> | ||||
|                  | ||||
|                 {# TODO twig extension to count comments #} | ||||
|                 <div class="comment-counter visually-hidden"> | ||||
|                     <span>x commentaires</span> | ||||
|                 </div> | ||||
|                  | ||||
|             </div> | ||||
|             <div class="item-col"> | ||||
|                 <ul class="record_actions"> | ||||
|                     <li> | ||||
|                         <a href="{{ chill_path_add_return_path('chill_main_notification_edit', {'id': c.notification.id}) }}" | ||||
|                            class="btn btn-edit" title="{{ 'Edit'|trans }}"></a> | ||||
|                         {# Vue component #} | ||||
|                         <span class="notification_toggle_read_status" | ||||
|                             data-notification-id="{{ c.notification.id }}" | ||||
|                             data-notification-current-is-read="{{ c.notification.isReadBy(app.user) }}" | ||||
|                             data-container="notification-status" | ||||
|                         ></span> | ||||
|                     </li> | ||||
|                 {% endif %} | ||||
|                 {% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', c.notification) %} | ||||
|                     <li> | ||||
|                         <a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}" | ||||
|                            class="btn {% if not c.notification.isSystem %}btn-show change-icon{% else %}btn-misc{% endif %}" title="{{ 'notification.see_comments_thread'|trans }}"> | ||||
|                             {% if not c.notification.isSystem() %} | ||||
|                                 <i class="fa fa-comment"></i> | ||||
|                             {% else %} | ||||
|                                 {{ 'Read more'|trans }} | ||||
|                             {% endif %} | ||||
|                         </a> | ||||
|                     </li> | ||||
|                 {% endif %} | ||||
|             </ul> | ||||
|                     {% if is_granted('CHILL_MAIN_NOTIFICATION_UPDATE', c.notification) %} | ||||
|                         <li> | ||||
|                             <a href="{{ chill_path_add_return_path('chill_main_notification_edit', {'id': c.notification.id}) }}" | ||||
|                                class="btn btn-edit" title="{{ 'Edit'|trans }}"></a> | ||||
|                         </li> | ||||
|                     {% endif %} | ||||
|                     {% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', c.notification) %} | ||||
|                         <li> | ||||
|                             <a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}" | ||||
|                                class="btn {% if not c.notification.isSystem %}btn-show change-icon{% else %}btn-misc{% endif %}" title="{{ 'notification.see_comments_thread'|trans }}"> | ||||
|                                 {% if not c.notification.isSystem() %} | ||||
|                                     <i class="fa fa-comment"></i> | ||||
|                                 {% else %} | ||||
|                                     {{ 'Read more'|trans }} | ||||
|                                 {% endif %} | ||||
|                             </a> | ||||
|                         </li> | ||||
|                     {% endif %} | ||||
|                 </ul> | ||||
|             </div> | ||||
|         </div> | ||||
|     {% endif %} | ||||
| {% endmacro %} | ||||
| @@ -107,15 +119,19 @@ | ||||
|             <button type="button" class="accordion-button collapsed" | ||||
|                 data-bs-toggle="collapse" data-bs-target="#flush-collapse-{{ notification.id }}" | ||||
|                 aria-expanded="false" aria-controls="flush-collapse-{{ notification.id }}"> | ||||
|                  | ||||
|                 {{ _self.title(_context) }} | ||||
|             </button> | ||||
|             {{ _self.header(_context) }} | ||||
|          | ||||
|         </div> | ||||
|         <div id="flush-collapse-{{ notification.id }}" | ||||
|             class="accordion-collapse collapse" | ||||
|             aria-labelledby="flush-heading-{{ notification.id }}" | ||||
|             data-bs-parent="#notification-fold"> | ||||
|              | ||||
|             {{ _self.content(_context) }} | ||||
|          | ||||
|         </div> | ||||
|     {% else %} | ||||
|         {{ _self.title(_context) }} | ||||
|   | ||||
| @@ -20,6 +20,8 @@ | ||||
|  | ||||
|     {{ form_row(form.title, { 'label': 'notification.subject'|trans }) }} | ||||
|     {{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }} | ||||
|      | ||||
|     {% include handler.template(notification) with handler.templateData(notification) %} | ||||
|  | ||||
|     <div class="mb-3 row"> | ||||
|         <label class="col-form-label col-sm-4" for="notification_message">{{ form_label(form.message) }}</label> | ||||
| @@ -28,8 +30,6 @@ | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     {% include handler.template(notification) with handler.templateData(notification) %} | ||||
|  | ||||
|     {{ form_end(form) }} | ||||
|  | ||||
|     <ul class="record_actions sticky-form-buttons"> | ||||
|   | ||||
| @@ -1,46 +1,12 @@ | ||||
| <div class="list-group my-2 notification notification-box"> | ||||
|     <div class="list-group-item"> | ||||
|         <h4>{{ 'notification.Sent'|trans }}</h4> | ||||
|     </div> | ||||
|     {# TODO pagination or limit #} | ||||
| <h1 class="mt-5"><a id="notification-list"></a>{{ 'notification.Notifications'|trans }}</h1> | ||||
|  | ||||
| <div class="flex-table accordion accordion-flush" id="notification-fold"> | ||||
|     {% for notification in notifications %} | ||||
|         <div class="list-group-item notification-status {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}"> | ||||
|  | ||||
|             {% if not notification.isSystem %} | ||||
|                 {% if notification.sender == app.user %} | ||||
|  | ||||
|                     <h6 class="notification-title"> | ||||
|                         <abbr title="{{ 'Le ' ~ notification.date|format_date('long') ~ '\n' ~ notification.title }}"> | ||||
|                             {{ notification.date|format_datetime('short','short') }} | ||||
|                         </abbr> | ||||
|                         {# Vue component #} | ||||
|                         <span class="notification_toggle_read_status" | ||||
|                             data-notification-id="{{ notification.id }}" | ||||
|                             data-notification-current-is-read="{{ notification.isReadBy(app.user) }}" | ||||
|                             data-container="notification-status" | ||||
|                             data-show-button-url="{{ chill_path_add_return_path('chill_main_notification_show', {'id': notification.id}, false) }}" | ||||
|                             data-button-class="btn-outline-primary" | ||||
|                             data-button-text="false" | ||||
|                         ></span> | ||||
|                     </h6> | ||||
|  | ||||
|                     {% if notification.addressees|length > 0 %} | ||||
|                         <abbr title="{{ 'notification.sent_to'|trans }}">{{ 'notification.to'|trans }}:</abbr> | ||||
|                     {% endif %} | ||||
|  | ||||
|                     {% for a in notification.addressees %} | ||||
|                         <span class="badge-user"> | ||||
|                             {{ a|chill_entity_render_string }} | ||||
|                         </span> | ||||
|                     {% endfor %} | ||||
|  | ||||
|                 {% else %} | ||||
|                     <div>{{ 'notification.you were notified by %sender%'|trans({'%sender%': notification.sender|chill_entity_render_string }) }}</div> | ||||
|                 {% endif %} | ||||
|             {% else %} | ||||
|                 <div>{{ 'notification.you were notified by system'|trans }}</div> | ||||
|             {% endif %} | ||||
|  | ||||
|         </div> | ||||
|         {% include 'ChillMainBundle:Notification:_list_item.html.twig' with { | ||||
|             'full_content': true, | ||||
|             'fold_item': true, | ||||
|             'action_button': true, | ||||
|         } %}{# | ||||
|     #} | ||||
|     {% endfor %} | ||||
| </div> | ||||
|   | ||||
| @@ -14,21 +14,6 @@ | ||||
|     {{ encore_entry_link_tags('mod_notification_toggle_read_status') }} | ||||
| {% endblock %} | ||||
|  | ||||
| {% import '@ChillPerson/AccompanyingCourse/Comment/macro_showItem.html.twig' as m %} | ||||
|  | ||||
| {% macro recordAction(comment) %} | ||||
|     {% if is_granted('CHILL_MAIN_NOTIFICATION_COMMENT_EDIT', comment) %} | ||||
|     <li> | ||||
|         <a href="{{ chill_path_forward_return_path('chill_main_notification_show', { | ||||
|             '_fragment': 'comment-' ~ comment.id, | ||||
|             'edit': comment.id, | ||||
|             'id': comment.notification.id | ||||
|         }) }}" class="btn btn-edit" title="{{ 'Edit'|trans }}" | ||||
|         ></a> | ||||
|     </li> | ||||
|     {% endif %} | ||||
| {% endmacro %} | ||||
|  | ||||
| {% block content %} | ||||
| <div class="col-10 notification notification-show"> | ||||
|  | ||||
| @@ -41,70 +26,12 @@ | ||||
|                  'template_data': handler.getTemplateData(notification) | ||||
|             }, | ||||
|             'action_button': false, | ||||
|             'full_content': true | ||||
|             'full_content': true, | ||||
|             'fold_item': false | ||||
|         } %} | ||||
|     </div> | ||||
|  | ||||
|     <div class="notification-comment-list my-5"> | ||||
|         <h2 class="chill-blue">{{ 'notification.comments_list'|trans }}</h2> | ||||
|  | ||||
|         {% if notification.comments|length > 0 %} | ||||
|             <div class="flex-table"> | ||||
|                 {% for comment in notification.comments %} | ||||
|  | ||||
|                     {% if editedCommentForm is null or editedCommentId != comment.id %} | ||||
|                         {{ m.show_comment(comment, { | ||||
|                             'recordAction': _self.recordAction(comment) | ||||
|                         }) }} | ||||
|                     {% else %} | ||||
|                         <div class="item-bloc"> | ||||
|                             <div class="item-row row"> | ||||
|                                 <a id="comment-{{ comment.id }}"></a> | ||||
|  | ||||
|                                 {{ form_start(editedCommentForm) }} | ||||
|                                 {{ form_errors(editedCommentForm) }} | ||||
|                                 {{ form_widget(editedCommentForm.content) }} | ||||
|                                 <input type="hidden" name="form" value="edit" /> | ||||
|                                 <ul class="record_actions"> | ||||
|                                     <li class="cancel"> | ||||
|                                         <a href="{{ chill_path_forward_return_path('chill_main_notification_show', { | ||||
|                                             '_fragment': 'comment-' ~ comment.id, | ||||
|                                             'id': notification.id }) }}" class="btn btn-cancel"> | ||||
|                                             {{ 'cancel'|trans }} | ||||
|                                         </a> | ||||
|                                     </li> | ||||
|                                     <li> | ||||
|                                         <button class="btn btn-save" type="submit">{{ 'Save'|trans }}</button> | ||||
|                                     </li> | ||||
|                                 </ul> | ||||
|                                 {{ form_end(editedCommentForm) }} | ||||
|  | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     {% endif %} | ||||
|  | ||||
|                 {% endfor %} | ||||
|             </div> | ||||
|         {% endif %} | ||||
|  | ||||
|         {% if appendCommentForm is not null %} | ||||
|         <div class="new-comment my-5"> | ||||
|             <h2 class="chill-blue mb-4">{{ 'Write a new comment'|trans }}</h2> | ||||
|  | ||||
|             {{ form_start(appendCommentForm) }} | ||||
|             {{ form_errors(appendCommentForm) }} | ||||
|             {{ form_widget(appendCommentForm.content) }} | ||||
|             <input type="hidden" name="form" value="append" /> | ||||
|             <ul class="record_actions"> | ||||
|                 <li> | ||||
|                     <button class="btn btn-create" type="submit">{{ 'notification.append_comment'|trans }}</button> | ||||
|                 </li> | ||||
|             </ul> | ||||
|             {{ form_end(appendCommentForm) }} | ||||
|  | ||||
|         </div> | ||||
|         {% endif %} | ||||
|     </div> | ||||
|     {% include 'ChillMainBundle:Notification:_item_comments.html.twig' %} | ||||
|  | ||||
|     <ul class="record_actions sticky-form-buttons"> | ||||
|         <li class="cancel"> | ||||
|   | ||||
| @@ -42,6 +42,3 @@ | ||||
|     {% endif %} | ||||
|  | ||||
| ></span> | ||||
|  | ||||
| {{ encore_entry_script_tags('vue_onthefly') }} | ||||
| {{ encore_entry_link_tags('vue_onthefly') }} | ||||
|   | ||||
| @@ -5,14 +5,10 @@ | ||||
|  | ||||
|     {{ form_row(transition_form.transition) }} | ||||
|  | ||||
|     <div id="finalizeAfter"> | ||||
|         {{ form_row(transition_form.finalizeAfter) }} | ||||
|     </div> | ||||
|      | ||||
|     {% if transition_form.freezeAfter is defined %} | ||||
|         {{ form_row(transition_form.freezeAfter) }} | ||||
|     {% endif %} | ||||
|      | ||||
|  | ||||
|     <div id="futureDestUsers"> | ||||
|         {{ form_row(transition_form.future_dest_users) }} | ||||
|     </div> | ||||
| @@ -31,7 +27,7 @@ | ||||
| {% else %} | ||||
|     <div class="alert alert-chill-yellow"> | ||||
|  | ||||
|         {% if entity_workflow.currentStep.isFinalizeAfter %} | ||||
|         {% if entity_workflow.currentStep.isFinal %} | ||||
|             <p>{{ 'workflow.This workflow is finalized'|trans }}</p> | ||||
|         {% else %} | ||||
|             <p>{{ 'workflow.You are not allowed to apply a transition on this workflow'|trans }}</p> | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| {% set acl = "0" %} | ||||
| {% if is_granted('CHILL_MAIN_WORKFLOW_CREATE', blank_workflow) %} | ||||
|     {# vue component #} | ||||
|     <div data-pick-workflow="1" | ||||
|         data-related-entity-class="{{ blank_workflow.relatedEntityClass }}" | ||||
|         data-related-entity-id="{{ blank_workflow.relatedEntityId }}" | ||||
|         data-workflows-availables="{{ workflows_availables|json_encode()|e('html_attr') }}" | ||||
|     ></div> | ||||
|     {% set acl = "1" %} | ||||
| {% endif %} | ||||
|  | ||||
| {% if entity_workflows|length > 0 %} | ||||
|     {# vue component #} | ||||
|     <div data-list-workflows="1" data-workflows="{{ entity_workflows_json|json_encode|e('html_attr') }}"></div> | ||||
| {% endif %} | ||||
| {# vue component #} | ||||
| <div data-list-workflows="1" | ||||
|     data-workflows="{{ entity_workflows_json|json_encode|e('html_attr') }}" | ||||
|     data-allow-create="{{ acl }}" | ||||
|     data-related-entity-class="{{ blank_workflow.relatedEntityClass }}" | ||||
|     data-related-entity-id="{{ blank_workflow.relatedEntityId }}" | ||||
|     data-workflows-availables="{{ workflows_availables|json_encode()|e('html_attr') }}" | ||||
| ></div> | ||||
|   | ||||
| @@ -2,30 +2,42 @@ | ||||
|  | ||||
| <div class="flex-table"> | ||||
|     {% for step in entity_workflow.stepsChained %} | ||||
|         {% set place_labels = workflow_metadata(entity_workflow, 'label', step.currentStep) %} | ||||
|         {% set place_label = place_labels is null ? step.currentStep : place_labels|localize_translatable_string %} | ||||
|  | ||||
|         <div class="item-bloc {{ 'bloc' ~ step.id }} {% if loop.first %}initial{% endif %}"> | ||||
|             <div class="item-row"> | ||||
|  | ||||
|                 {% if loop.first and step.next is null %} | ||||
|                     <div class="item-col"> | ||||
|                         {{ 'workflow.No transitions'|trans }} | ||||
|                     </div> | ||||
|                 {% else %} | ||||
|  | ||||
|                     <div class="item-col"> | ||||
|                         {% if step.previous is not null and step.previous.freezeAfter == true %} | ||||
|                             <i class="fa fa-snowflake-o fa-sm me-1" title="{{ 'workflow.Freezed'|trans }}"></i> | ||||
|                         {% endif %} | ||||
|                     </div> | ||||
|                     <div class="item-col flex-column align-items-end"> | ||||
|                         <div class="decided"> | ||||
|                             {{ place_label }} | ||||
|                         </div> | ||||
|                         {# | ||||
|                         <div class="decided"> | ||||
|                         <i class="fa fa-times fa-fw text-danger"></i> | ||||
|                         Refusé | ||||
|                         </div> | ||||
|                         #} | ||||
|                     </div> | ||||
|                 {% endif %} | ||||
|  | ||||
|                 <div class="item-col flex-column align-items-end"> | ||||
|                     <div class="decided"> | ||||
|                         {% if not loop.first %} | ||||
|                         <i class="fa fa-check fa-fw text-success"></i> | ||||
|                         {% endif %} | ||||
|                         {{ step.currentStep }} | ||||
|                     </div> | ||||
|                     {# | ||||
|                     <div class="decided"> | ||||
|                     <i class="fa fa-times fa-fw text-danger"></i> | ||||
|                     Refusé | ||||
|                     </div> | ||||
|                     #} | ||||
|                 </div> | ||||
|             </div> | ||||
|             {% if step.next is not null %} | ||||
|                 {% set transition = chill_workflow_transition_by_string(step.entityWorkflow, step.transitionAfter) %} | ||||
|                 {% set transition_labels = workflow_metadata(step.entityWorkflow, 'label', transition) %} | ||||
|                 {% set transition_label = transition_labels is null ? step.transitionAfter : transition_labels|localize_translatable_string %} | ||||
|                 {% set forward = workflow_metadata(step.entityWorkflow, 'isForward', transition) %} | ||||
|                 <div class="item-row separator"> | ||||
|                     <div class="item-col" style="width: inherit;"> | ||||
|                         {% if step.transitionBy is not null %} | ||||
| @@ -40,7 +52,12 @@ | ||||
|                     <div class="item-col flex-column align-items-end"> | ||||
|                         <div class="to-decision"> | ||||
|                             <i class="fa fa-share fa-fw text-secondary" title="transféré"></i> | ||||
|                             {{ step.next.currentStep }} | ||||
|                             {% if forward %} | ||||
|                                 <i class="fa fa-check fa-fw text-success"></i> | ||||
|                             {% else %} | ||||
|                                 <i class="fa fa-times fa-fw text-danger"></i> | ||||
|                             {% endif %} | ||||
|                             {{ transition_label }} | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|   | ||||
| @@ -1,41 +1,62 @@ | ||||
| {% macro popoverContent(step) %} | ||||
|     <ul class="small_in_title"> | ||||
|         <li> | ||||
|             <span class="item-key">{{ 'By'|trans ~ ' : ' }}</span> | ||||
|             <b>{{ step.transitionBy|chill_entity_render_box }}</b> | ||||
|         </li> | ||||
|         <li> | ||||
|             <span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span> | ||||
|             <b>{{ step.transitionAt|format_datetime('short', 'short') }}</b> | ||||
|         </li> | ||||
|         {% if step.previous is not null %} | ||||
|             <li> | ||||
|                 <span class="item-key">{{ 'By'|trans ~ ' : ' }}</span> | ||||
|                 <b>{{ step.previous.transitionBy|chill_entity_render_box }}</b> | ||||
|             </li> | ||||
|             <li> | ||||
|                 <span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span> | ||||
|                 <b>{{ step.previous.transitionAt|format_datetime('short', 'short') }}</b> | ||||
|             </li> | ||||
|         {% else %} | ||||
|             <li> | ||||
|                 <span class="item-key">{{ 'workflow.Created by'|trans ~ ' : ' }}</span> | ||||
|                 <b>{{ step.entityWorkflow.createdBy|chill_entity_render_box }}</b> | ||||
|             </li> | ||||
|             <li> | ||||
|                 <span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span> | ||||
|                 <b>{{ step.entityWorkflow.createdAt|format_datetime('short', 'short') }}</b> | ||||
|             </li> | ||||
|         {% endif %} | ||||
|     </ul> | ||||
| {% endmacro %} | ||||
|  | ||||
| {% macro popoverTitle(step) %} | ||||
|     {% if step.previous is not null and step.previous.freezeAfter == true %} | ||||
|         <i class="fa fa-snowflake-o fa-sm me-1" title="{{ 'workflow.Freezed'|trans }}"></i> | ||||
|     {% endif %} | ||||
|     {% if step.previous is not null %} | ||||
|         {% set transition = chill_workflow_transition_by_string(step.entityWorkflow, step.previous.transitionAfter) %} | ||||
|         {% set labels = workflow_metadata(step.entityWorkflow, 'label', transition) %} | ||||
|         {% set label = labels is null ? step.previous.transitionAfter : labels|localize_translatable_string %} | ||||
|         {{ label }} | ||||
|     {% endif %} | ||||
| {% endmacro %} | ||||
|  | ||||
| {% macro breadcrumb(_ctx) %} | ||||
|     <div class="breadcrumb"> | ||||
|         {% for step in _ctx.entity_workflow.stepsChained %} | ||||
|             {% if step.previous is null %} | ||||
|                 {# | ||||
|                 {% set popContent = "Point de départ du workflow" %} | ||||
|                 {{ dump(step) }} | ||||
|                 #} | ||||
|                 {% set popContent = _self.popoverContent(step) %} | ||||
|             {% else %} | ||||
|                 {% set popContent = _self.popoverContent(step.previous) %} | ||||
|             {% endif %} | ||||
|             {% set labels = workflow_metadata(_ctx.entity_workflow, 'label', step.currentStep) %} | ||||
|             {% set label = labels is null ? step.currentStep : labels|localize_translatable_string %} | ||||
|             {% set popTitle = _self.popoverTitle(step) %} | ||||
|             {% set popContent = _self.popoverContent(step) %} | ||||
|             <span class="mx-2" | ||||
|                 tabindex="0" | ||||
|                 data-bs-trigger="focus hover" | ||||
|                 data-bs-toggle="popover" | ||||
|                 data-bs-placement="bottom" | ||||
|                 data-bs-custom-class="workflow-transition" | ||||
|                 title="{{ step.currentStep }}" | ||||
|                 title="{{ popTitle|e('html_attr') }}" | ||||
|                 data-bs-content="{{ popContent|e('html_attr') }}" | ||||
|                 > | ||||
|                 {% if step.currentStep == 'initial' %} | ||||
|                     <i class="fa fa-circle me-1 text-chill-yellow"></i> | ||||
|                 {% endif %} | ||||
|                 {{ step.currentStep }} | ||||
|                 {% if step.previous is not null and step.previous.freezeAfter == true %} | ||||
|                     <i class="fa fa-snowflake-o fa-sm me-1" title="{{ 'workflow.Freezed'|trans }}"></i> | ||||
|                 {% endif %} | ||||
|                 {{ label }} | ||||
|             </span> | ||||
|             {% if not loop.last %} | ||||
|                 → | ||||
|   | ||||
| @@ -13,6 +13,7 @@ | ||||
|     {{ encore_entry_link_tags('mod_ckeditor5') }} | ||||
|     {{ encore_entry_link_tags('chill') }} | ||||
|     {{ encore_entry_link_tags('mod_blur') }} | ||||
|     {{ encore_entry_link_tags('vue_onthefly') }} | ||||
|     {% block css %}<!-- nothing added to css -->{% endblock %} | ||||
| </head> | ||||
|  | ||||
| @@ -59,26 +60,26 @@ | ||||
|                 {% endif %} | ||||
|  | ||||
|                 {% block content %} | ||||
|                 <div class="col-8 main_search"> | ||||
|                     <h2>{{ 'Search'|trans }}</h2> | ||||
|  | ||||
|                     <form action="{{ path('chill_main_search') }}" method="get"> | ||||
|                         <input class="form-control form-control-lg" name="q" type="search" placeholder="{{ 'Search persons, ...'|trans }}" /> | ||||
|                         <center> | ||||
|                             <button type="submit" class="btn btn-lg btn-warning mt-3"> | ||||
|                                 <i class="fa fa-fw fa-search"></i> {{ 'Search'|trans }} | ||||
|                             </button> | ||||
|                             <a class="btn btn-lg btn-misc mt-3" href="{{ path('chill_main_advanced_search_list') }}"> | ||||
|                                 <i class="fa fa-fw fa-search"></i> {{ 'Advanced search'|trans }} | ||||
|                             </a> | ||||
|                         </center> | ||||
|                     </form> | ||||
|                 </div> | ||||
|                 <div class="col-8"> | ||||
|                     <a href="{{ path('chill_crud_aside_activity_new', {'type' : 7, 'duration' : '600', 'note' : 'Pas des remarques' }) }}"><div class="bloc btn btn-success btn-md btn-block">Appel téléphonique</div></a> | ||||
|                 </div> | ||||
|  | ||||
|                 {{  chill_widget('homepage', {} ) }} | ||||
|                     <div class="col-8 main_search"> | ||||
|                         <h2>{{ 'Search'|trans }}</h2> | ||||
|      | ||||
|                         <form action="{{ path('chill_main_search') }}" method="get"> | ||||
|                             <input class="form-control form-control-lg" name="q" type="search" placeholder="{{ 'Search persons, ...'|trans }}" /> | ||||
|                             <center> | ||||
|                                 <button type="submit" class="btn btn-lg btn-warning mt-3"> | ||||
|                                     <i class="fa fa-fw fa-search"></i> {{ 'Search'|trans }} | ||||
|                                 </button> | ||||
|                                 <a class="btn btn-lg btn-misc mt-3" href="{{ path('chill_main_advanced_search_list') }}"> | ||||
|                                     <i class="fa fa-fw fa-search"></i> {{ 'Advanced search'|trans }} | ||||
|                                 </a> | ||||
|                             </center> | ||||
|                         </form> | ||||
|                     </div> | ||||
|      | ||||
|                     {#  DISABLED {{  chill_widget('homepage', {} ) }} #} | ||||
|                      | ||||
|                     {% include '@ChillMain/Homepage/index.html.twig' %} | ||||
|                      | ||||
|                 {% endblock %} | ||||
|  | ||||
|             </div> | ||||
| @@ -94,6 +95,7 @@ | ||||
|     {{ encore_entry_script_tags('mod_ckeditor5') }} | ||||
|     {{ encore_entry_script_tags('mod_blur') }} | ||||
|     {{ encore_entry_script_tags('chill') }} | ||||
|     {{ encore_entry_script_tags('vue_onthefly') }} | ||||
|  | ||||
|     <script type="text/javascript"> | ||||
|         window.addEventListener('DOMContentLoaded', function(e) { | ||||
|   | ||||
| @@ -45,13 +45,15 @@ class SearchUserApiProvider implements SearchApiInterface | ||||
|         $query | ||||
|             ->setSelectKey('user') | ||||
|             ->setSelectJsonbMetadata("jsonb_build_object('id', u.id)") | ||||
|             ->setSelectPertinence('GREATEST(SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical), | ||||
|                 SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical))', [$pattern, $pattern]) | ||||
|             ->setSelectPertinence('GREATEST(SIMILARITY(LOWER(UNACCENT(?)), u.label), | ||||
|                 SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical))', [$pattern, $pattern]) | ||||
|             ->setFromClause('users AS u') | ||||
|             ->setWhereClauses('SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15 | ||||
|                 OR | ||||
|                 SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical) > 0.15 | ||||
|             ', [$pattern, $pattern]); | ||||
|             ->setWhereClauses(' | ||||
|                 SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15 | ||||
|                 OR u.usernamecanonical LIKE \'%\' || LOWER(UNACCENT(?)) || \'%\' | ||||
|                 OR SIMILARITY(LOWER(UNACCENT(?)), LOWER(UNACCENT(u.label))) > 0.15 | ||||
|                 OR u.label LIKE \'%\' || LOWER(UNACCENT(?)) || \'%\' | ||||
|             ', [$pattern, $pattern, $pattern, $pattern]); | ||||
|  | ||||
|         return $query; | ||||
|     } | ||||
|   | ||||
| @@ -22,6 +22,10 @@ class SearchApiQuery | ||||
|  | ||||
|     private array $fromClauseParams = []; | ||||
|  | ||||
|     private bool $isDistinct = false; | ||||
|  | ||||
|     private ?string $isDistinctKey = null; | ||||
|  | ||||
|     private ?string $jsonbMetadata = null; | ||||
|  | ||||
|     private array $jsonbMetadataParams = []; | ||||
| @@ -105,6 +109,11 @@ class SearchApiQuery | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function getDistinct(): bool | ||||
|     { | ||||
|         return $this->isDistinct; | ||||
|     } | ||||
|  | ||||
|     public function getFromClause(): string | ||||
|     { | ||||
|         return $this->fromClause; | ||||
| @@ -139,6 +148,14 @@ class SearchApiQuery | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setDistinct(bool $distinct, string $distinctKey): self | ||||
|     { | ||||
|         $this->isDistinct = $distinct; | ||||
|         $this->isDistinctKey = $distinctKey; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setFromClause(string $fromClause, array $params = []): self | ||||
|     { | ||||
|         $this->fromClause = $fromClause; | ||||
| @@ -185,7 +202,11 @@ class SearchApiQuery | ||||
|     private function buildSelectClause(bool $countOnly = false): string | ||||
|     { | ||||
|         if ($countOnly) { | ||||
|             return 'count(*) AS c'; | ||||
|             if (!$this->isDistinct) { | ||||
|                 return 'count(*) AS c'; | ||||
|             } | ||||
|  | ||||
|             return 'count(distinct ' . $this->isDistinctKey . ') AS c'; | ||||
|         } | ||||
|  | ||||
|         $selects = $this->getSelectClauses(); | ||||
| @@ -202,7 +223,7 @@ class SearchApiQuery | ||||
|             $selects[] = strtr('{pertinence} AS pertinence', ['{pertinence}' => $this->pertinence]); | ||||
|         } | ||||
|  | ||||
|         return implode(', ', $selects); | ||||
|         return ($this->isDistinct ? 'DISTINCT ' : '') . implode(', ', $selects); | ||||
|     } | ||||
|  | ||||
|     private function buildSelectParams(bool $count = false): array | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user