mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 01:08:26 +00:00 
			
		
		
		
	Compare commits
	
		
			71 Commits
		
	
	
		
			v3.10.2
			...
			321-text-e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b5c9e65986 | |||
| 8b2af35e97 | |||
| dc44c46667 | |||
| ba571c1a69 | |||
| 6a364705f2 | |||
| b6d454691a | |||
| 6d7a6932a9 | |||
|  | 2faf194b15 | ||
| f207599d86 | |||
| b0959f8cc5 | |||
| 4c5dee5f0a | |||
| f6c98aa0d5 | |||
| 6d13d184d5 | |||
| af36eccfaf | |||
| 483a20a43f | |||
| 6d8e2ad825 | |||
| 86388a63a8 | |||
|  | 5ea55ebfe5 | ||
| f97dc8f931 | |||
|  | a9c3aab528 | ||
| 1181377bd6 | |||
|  | 2275b7c560 | ||
| 4a8d298ae5 | |||
| 3e7f03d331 | |||
| b830952b9e | |||
| ad17313c61 | |||
| 620515ad15 | |||
| 50c377ee22 | |||
| cc7e7a90ee | |||
| 1d4ef19051 | |||
| 8337a724d1 | |||
| 8ca377d5d4 | |||
| 224e0bae43 | |||
| 3aa4fac80d | |||
|  | a7517eb647 | ||
| e278e636e0 | |||
|  | 40e373a9c7 | ||
| 1c1f418b18 | |||
| bf0e14b43a | |||
| 203a098054 | |||
| d58acff541 | |||
| 5858e05a42 | |||
| b9b4fafe14 | |||
| fe6949ea26 | |||
| 8a444a12f4 | |||
| 2dcce7b826 | |||
| dcd1777a70 | |||
| a6eb28175a | |||
| 7d78512823 | |||
| d0cd4792d6 | |||
| 6d196ead94 | |||
| 4047d5fd5b | |||
| 9aac80d834 | |||
| 7560dc57c6 | |||
| 10314845f6 | |||
| 9b84bc4d69 | |||
| a2fcf039be | |||
| b4d887a372 | |||
| 0aaa7122da | |||
| 1bc7f85874 | |||
| 1d2fd000aa | |||
| 506df432b0 | |||
| c32c18b0e2 | |||
| 321d569ee9 | |||
| cd40eb3932 | |||
| f0f2531fa3 | |||
| 183a220e7b | |||
| 9df127a82c | |||
| 04a1412562 | |||
| 3aef0a185e | |||
| 578bce31b9 | 
							
								
								
									
										6
									
								
								.changes/unreleased/DX-20250430-144550.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/DX-20250430-144550.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| kind: DX | ||||
| body: Remove dead code for wopi-link module | ||||
| time: 2025-04-30T14:45:50.406111606+02:00 | ||||
| custom: | ||||
|     Issue: "352" | ||||
|     SchemaChange: No schema change | ||||
							
								
								
									
										7
									
								
								.changes/unreleased/Feature-20250424-142211.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.changes/unreleased/Feature-20250424-142211.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| kind: Feature | ||||
| body: Add the document file name to the document title when a user upload a document, | ||||
|   unless there is already a document title. | ||||
| time: 2025-04-24T14:22:11.800975422+02:00 | ||||
| custom: | ||||
|   Issue: "377" | ||||
|   SchemaChange: No schema change | ||||
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20250520-095628.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20250520-095628.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| kind: Feature | ||||
| body: Add desactivation date for social action and issue csv export | ||||
| time: 2025-05-20T09:56:28.108941934+02:00 | ||||
| custom: | ||||
|     Issue: "" | ||||
|     SchemaChange: No schema change | ||||
							
								
								
									
										7
									
								
								.changes/unreleased/Fixed-20250424-133943.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.changes/unreleased/Fixed-20250424-133943.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| kind: Fixed | ||||
| body: trying to prevent bug of typeerror in doc-history + improved display of document | ||||
|   history | ||||
| time: 2025-04-24T13:39:43.878468232+02:00 | ||||
| custom: | ||||
|   Issue: "376" | ||||
|   SchemaChange: No schema change | ||||
							
								
								
									
										7
									
								
								.changes/unreleased/Fixed-20250424-163746.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.changes/unreleased/Fixed-20250424-163746.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| kind: Fixed | ||||
| body: Display previous participation in acc course work even if the person has left | ||||
|   the acc course | ||||
| time: 2025-04-24T16:37:46.970203594+02:00 | ||||
| custom: | ||||
|   Issue: "381" | ||||
|   SchemaChange: No schema change | ||||
							
								
								
									
										6
									
								
								.changes/unreleased/Fixed-20250505-102715.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Fixed-20250505-102715.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| kind: Fixed | ||||
| body: Fix display of text in calendar events | ||||
| time: 2025-05-05T10:27:15.461493066+02:00 | ||||
| custom: | ||||
|     Issue: "372" | ||||
|     SchemaChange: No schema change | ||||
							
								
								
									
										6
									
								
								.changes/unreleased/Fixed-20250514-145339.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Fixed-20250514-145339.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| kind: Fixed | ||||
| body: Add missing translation for user_group.no_user_groups | ||||
| time: 2025-05-14T14:53:39.53927329+02:00 | ||||
| custom: | ||||
|     Issue: "" | ||||
|     SchemaChange: No schema change | ||||
							
								
								
									
										6
									
								
								.changes/unreleased/UX-20250423-172624.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/UX-20250423-172624.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| kind: UX | ||||
| body: Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page | ||||
| time: 2025-04-23T17:26:24.45777387+02:00 | ||||
| custom: | ||||
|     Issue: "374" | ||||
|     SchemaChange: No schema change | ||||
							
								
								
									
										3
									
								
								.changes/v3.10.3.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.changes/v3.10.3.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ## v3.10.3 - 2025-03-18 | ||||
| ### DX | ||||
| * Eslint fixes    | ||||
							
								
								
									
										19
									
								
								.changes/v3.11.0.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.changes/v3.11.0.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| ## v3.11.0 - 2025-04-17 | ||||
| ### Feature | ||||
| * ([#365](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/365)) Add counters of actions and activities, with 2 boxes to (1) show the number of active actions on total actions and (2) show the number of activities in a accompanying period, and pills in menus for showing the number of active actions and the number of activities. | ||||
| * ([#364](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/364)) Added a second phone number "telephone2" to the thirdParty entity. Adapted twig templates and vuejs apps to handle this phone number | ||||
|  | ||||
|   **Schema Change**: Add columns or tables | ||||
| * Signature: add a button to go directly to the signature zone, even if there is only one | ||||
| ### Fixed | ||||
| * Fixed wrong translations in the on-the-fly for creation of thirdParty | ||||
| * Fixed update of phone number in on-the-fly edition of thirdParty | ||||
| * Fixed closing of modal when editing thirdParty in accompanying course works | ||||
| * Shorten the delay between two execution of AccompanyingPeriodStepChangeCronjob, to ensure at least one execution in a day | ||||
| * ([#102](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/102)) Fix display of title in document list | ||||
| * When cleaning the old stored object versions, do not throw an error if the stored object is not found on disk | ||||
| * Add consistent log prefix and key to logs when stale workflows are automatically canceled | ||||
| * ([#380](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/380)) Remove the "not null" validation constraint on recently added properties on HouseholdComposition | ||||
|  | ||||
| ### DX | ||||
| * Add new chill-col style for displaying title and aside in a flex table | ||||
| @@ -6,6 +6,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), | ||||
| and is generated by [Changie](https://github.com/miniscruff/changie). | ||||
|  | ||||
|  | ||||
| ## v3.10.3 - 2025-03-18 | ||||
| ### DX | ||||
| * Eslint fixes    | ||||
|  | ||||
| ## v3.10.2 - 2025-03-17 | ||||
| ### Fixed | ||||
| * Replace a ts-expect-error with a ts-ignore    | ||||
|   | ||||
| @@ -220,6 +220,7 @@ framework: | ||||
|                         - attenteModification | ||||
|                         - attenteMiseEnForme | ||||
|                         - attenteValidationMiseEnForme | ||||
|                         - attenteSignature | ||||
|                         - attenteVisa | ||||
|                         - postSignature | ||||
|                         - attenteTraitement | ||||
|   | ||||
| @@ -34,6 +34,7 @@ export default ts.config( | ||||
|             // override/add rules settings here, such as: | ||||
|             "vue/multi-word-component-names": "off", | ||||
|             "@typescript-eslint/no-require-imports": "off", | ||||
|             "@typescript-eslint/ban-ts-comment": "off" | ||||
|         }, | ||||
|     }, | ||||
| ); | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
|     "@hotwired/stimulus": "^3.0.0", | ||||
|     "@luminateone/eslint-baseline": "^1.0.9", | ||||
|     "@symfony/stimulus-bridge": "^3.2.0", | ||||
|     "@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets", | ||||
|     "@symfony/webpack-encore": "^4.1.0", | ||||
|     "@tsconfig/node20": "^20.1.4", | ||||
|     "@types/dompurify": "^3.0.5", | ||||
|   | ||||
| @@ -11,6 +11,7 @@ declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\ActivityBundle\Menu; | ||||
|  | ||||
| use Chill\ActivityBundle\Entity\Activity; | ||||
| use Chill\ActivityBundle\Security\Authorization\ActivityVoter; | ||||
| use Chill\MainBundle\Routing\LocalMenuBuilderInterface; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| @@ -23,22 +24,30 @@ use Symfony\Contracts\Translation\TranslatorInterface; | ||||
|  */ | ||||
| class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface | ||||
| { | ||||
|     public function __construct(protected Security $security, protected TranslatorInterface $translator) {} | ||||
|     public function __construct( | ||||
|         protected Security $security, | ||||
|         protected TranslatorInterface $translator, | ||||
|         private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry, | ||||
|     ) {} | ||||
|  | ||||
|     public function buildMenu($menuId, MenuItem $menu, array $parameters) | ||||
|     { | ||||
|         $period = $parameters['accompanyingCourse']; | ||||
|  | ||||
|         $activities = $this->managerRegistry->getManager()->getRepository(Activity::class)->findBy( | ||||
|             ['accompanyingPeriod' => $period] | ||||
|         ); | ||||
|  | ||||
|         if ( | ||||
|             AccompanyingPeriod::STEP_DRAFT !== $period->getStep() | ||||
|             && $this->security->isGranted(ActivityVoter::SEE, $period) | ||||
|         ) { | ||||
|             $menu->addChild($this->translator->trans('Activity'), [ | ||||
|             $menu->addChild($this->translator->trans('Activities'), [ | ||||
|                 'route' => 'chill_activity_activity_list', | ||||
|                 'routeParameters' => [ | ||||
|                     'accompanying_period_id' => $period->getId(), | ||||
|                 ], ]) | ||||
|                 ->setExtras(['order' => 40]); | ||||
|                 ->setExtras(['order' => 40, 'counter' => count($activities) > 0 ? count($activities) : null]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,7 @@ declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\ActivityBundle\Menu; | ||||
|  | ||||
| use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface; | ||||
| use Chill\ActivityBundle\Security\Authorization\ActivityVoter; | ||||
| use Chill\MainBundle\Routing\LocalMenuBuilderInterface; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| @@ -23,13 +24,20 @@ use Symfony\Contracts\Translation\TranslatorInterface; | ||||
|  */ | ||||
| final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface | ||||
| { | ||||
|     public function __construct(private AuthorizationCheckerInterface $authorizationChecker, private TranslatorInterface $translator) {} | ||||
|     public function __construct( | ||||
|         private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository, | ||||
|         private AuthorizationCheckerInterface $authorizationChecker, | ||||
|         private TranslatorInterface $translator, | ||||
|     ) {} | ||||
|  | ||||
|     public function buildMenu($menuId, MenuItem $menu, array $parameters) | ||||
|     { | ||||
|         /** @var Person $person */ | ||||
|         $person = $parameters['person']; | ||||
|  | ||||
|  | ||||
|         $count = $this->activityACLAwareRepository->countByPerson($person, ActivityVoter::SEE); | ||||
|  | ||||
|         if ($this->authorizationChecker->isGranted(ActivityVoter::SEE, $person)) { | ||||
|             $menu->addChild( | ||||
|                 $this->translator->trans('Activities'), | ||||
| @@ -38,7 +46,7 @@ final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface | ||||
|                     'routeParameters' => ['person_id' => $person->getId()], | ||||
|                 ] | ||||
|             ) | ||||
|                 ->setExtra('order', 201); | ||||
|                 ->setExtras(['order' => 201, 'counter' => $count > 0 ? $count : null]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -120,3 +120,34 @@ li.document-list-item { | ||||
|         vertical-align: baseline; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .badge-activity-type-simple { | ||||
|     @extend .badge; | ||||
|     display: inline-block; | ||||
|     margin: 0.2rem 0; | ||||
|     padding-left: 0; | ||||
|     padding-right: 0.5rem; | ||||
|  | ||||
|     border-left: 20px groove #9acd32; | ||||
|     border-radius: $badge-border-radius; | ||||
|  | ||||
|     color: black; | ||||
|     font-weight: normal; | ||||
|     font-size: unset; | ||||
|     max-width: 100%; | ||||
|     background-color: $gray-100; | ||||
|  | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
|     text-indent: 5px hanging; | ||||
|     text-align: left; | ||||
|  | ||||
|     &::before { | ||||
|         margin-right: 3px; | ||||
|         position: relative; | ||||
|         left: -0.5px; | ||||
|         font-family: ForkAwesome; | ||||
|         content: '\f04b'; | ||||
|         color: #9acd32; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -11,7 +11,7 @@ import Location from "./components/Location.vue"; | ||||
|  | ||||
| export default { | ||||
|     name: "App", | ||||
|     props: ["hasSocialIssues", "hasLocation", "hasPerson"], | ||||
|     props: ["hasSocialIssues", "hasLocation", "hasPerson", "isSimpleEditor"], | ||||
|     components: { | ||||
|         ConcernedGroups, | ||||
|         SocialIssuesAcc, | ||||
|   | ||||
| @@ -14,18 +14,21 @@ const i18n = _createI18n(activityMessages); | ||||
| const hasSocialIssues = document.querySelector("#social-issues-acc") !== null; | ||||
| const hasLocation = document.querySelector("#location") !== null; | ||||
| const hasPerson = document.querySelector("#add-persons") !== null; | ||||
| const isSimpleEditor = true; | ||||
|  | ||||
| const app = createApp({ | ||||
|   template: `<app | ||||
|        :hasSocialIssues="hasSocialIssues" | ||||
|        :hasLocation="hasLocation" | ||||
|        :hasPerson="hasPerson" | ||||
|        :isSimpleEditor = "isSimpleEditor" | ||||
|     ></app>`, | ||||
|   data() { | ||||
|     return { | ||||
|       hasSocialIssues, | ||||
|       hasLocation, | ||||
|       hasPerson, | ||||
|       isSimpleEditor | ||||
|     }; | ||||
|   }, | ||||
| }) | ||||
|   | ||||
| @@ -126,4 +126,4 @@ | ||||
|  | ||||
| {% block css %} | ||||
|     {{ encore_entry_link_tags('mod_pickentity_type') }} | ||||
| {% endblock %} | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -13,44 +13,44 @@ | ||||
| {% endif %} | ||||
|  | ||||
| <div class="item-row"> | ||||
|     <div class="item-col" style="width: unset"> | ||||
|         {% if document.isPending %} | ||||
|             <div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div> | ||||
|         {% elseif document.isFailure %} | ||||
|             <div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div> | ||||
|         {% endif %} | ||||
|  | ||||
|         <div> | ||||
|             {% if activity.accompanyingPeriod is not null and context == 'person' %} | ||||
|                 <span class="badge bg-primary"> | ||||
|                         <i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }} | ||||
|                     </span>  | ||||
|     <div class="item-two-col-grid"> | ||||
|         <div class="title"> | ||||
|             {% if document.isPending %} | ||||
|                 <div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div> | ||||
|             {% elseif document.isFailure %} | ||||
|                 <div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div> | ||||
|             {% endif %} | ||||
|             <div class="badge-activity-type"> | ||||
|                 <span class="title_label"></span> | ||||
|                 <span class="title_action"> | ||||
|                     {{ activity.type.name | localize_translatable_string }} | ||||
|  | ||||
|             <div> | ||||
|                 <div> | ||||
|                     <div class="badge-activity-type-simple"> | ||||
|                         {{ activity.type.name | localize_translatable_string }} | ||||
|                     </div> | ||||
|                     {% if activity.emergency %} | ||||
|                         <span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span> | ||||
|                     {% endif %} | ||||
|                     </span> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="denomination h2"> | ||||
|             {{ document.title|chill_print_or_message("No title") }} | ||||
|         </div> | ||||
|         {% if document.hasTemplate %} | ||||
|             <div> | ||||
|                 <p>{{ document.template.name|localize_translatable_string }}</p> | ||||
|             <div class="denomination h2"> | ||||
|                 {{ document.title|chill_print_or_message("No title") }} | ||||
|             </div> | ||||
|         {% endif %} | ||||
|     </div> | ||||
|  | ||||
|     <div class="item-col"> | ||||
|         <div class="container"> | ||||
|             {% if document.hasTemplate %} | ||||
|                 <div> | ||||
|                     <p>{{ document.template.name|localize_translatable_string }}</p> | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|         </div> | ||||
|         <div class="aside"> | ||||
|             <div class="dates row text-end"> | ||||
|                 <span>{{ document.createdAt|format_date('short') }}</span> | ||||
|             </div> | ||||
|             {% if activity.accompanyingPeriod is not null and context == 'person' %} | ||||
|                 <div class="text-end"> | ||||
|                     <span class="badge bg-primary"> | ||||
|                         <i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }} | ||||
|                     </span>  | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| @import '~ChillPersonAssets/chill/scss/mixins.scss'; | ||||
| @import '~ChillMainAssets/module/bootstrap/shared'; | ||||
| @import '~ChillPersonAssets/chill/scss/mixins.scss'; | ||||
| @import 'bootstrap/scss/_badge.scss'; | ||||
|  | ||||
| .badge-calendar { | ||||
|     display: inline-block; | ||||
| @@ -23,3 +24,35 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| .badge-calendar-simple { | ||||
|     @extend .badge; | ||||
|     display: inline-block; | ||||
|     margin: 0.2rem 0; | ||||
|     padding-left: 0; | ||||
|     padding-right: 0.5rem; | ||||
|  | ||||
|     border-left: 20px groove $chill-l-gray; | ||||
|     border-radius: $badge-border-radius; | ||||
|  | ||||
|     max-width: 100%; | ||||
|     background-color: $gray-100; | ||||
|  | ||||
|     color: black; | ||||
|     font-weight: normal; | ||||
|     overflow: hidden; | ||||
|     font-weight: normal; | ||||
|     font-size: unset; | ||||
|     text-overflow: ellipsis; | ||||
|     text-indent: 5px hanging; | ||||
|     text-align: left; | ||||
|  | ||||
|     &::before { | ||||
|         margin-right: 3px; | ||||
|         position: relative; | ||||
|         left: -0.5px; | ||||
|         font-family: ForkAwesome; | ||||
|         content: '\f04b'; | ||||
|         color: $chill-l-gray; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -16,7 +16,7 @@ div.calendar-list { | ||||
|     } | ||||
|  | ||||
|     & > a.calendar-list__global { | ||||
|         display: inline-block;; | ||||
|         display: inline-block; | ||||
|         padding: 0.2rem; | ||||
|         min-width: 2rem; | ||||
|         border: 1px solid var(--bs-chill-blue); | ||||
|   | ||||
| @@ -96,23 +96,23 @@ | ||||
|         </div> | ||||
|     </div> | ||||
|     <FullCalendar :options="calendarOptions" ref="calendarRef"> | ||||
|         <template v-slot:eventContent="{ arg }: { arg: { event: EventApi } }"> | ||||
|             <span :class="eventClasses(arg.event)"> | ||||
|                 <b v-if="arg.event.extendedProps.is === 'remote'">{{ | ||||
|                     arg.event.title | ||||
|         <template v-slot:eventContent="{ event }"> | ||||
|             <span :class="eventClasses(event)"> | ||||
|                 <b v-if="event.extendedProps.is === 'remote'">{{ | ||||
|                     event.title | ||||
|                 }}</b> | ||||
|                 <b v-else-if="arg.event.extendedProps.is === 'range'" | ||||
|                     >{{ arg.event.startStr }} - | ||||
|                     {{ arg.event.extendedProps.locationName }}</b | ||||
|                 <b v-else-if="event.extendedProps.is === 'range'" | ||||
|                     >{{ formatDate(event.startStr) }} - | ||||
|                     {{ event.extendedProps.locationName }}</b | ||||
|                 > | ||||
|                 <b v-else-if="arg.event.extendedProps.is === 'local'">{{ | ||||
|                     arg.event.title | ||||
|                 <b v-else-if="event.extendedProps.is === 'local'">{{ | ||||
|                     event.title | ||||
|                 }}</b> | ||||
|                 <b v-else>no 'is'</b> | ||||
|                 <a | ||||
|                     v-if="arg.event.extendedProps.is === 'range'" | ||||
|                     v-if="event.extendedProps.is === 'range'" | ||||
|                     class="fa fa-fw fa-times delete" | ||||
|                     @click.prevent="onClickDelete(arg.event)" | ||||
|                     @click.prevent="onClickDelete(event)" | ||||
|                 > | ||||
|                 </a> | ||||
|             </span> | ||||
| @@ -221,13 +221,12 @@ import type { | ||||
|     DatesSetArg, | ||||
|     EventInput, | ||||
| } from "@fullcalendar/core"; | ||||
| import { reactive, computed, ref, onMounted } from "vue"; | ||||
| import { computed, ref, onMounted } from "vue"; | ||||
| import { useStore } from "vuex"; | ||||
| import { key } from "./store"; | ||||
| import FullCalendar from "@fullcalendar/vue3"; | ||||
| import frLocale from "@fullcalendar/core/locales/fr"; | ||||
| import interactionPlugin, { | ||||
|     DropArg, | ||||
|     EventResizeDoneArg, | ||||
| } from "@fullcalendar/interaction"; | ||||
| import timeGridPlugin from "@fullcalendar/timegrid"; | ||||
| @@ -237,19 +236,13 @@ import { | ||||
|     EventDropArg, | ||||
|     EventClickArg, | ||||
| } from "@fullcalendar/core"; | ||||
| import { | ||||
|     dateToISO, | ||||
|     ISOToDate, | ||||
| } from "../../../../../ChillMainBundle/Resources/public/chill/js/date"; | ||||
| import { dateToISO, ISOToDate } from "ChillMainAssets/chill/js/date"; | ||||
| import VueMultiselect from "vue-multiselect"; | ||||
| import { Location } from "../../../../../ChillMainBundle/Resources/public/types"; | ||||
| import { Location } from "ChillMainAssets/types"; | ||||
| import EditLocation from "./Components/EditLocation.vue"; | ||||
| import { useI18n } from "vue-i18n"; | ||||
|  | ||||
| const store = useStore(key); | ||||
|  | ||||
| const { t } = useI18n(); | ||||
|  | ||||
| const showWeekends = ref(false); | ||||
| const slotDuration = ref("00:15:00"); | ||||
| const slotMinTime = ref("09:00:00"); | ||||
| @@ -301,6 +294,11 @@ const nextWeeks = computed((): Weeks[] => | ||||
|     }), | ||||
| ); | ||||
|  | ||||
| const formatDate = (datetime: string) => { | ||||
|     console.log(typeof datetime); | ||||
|     return ISOToDate(datetime); | ||||
| }; | ||||
|  | ||||
| const baseOptions = ref<CalendarOptions>({ | ||||
|     locale: frLocale, | ||||
|     plugins: [interactionPlugin, timeGridPlugin], | ||||
| @@ -353,7 +351,7 @@ const pickedLocation = computed<Location | null>({ | ||||
|  * return the show classes for the event | ||||
|  * @param arg | ||||
|  */ | ||||
| const eventClasses = function (arg: EventApi): object { | ||||
| const eventClasses = function (): object { | ||||
|     return { calendarRangeItems: true }; | ||||
| }; | ||||
|  | ||||
| @@ -431,7 +429,6 @@ function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) { | ||||
|     if (payload.event.extendedProps.is !== "range") { | ||||
|         return; | ||||
|     } | ||||
|     const changedEvent = payload.event; | ||||
|  | ||||
|     store.dispatch("calendarRanges/patchRangeTime", { | ||||
|         calendarRangeId: payload.event.extendedProps.calendarRangeId, | ||||
|   | ||||
| @@ -6,50 +6,48 @@ | ||||
|  | ||||
|  | ||||
| <div class="item-row"> | ||||
|     <div class="item-col" style="width: unset"> | ||||
|         {% if document.storedObject.isPending %} | ||||
|             <div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div> | ||||
|         {% elseif document.storedObject.isFailure %} | ||||
|             <div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div> | ||||
|         {% endif %} | ||||
|  | ||||
|         <div> | ||||
|             {% if c.accompanyingPeriod is not null and context == 'person' %} | ||||
|                 <span class="badge bg-primary"> | ||||
|                         <i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }} | ||||
|                     </span>  | ||||
|     <div class="item-two-col-grid"> | ||||
|         <div class="title"> | ||||
|             {% if document.storedObject.isPending %} | ||||
|                 <div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div> | ||||
|             {% elseif document.storedObject.isFailure %} | ||||
|                 <div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div> | ||||
|             {% endif %} | ||||
|  | ||||
|             <span class="badge-calendar"> | ||||
|                     <span class="title_label"></span> | ||||
|                     <span class="title_action"> | ||||
|                         {{ 'Calendar'|trans }} | ||||
|                         {% if c.endDate.diff(c.startDate).days >= 1 %} | ||||
|                             {{ c.startDate|format_datetime('short', 'short') }} | ||||
|                             - {{ c.endDate|format_datetime('short', 'short') }} | ||||
|                         {% else %} | ||||
|                             {{ c.startDate|format_datetime('short', 'short') }} | ||||
|                             - {{ c.endDate|format_datetime('none', 'short') }} | ||||
|                         {% endif %} | ||||
|                     </span> | ||||
|                 </span> | ||||
|         </div> | ||||
|  | ||||
|         <div class="denomination h2"> | ||||
|             {{ document.storedObject.title|chill_print_or_message("No title") }} | ||||
|         </div> | ||||
|         {% if document.storedObject.hasTemplate %} | ||||
|             <div> | ||||
|                 <p>{{ document.storedObject.template.name|localize_translatable_string }}</p> | ||||
|             </div> | ||||
|         {% endif %} | ||||
|     </div> | ||||
|  | ||||
|     <div class="item-col"> | ||||
|         <div class="container"> | ||||
|                 <span class="badge-calendar-simple"> | ||||
|                     {{ 'Calendar'|trans }} | ||||
|                     {% if c.endDate.diff(c.startDate).days >= 1 %} | ||||
|                         {{ c.startDate|format_datetime('short', 'short') }} | ||||
|                         - {{ c.endDate|format_datetime('short', 'short') }} | ||||
|                     {% else %} | ||||
|                         {{ c.startDate|format_datetime('short', 'short') }} | ||||
|                         - {{ c.endDate|format_datetime('none', 'short') }} | ||||
|                     {% endif %} | ||||
|                 </span> | ||||
|             </div> | ||||
|  | ||||
|             <div class="denomination h2"> | ||||
|                 {{ document.storedObject.title|chill_print_or_message("No title") }} | ||||
|             </div> | ||||
|             {% if document.storedObject.hasTemplate %} | ||||
|                 <div> | ||||
|                     <p>{{ document.storedObject.template.name|localize_translatable_string }}</p> | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|         </div> | ||||
|         <div class="aside"> | ||||
|             <div class="dates row text-end"> | ||||
|                 <span>{{ document.storedObject.createdAt|format_date('short') }}</span> | ||||
|             </div> | ||||
|             {% if c.accompanyingPeriod is not null and context == 'person' %} | ||||
|                 <div class="text-end"> | ||||
|                     <span class="badge bg-primary"> | ||||
|                         <i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }} | ||||
|                     </span>  | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|   | ||||
| @@ -10,6 +10,9 @@ const startApp = ( | ||||
|     collectionEntry: null | HTMLLIElement, | ||||
| ): void => { | ||||
|     console.log("app started", divElement); | ||||
|  | ||||
|     const inputTitle = collectionEntry?.querySelector("input[type='text']"); | ||||
|  | ||||
|     const input_stored_object: HTMLInputElement | null = | ||||
|         divElement.querySelector("input[data-stored-object]"); | ||||
|     if (null === input_stored_object) { | ||||
| @@ -26,9 +29,10 @@ const startApp = ( | ||||
|     const app = createApp({ | ||||
|         template: | ||||
|             '<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>', | ||||
|         data(vm) { | ||||
|         data() { | ||||
|             return { | ||||
|                 existingDoc: existingDoc, | ||||
|                 inputTitle: inputTitle, | ||||
|             }; | ||||
|         }, | ||||
|         components: { | ||||
| @@ -38,10 +42,13 @@ const startApp = ( | ||||
|             addDocument: function ({ | ||||
|                 stored_object, | ||||
|                 stored_object_version, | ||||
|                 file_name, | ||||
|             }: { | ||||
|                 stored_object: StoredObject; | ||||
|                 stored_object_version: StoredObjectVersion; | ||||
|                 file_name: string; | ||||
|             }): void { | ||||
|                 stored_object.title = file_name; | ||||
|                 console.log("object added", stored_object); | ||||
|                 console.log("version added", stored_object_version); | ||||
|                 this.$data.existingDoc = stored_object; | ||||
| @@ -49,6 +56,11 @@ const startApp = ( | ||||
|                 input_stored_object.value = JSON.stringify( | ||||
|                     this.$data.existingDoc, | ||||
|                 ); | ||||
|                 if (this.$data.inputTitle) { | ||||
|                     if (!this.$data.inputTitle?.value) { | ||||
|                         this.$data.inputTitle.value = file_name; | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             removeDocument: function (object: StoredObject): void { | ||||
|                 console.log("catch remove document", object); | ||||
|   | ||||
| @@ -2,26 +2,28 @@ | ||||
|     <teleport to="body"> | ||||
|         <modal v-if="modalOpen" @close="modalOpen = false"> | ||||
|             <template v-slot:header> | ||||
|                 <h2>{{ $t("signature_confirmation") }}</h2> | ||||
|                 <h2>{{ trans(SIGNATURES_SIGNATURE_CONFIRMATION) }}</h2> | ||||
|             </template> | ||||
|             <template v-slot:body> | ||||
|                 <div class="signature-modal-body text-center" v-if="loading"> | ||||
|                     <p>{{ $t("electronic_signature_in_progress") }}</p> | ||||
|                     <p> | ||||
|                         {{ trans(SIGNATURES_ELECTRONIC_SIGNATURE_IN_PROGRESS) }} | ||||
|                     </p> | ||||
|                     <div class="loading"> | ||||
|                         <i | ||||
|                             class="fa fa-circle-o-notch fa-spin fa-3x" | ||||
|                             :title="$t('loading')" | ||||
|                             :title="trans(SIGNATURES_LOADING)" | ||||
|                         ></i> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="signature-modal-body text-center" v-else> | ||||
|                     <p>{{ $t("you_are_going_to_sign") }}</p> | ||||
|                     <p>{{ $t("are_you_sure") }}</p> | ||||
|                     <p>{{ trans(SIGNATURES_YOU_ARE_GOING_TO_SIGN) }}</p> | ||||
|                     <p>{{ trans(SIGNATURES_ARE_YOU_SURE) }}</p> | ||||
|                 </div> | ||||
|             </template> | ||||
|             <template v-slot:footer> | ||||
|                 <button class="btn btn-action" @click.prevent="confirmSign"> | ||||
|                     {{ $t("yes") }} | ||||
|                     {{ trans(SIGNATURES_YES) }} | ||||
|                 </button> | ||||
|             </template> | ||||
|         </modal> | ||||
| @@ -82,28 +84,39 @@ | ||||
|                         @change="toggleMultiPage" | ||||
|                     /> | ||||
|                     <label class="form-check-label" for="checkboxMulti"> | ||||
|                         {{ $t("all_pages") }} | ||||
|                         {{ trans(SIGNATURES_ALL_PAGES) }} | ||||
|                     </label> | ||||
|                 </template> | ||||
|             </div> | ||||
|             <div | ||||
|                 v-if="signature.zones.length > 0" | ||||
|                 v-if="signature.zones.length === 1 && signedState !== 'signed'" | ||||
|                 class="col-5 p-0 text-center turnSignature" | ||||
|             > | ||||
|                 <button | ||||
|                     :disabled="isFirstSignatureZone" | ||||
|                     class="btn btn-light btn-sm" | ||||
|                     @click="goToSignatureZoneUnique" | ||||
|                 > | ||||
|                     {{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }} | ||||
|                 </button> | ||||
|             </div> | ||||
|             <div | ||||
|                 v-if="signature.zones.length > 1" | ||||
|                 class="col-5 p-0 text-center turnSignature" | ||||
|             > | ||||
|                 <button | ||||
|                     :disabled="isFirstSignatureZone()" | ||||
|                     class="btn btn-light btn-sm" | ||||
|                     @click="turnSignature(-1)" | ||||
|                 > | ||||
|                     {{ $t("last_zone") }} | ||||
|                     {{ trans(SIGNATURES_LAST_ZONE) }} | ||||
|                 </button> | ||||
|                 <span>|</span> | ||||
|                 <button | ||||
|                     :disabled="isLastSignatureZone" | ||||
|                     :disabled="isLastSignatureZone()" | ||||
|                     class="btn btn-light btn-sm" | ||||
|                     @click="turnSignature(1)" | ||||
|                 > | ||||
|                     {{ $t("next_zone") }} | ||||
|                     {{ trans(SIGNATURES_NEXT_ZONE) }} | ||||
|                 </button> | ||||
|             </div> | ||||
|             <div class="col text-end" v-if="signedState !== 'signed'"> | ||||
| @@ -112,9 +125,9 @@ | ||||
|                     :hidden="!userSignatureZone" | ||||
|                     @click="undoSign" | ||||
|                     v-if="signature.zones.length > 1" | ||||
|                     :title="$t('choose_another_signature')" | ||||
|                     :title="trans(SIGNATURES_CHOOSE_ANOTHER_SIGNATURE)" | ||||
|                 > | ||||
|                     {{ $t("another_zone") }} | ||||
|                     {{ trans(SIGNATURES_ANOTHER_ZONE) }} | ||||
|                 </button> | ||||
|                 <button | ||||
|                     class="btn btn-misc btn-sm" | ||||
| @@ -122,7 +135,7 @@ | ||||
|                     @click="undoSign" | ||||
|                     v-else | ||||
|                 > | ||||
|                     {{ $t("cancel") }} | ||||
|                     {{ trans(SIGNATURES_CANCEL) }} | ||||
|                 </button> | ||||
|                 <button | ||||
|                     v-if="userSignatureZone === null" | ||||
| @@ -134,7 +147,7 @@ | ||||
|                         active: canvasEvent === 'add', | ||||
|                     }" | ||||
|                     @click="toggleAddZone()" | ||||
|                     :title="$t('add_sign_zone')" | ||||
|                     :title="trans(SIGNATURES_ADD_SIGN_ZONE)" | ||||
|                 > | ||||
|                     <template v-if="canvasEvent === 'add'"> | ||||
|                         <div | ||||
| @@ -186,48 +199,70 @@ | ||||
|                         @change="toggleMultiPage" | ||||
|                     /> | ||||
|                     <label class="form-check-label" for="checkboxMulti"> | ||||
|                         {{ $t("see_all_pages") }} | ||||
|                         {{ trans(SIGNATURES_SEE_ALL_PAGES) }} | ||||
|                     </label> | ||||
|                 </template> | ||||
|             </div> | ||||
|             <div | ||||
|                 v-if="signature.zones.length > 0 && signedState !== 'signed'" | ||||
|                 v-if="signature.zones.length === 1 && signedState !== 'signed'" | ||||
|                 class="col-4 d-xl-none text-center turnSignature p-0" | ||||
|             > | ||||
|                 <button | ||||
|                     :disabled="!hasSignatureZoneSelected" | ||||
|                     class="btn btn-light btn-sm" | ||||
|                     @click="turnSignature(-1)" | ||||
|                     @click="goToSignatureZoneUnique" | ||||
|                 > | ||||
|                     {{ $t("last_zone") }} | ||||
|                 </button> | ||||
|                 <span>|</span> | ||||
|                 <button | ||||
|                     :disabled="isLastSignatureZone" | ||||
|                     class="btn btn-light btn-sm" | ||||
|                     @click="turnSignature(1)" | ||||
|                 > | ||||
|                     {{ $t("next_zone") }} | ||||
|                     {{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }} | ||||
|                 </button> | ||||
|             </div> | ||||
|             <div | ||||
|                 v-if="signature.zones.length > 0 && signedState !== 'signed'" | ||||
|                 class="col-4 d-none d-xl-flex p-0 text-center turnSignature" | ||||
|                 v-if="signature.zones.length > 1 && signedState !== 'signed'" | ||||
|                 class="col-4 d-xl-none text-center turnSignature p-0" | ||||
|             > | ||||
|                 <button | ||||
|                     :disabled="isFirstSignatureZone" | ||||
|                     :disabled="isFirstSignatureZone()" | ||||
|                     class="btn btn-light btn-sm" | ||||
|                     @click="turnSignature(-1)" | ||||
|                 > | ||||
|                     {{ $t("last_sign_zone") }} | ||||
|                     {{ trans(SIGNATURES_LAST_ZONE) }} | ||||
|                 </button> | ||||
|                 <span>|</span> | ||||
|                 <button | ||||
|                     :disabled="isLastSignatureZone" | ||||
|                     :disabled="isLastSignatureZone()" | ||||
|                     class="btn btn-light btn-sm" | ||||
|                     @click="turnSignature(1)" | ||||
|                 > | ||||
|                     {{ $t("next_sign_zone") }} | ||||
|                     {{ trans(SIGNATURES_NEXT_ZONE) }} | ||||
|                 </button> | ||||
|             </div> | ||||
|             <div | ||||
|                 v-if="signature.zones.length === 1 && signedState !== 'signed'" | ||||
|                 class="col-4 d-none d-xl-flex p-0 text-center turnSignature" | ||||
|             > | ||||
|                 <button | ||||
|                     class="btn btn-light btn-sm" | ||||
|                     @click="goToSignatureZoneUnique" | ||||
|                 > | ||||
|                     {{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }} | ||||
|                 </button> | ||||
|             </div> | ||||
|             <div | ||||
|                 v-if="signature.zones.length > 1 && signedState !== 'signed'" | ||||
|                 class="col-4 d-none d-xl-flex p-0 text-center turnSignature" | ||||
|             > | ||||
|                 <button | ||||
|                     :disabled="isFirstSignatureZone()" | ||||
|                     class="btn btn-light btn-sm" | ||||
|                     @click="turnSignature(-1)" | ||||
|                 > | ||||
|                     {{ trans(SIGNATURES_LAST_SIGN_ZONE) }} | ||||
|                 </button> | ||||
|                 <span>|</span> | ||||
|                 <button | ||||
|                     :disabled="isLastSignatureZone()" | ||||
|                     class="btn btn-light btn-sm" | ||||
|                     @click="turnSignature(1)" | ||||
|                 > | ||||
|                     {{ trans(SIGNATURES_NEXT_SIGN_ZONE) }} | ||||
|                 </button> | ||||
|             </div> | ||||
|             <div class="col text-end" v-if="signedState !== 'signed'"> | ||||
| @@ -237,7 +272,7 @@ | ||||
|                     @click="undoSign" | ||||
|                     v-if="signature.zones.length > 1" | ||||
|                 > | ||||
|                     {{ $t("choose_another_signature") }} | ||||
|                     {{ trans(SIGNATURES_CHOOSE_ANOTHER_SIGNATURE) }} | ||||
|                 </button> | ||||
|                 <button | ||||
|                     class="btn btn-misc btn-sm" | ||||
| @@ -245,7 +280,7 @@ | ||||
|                     @click="undoSign" | ||||
|                     v-else | ||||
|                 > | ||||
|                     {{ $t("cancel") }} | ||||
|                     {{ trans(SIGNATURES_CANCEL) }} | ||||
|                 </button> | ||||
|                 <button | ||||
|                     v-if="userSignatureZone === null" | ||||
| @@ -257,13 +292,13 @@ | ||||
|                         active: canvasEvent === 'add', | ||||
|                     }" | ||||
|                     @click="toggleAddZone()" | ||||
|                     :title="$t('add_sign_zone')" | ||||
|                     :title="trans(SIGNATURES_ADD_SIGN_ZONE)" | ||||
|                 > | ||||
|                     <template v-if="canvasEvent !== 'add'"> | ||||
|                         {{ $t("add_zone") }} | ||||
|                         {{ trans(SIGNATURES_ADD_ZONE) }} | ||||
|                     </template> | ||||
|                     <template v-else> | ||||
|                         {{ $t("click_on_document") }} | ||||
|                         {{ trans(SIGNATURES_CLICK_ON_DOCUMENT) }} | ||||
|                         <div | ||||
|                             class="spinner-border spinner-border-sm" | ||||
|                             role="status" | ||||
| @@ -297,10 +332,10 @@ | ||||
|                     v-if="signedState !== 'signed'" | ||||
|                     :href="getReturnPath()" | ||||
|                 > | ||||
|                     {{ $t("cancel") }} | ||||
|                     {{ trans(SIGNATURES_CANCEL) }} | ||||
|                 </a> | ||||
|                 <a class="btn btn-misc" v-else :href="getReturnPath()"> | ||||
|                     {{ $t("return") }} | ||||
|                     {{ trans(SIGNATURES_RETURN) }} | ||||
|                 </a> | ||||
|             </div> | ||||
|             <div class="col text-end" v-if="signedState !== 'signed'"> | ||||
| @@ -309,7 +344,7 @@ | ||||
|                     :disabled="!userSignatureZone" | ||||
|                     @click="sign" | ||||
|                 > | ||||
|                     {{ $t("sign") }} | ||||
|                     {{ trans(SIGNATURES_SIGN) }} | ||||
|                 </button> | ||||
|             </div> | ||||
|             <div class="col-4" v-else></div> | ||||
| @@ -318,7 +353,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref, Ref, computed } from "vue"; | ||||
| import { ref, Ref } from "vue"; | ||||
| import { useToast } from "vue-toast-notification"; | ||||
| import "vue-toast-notification/dist/theme-sugar.css"; | ||||
| import { | ||||
| @@ -329,13 +364,38 @@ import { | ||||
|     SignedState, | ||||
|     ZoomLevel, | ||||
| } from "../../types"; | ||||
| import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; | ||||
| import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; | ||||
| import * as pdfjsLib from "pdfjs-dist"; | ||||
| import { | ||||
|     PDFDocumentProxy, | ||||
|     PDFPageProxy, | ||||
| } from "pdfjs-dist/types/src/display/api"; | ||||
|  | ||||
| import { | ||||
|     SIGNATURES_YES, | ||||
|     SIGNATURES_ARE_YOU_SURE, | ||||
|     SIGNATURES_YOU_ARE_GOING_TO_SIGN, | ||||
|     SIGNATURES_SIGNATURE_CONFIRMATION, | ||||
|     SIGNATURES_SIGN, | ||||
|     SIGNATURES_CHOOSE_ANOTHER_SIGNATURE, | ||||
|     SIGNATURES_CANCEL, | ||||
|     SIGNATURES_LAST_SIGN_ZONE, | ||||
|     SIGNATURES_NEXT_SIGN_ZONE, | ||||
|     SIGNATURES_ADD_SIGN_ZONE, | ||||
|     SIGNATURES_CLICK_ON_DOCUMENT, | ||||
|     SIGNATURES_LAST_ZONE, | ||||
|     SIGNATURES_NEXT_ZONE, | ||||
|     SIGNATURES_ADD_ZONE, | ||||
|     SIGNATURES_ANOTHER_ZONE, | ||||
|     SIGNATURES_ELECTRONIC_SIGNATURE_IN_PROGRESS, | ||||
|     SIGNATURES_LOADING, | ||||
|     SIGNATURES_RETURN, | ||||
|     SIGNATURES_SEE_ALL_PAGES, | ||||
|     SIGNATURES_ALL_PAGES, | ||||
|     SIGNATURES_GO_TO_SIGNATURE_UNIQUE, | ||||
|     trans, | ||||
| } from "translator"; | ||||
|  | ||||
| // @ts-ignore incredible but the console.log is needed | ||||
| import * as PdfWorker from "pdfjs-dist/build/pdf.worker.mjs"; | ||||
| console.log(PdfWorker); | ||||
| @@ -416,19 +476,15 @@ const $toast = useToast(); | ||||
| const signature = window.signature; | ||||
|  | ||||
| const isFirstSignatureZone = () => | ||||
|     userSignatureZone.value?.index ? userSignatureZone.value.index < 1 : false; | ||||
|     userSignatureZone.value?.index != null | ||||
|         ? userSignatureZone.value.index < 1 | ||||
|         : false; | ||||
|  | ||||
| const isLastSignatureZone = () => | ||||
|     userSignatureZone.value?.index | ||||
|         ? userSignatureZone.value.index >= signature.zones.length - 1 | ||||
|         : false; | ||||
|  | ||||
| /** | ||||
|  * Return true if the user has selected a user zone (existing on the doc or created by the user) | ||||
|  */ | ||||
| const hasSignatureZoneSelected = computed<boolean>( | ||||
|     () => userSignatureZone.value !== null, | ||||
| ); | ||||
|  | ||||
| const setZoomLevel = async (zoomLevel: string) => { | ||||
|     zoom.value = Number.parseFloat(zoomLevel); | ||||
|     await resetPages(); | ||||
| @@ -600,6 +656,15 @@ const turnPage = async (upOrDown: number) => { | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const selectZoneInCanvas = (signatureZone: SignatureZone) => { | ||||
|     page.value = signatureZone.PDFPage.index + 1; | ||||
|     const canvas = getCanvas(signatureZone.PDFPage.index + 1); | ||||
|     selectZone(signatureZone, canvas); | ||||
|     canvas.scrollIntoView(); | ||||
| }; | ||||
|  | ||||
| const goToSignatureZoneUnique = () => selectZoneInCanvas(signature.zones[0]); | ||||
|  | ||||
| const turnSignature = async (upOrDown: number) => { | ||||
|     let zoneIndex = userSignatureZone.value?.index ?? -1; | ||||
|     if (zoneIndex < -1) { | ||||
| @@ -612,10 +677,7 @@ const turnSignature = async (upOrDown: number) => { | ||||
|     } | ||||
|     let currentZone = signature.zones[zoneIndex]; | ||||
|     if (currentZone) { | ||||
|         page.value = currentZone.PDFPage.index + 1; | ||||
|         const canvas = getCanvas(currentZone.PDFPage.index + 1); | ||||
|         selectZone(currentZone, canvas); | ||||
|         canvas.scrollIntoView(); | ||||
|         selectZoneInCanvas(currentZone); | ||||
|     } | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -23,6 +23,7 @@ const emit = | ||||
|             { | ||||
|                 stored_object_version: StoredObjectVersionCreated, | ||||
|                 stored_object: StoredObject, | ||||
|                 file_name: string, | ||||
|             }, | ||||
|         ) => void | ||||
|     >(); | ||||
| @@ -114,7 +115,21 @@ const handleFile = async (file: File): Promise<void> => { | ||||
|         persisted: false, | ||||
|     }; | ||||
|  | ||||
|     emit("addDocument", { stored_object, stored_object_version }); | ||||
|     const fileName = file.name; | ||||
|     let file_name = "Nouveau document"; | ||||
|     const file_name_split = fileName.split("."); | ||||
|     if (file_name_split.length > 1) { | ||||
|         const extension = file_name_split | ||||
|             ? file_name_split[file_name_split.length - 1] | ||||
|             : ""; | ||||
|         file_name = fileName.replace(extension, "").slice(0, -1); | ||||
|     } | ||||
|  | ||||
|     emit("addDocument", { | ||||
|         stored_object, | ||||
|         stored_object_version, | ||||
|         file_name: file_name, | ||||
|     }); | ||||
|     uploading.value = false; | ||||
| }; | ||||
| </script> | ||||
|   | ||||
| @@ -20,6 +20,7 @@ const emit = defineEmits<{ | ||||
|         { | ||||
|             stored_object: StoredObject, | ||||
|             stored_object_version: StoredObjectVersion, | ||||
|             file_name: string, | ||||
|         }, | ||||
|     ): void; | ||||
|     (e: "removeDocument"): void; | ||||
| @@ -42,14 +43,16 @@ const buttonState = computed<"add" | "replace">(() => { | ||||
| function onAddDocument({ | ||||
|     stored_object, | ||||
|     stored_object_version, | ||||
|     file_name, | ||||
| }: { | ||||
|     stored_object: StoredObject; | ||||
|     stored_object_version: StoredObjectVersion; | ||||
|     file_name: string; | ||||
| }): void { | ||||
|     const message = | ||||
|         buttonState.value === "add" ? "Document ajouté" : "Document remplacé"; | ||||
|     $toast.success(message); | ||||
|     emit("addDocument", { stored_object_version, stored_object }); | ||||
|     emit("addDocument", { stored_object_version, stored_object, file_name }); | ||||
|     state.showModal = false; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -19,6 +19,7 @@ const emit = defineEmits<{ | ||||
|         { | ||||
|             stored_object: StoredObject, | ||||
|             stored_object_version: StoredObjectVersion, | ||||
|             file_name: string, | ||||
|         }, | ||||
|     ): void; | ||||
|     (e: "removeDocument"): void; | ||||
| @@ -53,11 +54,13 @@ const dav_link_href = computed<string | undefined>(() => { | ||||
| const onAddDocument = ({ | ||||
|     stored_object, | ||||
|     stored_object_version, | ||||
|     file_name, | ||||
| }: { | ||||
|     stored_object: StoredObject; | ||||
|     stored_object_version: StoredObjectVersion; | ||||
|     file_name: string; | ||||
| }): void => { | ||||
|     emit("addDocument", { stored_object, stored_object_version }); | ||||
|     emit("addDocument", { stored_object, stored_object_version, file_name }); | ||||
| }; | ||||
|  | ||||
| const onRemoveDocument = (e: Event): void => { | ||||
|   | ||||
| @@ -53,7 +53,7 @@ const onRestored = ({ | ||||
| <template> | ||||
|     <template v-if="props.versions.length > 0"> | ||||
|         <div class="container"> | ||||
|             <template v-for="v in props.versions"> | ||||
|             <template v-for="v in props.versions" :key="v.id"> | ||||
|                 <history-button-list-item | ||||
|                     :version="v" | ||||
|                     :can-edit="canEdit" | ||||
|   | ||||
| @@ -32,13 +32,17 @@ const onRestore = ({ | ||||
|     emit("restoreVersion", { newVersion }); | ||||
| }; | ||||
|  | ||||
| const isKeptBeforeConversion = computed<boolean>(() => | ||||
|     props.version["point-in-times"].reduce( | ||||
|         (accumulator: boolean, pit: StoredObjectPointInTime) => | ||||
|             accumulator || "keep-before-conversion" === pit.reason, | ||||
|         false, | ||||
|     ), | ||||
| ); | ||||
| const isKeptBeforeConversion = computed<boolean>(() => { | ||||
|     if ("point-in-times" in props.version) { | ||||
|         return props.version["point-in-times"].reduce( | ||||
|             (accumulator: boolean, pit: StoredObjectPointInTime) => | ||||
|                 accumulator || "keep-before-conversion" === pit.reason, | ||||
|             false, | ||||
|         ); | ||||
|     } else { | ||||
|         return false; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const isRestored = computed<boolean>( | ||||
|     () => props.version.version > 0 && null !== props.version["from-restored"], | ||||
| @@ -90,11 +94,11 @@ const classes = computed<{ | ||||
|         <div class="col-12"> | ||||
|             <file-icon :type="version.type"></file-icon> | ||||
|             <span | ||||
|                 ><strong>#{{ version.version + 1 }}</strong></span | ||||
|                 ><strong> #{{ version.version + 1 }} </strong></span | ||||
|             > | ||||
|             <template | ||||
|                 v-if="version.createdBy !== null && version.createdAt !== null" | ||||
|                 ><strong v-if="version.version == 0">Créé par</strong | ||||
|                 ><strong v-if="version.version == 0">créé par</strong | ||||
|                 ><strong v-else>modifié par</strong> | ||||
|                 <span class="badge-user" | ||||
|                     ><UserRenderBoxBadge | ||||
|   | ||||
| @@ -23,7 +23,7 @@ License * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
| {{ encore_entry_link_tags("mod_document_action_buttons_group") }} | ||||
| {% endblock %} {% block content %} | ||||
|  | ||||
| <div class="col-md-10 col-xxl"> | ||||
| <div class="document-list"> | ||||
|     <h1> | ||||
|         {{ 'Documents for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }} | ||||
|     </h1> | ||||
|   | ||||
| @@ -3,54 +3,56 @@ | ||||
| {% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %} | ||||
|  | ||||
| <div class="item-row"> | ||||
|     <div class="item-col" style="width: unset"> | ||||
|         {% if document.object.isPending %} | ||||
|             <div class="badge text-bg-info" data-docgen-is-pending="{{ document.object.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div> | ||||
|         {% elseif document.object.isFailure %} | ||||
|             <div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div> | ||||
|         {% endif %} | ||||
|     <!-- person document or accompanying course document --> | ||||
|     <div class="item-two-col-grid"> | ||||
|         <div class="title"> | ||||
|             {% if document.object.isPending %} | ||||
|                 <div class="badge text-bg-info" data-docgen-is-pending="{{ document.object.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div> | ||||
|             {% elseif document.object.isFailure %} | ||||
|                 <div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div> | ||||
|             {% endif %} | ||||
|  | ||||
|         {% if context == 'person' and accompanyingCourse is defined %} | ||||
|             <div> | ||||
|                     <span class="badge bg-primary"> | ||||
|                         <i class="fa fa-random"></i> {{ accompanyingCourse.id }} | ||||
|                     </span>  | ||||
|             <div class="denomination h2"> | ||||
|                 {{ document.title|chill_print_or_message("No title") }} | ||||
|             </div> | ||||
|         {% elseif context == 'accompanying-period' and person is defined %} | ||||
|             <div> | ||||
|                     <span class="badge bg-primary"> | ||||
|                         {{  'Document from person %name%'|trans({ '%name%': document.person|chill_entity_render_string }) }} | ||||
|                     </span>  | ||||
|             </div> | ||||
|  | ||||
|         {% endif %} | ||||
|         <div class="denomination h2"> | ||||
|             {{ document.title|chill_print_or_message("No title") }} | ||||
|         </div> | ||||
|         {% if document.object.type is not empty %} | ||||
|             <div> | ||||
|                 {{ mm.mimeIcon(document.object.type) }} | ||||
|             </div> | ||||
|         {% endif %} | ||||
|         <div> | ||||
|             <p>{{ document.category.name|localize_translatable_string }}</p> | ||||
|         </div> | ||||
|         {% if document.object.hasTemplate %} | ||||
|             <div> | ||||
|                 <p>{{ document.object.template.name|localize_translatable_string }}</p> | ||||
|             </div> | ||||
|         {% endif %} | ||||
|     </div> | ||||
|  | ||||
|     <div class="item-col"> | ||||
|         <div class="container"> | ||||
|             {% if document.date is not null %} | ||||
|                 <div class="dates row text-end"> | ||||
|                     <span>{{ document.date|format_date('short') }}</span> | ||||
|             {% if document.object.type is not empty %} | ||||
|                 <div> | ||||
|                     {{ mm.mimeIcon(document.object.type) }} | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|             {% if document.category %} | ||||
|                 <div> | ||||
|                     <p>{{ document.category.name|localize_translatable_string }}</p> | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|             {% if document.object.hasTemplate %} | ||||
|                 <div> | ||||
|                     <p>{{ document.object.template.name|localize_translatable_string }}</p> | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|         </div> | ||||
|         {% if document.date is not null %} | ||||
|             <div class="aside"> | ||||
|                 <div class="dates row text-end"> | ||||
|                     <span>{{ document.date|format_date('short') }}</span> | ||||
|                 </div> | ||||
|                 {% if context == 'person' and accompanyingCourse is defined %} | ||||
|                     <div class="text-end"> | ||||
|                         <span class="badge bg-primary"> | ||||
|                             <i class="fa fa-random"></i> {{ accompanyingCourse.id }} | ||||
|                         </span>  | ||||
|                     </div> | ||||
|                 {% elseif context == 'accompanying-period' and person is defined %} | ||||
|                     <div class="text-end"> | ||||
|                         <span class="badge bg-primary"> | ||||
|                              {{ document.person|chill_entity_render_string }} | ||||
|                         </span>  | ||||
|                     </div> | ||||
|                 {% endif %} | ||||
|             </div> | ||||
|         {% endif %} | ||||
|     </div> | ||||
|  | ||||
| </div> | ||||
| {% if document.description is not empty %} | ||||
|     <div class="item-row"> | ||||
|   | ||||
| @@ -62,7 +62,15 @@ final readonly class RemoveOldVersionMessageHandler implements MessageHandlerInt | ||||
|  | ||||
|         $storedObject = $storedObjectVersion->getStoredObject(); | ||||
|  | ||||
|         $this->storedObjectManager->delete($storedObjectVersion); | ||||
|         if ($this->storedObjectManager->exists($storedObjectVersion)) { | ||||
|             $this->storedObjectManager->delete($storedObjectVersion); | ||||
|         } else { | ||||
|             $this->logger->notice( | ||||
|                 self::LOG_PREFIX.'Stored object version does not exists any more.', | ||||
|                 ['storedObjectVersionName' => $storedObjectVersion->getFilename()], | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         // to ensure an immediate deletion | ||||
|         $this->entityManager->remove($storedObjectVersion); | ||||
|  | ||||
|   | ||||
| @@ -44,6 +44,7 @@ class RemoveOldVersionMessageHandlerTest extends TestCase | ||||
|         $entityManager->expects($this->once())->method('clear'); | ||||
|  | ||||
|         $storedObjectManager = $this->createMock(StoredObjectManagerInterface::class); | ||||
|         $storedObjectManager->expects($this->once())->method('exists')->willReturn(true); | ||||
|         $storedObjectManager->expects($this->once())->method('delete')->with($this->identicalTo($version)); | ||||
|  | ||||
|         $handler = new RemoveOldVersionMessageHandler($storedObjectVersionRepository, new NullLogger(), $entityManager, $storedObjectManager, new MockClock()); | ||||
| @@ -51,6 +52,29 @@ class RemoveOldVersionMessageHandlerTest extends TestCase | ||||
|         $handler(new RemoveOldVersionMessage(1)); | ||||
|     } | ||||
|  | ||||
|     public function testInvokeForVersionNotExisting(): void | ||||
|     { | ||||
|         $object = new StoredObject(); | ||||
|         $version = $object->registerVersion(); | ||||
|         $storedObjectVersionRepository = $this->createMock(StoredObjectVersionRepository::class); | ||||
|         $storedObjectVersionRepository->expects($this->once())->method('find') | ||||
|             ->with($this->identicalTo(1)) | ||||
|             ->willReturn($version); | ||||
|  | ||||
|         $entityManager = $this->createMock(EntityManagerInterface::class); | ||||
|         $entityManager->expects($this->once())->method('remove')->with($this->identicalTo($version)); | ||||
|         $entityManager->expects($this->once())->method('flush'); | ||||
|         $entityManager->expects($this->once())->method('clear'); | ||||
|  | ||||
|         $storedObjectManager = $this->createMock(StoredObjectManagerInterface::class); | ||||
|         $storedObjectManager->expects($this->once())->method('exists')->willReturn(false); | ||||
|         $storedObjectManager->expects($this->never())->method('delete')->with($this->identicalTo($version)); | ||||
|  | ||||
|         $handler = new RemoveOldVersionMessageHandler($storedObjectVersionRepository, new NullLogger(), $entityManager, $storedObjectManager, new MockClock()); | ||||
|  | ||||
|         $handler(new RemoveOldVersionMessage(1)); | ||||
|     } | ||||
|  | ||||
|     public function testInvokeWithStoredObjectToDelete(): void | ||||
|     { | ||||
|         $object = new StoredObject(); | ||||
| @@ -123,6 +147,6 @@ class DummyStoredObjectManager implements StoredObjectManagerInterface | ||||
|  | ||||
|     public function exists(StoredObject|StoredObjectVersion $document): bool | ||||
|     { | ||||
|         throw new \RuntimeException(); | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -99,3 +99,30 @@ CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE: Modifier un document | ||||
| entity_display_title: | ||||
|     Document (n°%doc%): "Document (n°%doc%)" | ||||
|     Doc for evaluation (n°%eval%): Document de l'évaluation n°%eval% | ||||
|  | ||||
|  | ||||
| # SIGNATURES | ||||
|  | ||||
| signatures: | ||||
|     yes: Oui | ||||
|     are_you_sure: Êtes-vous sûr·e? | ||||
|     you_are_going_to_sign: Vous allez signer le document | ||||
|     signature_confirmation: Confirmation de la signature | ||||
|     sign: Signer | ||||
|     choose_another_signature: Choisir une autre zone | ||||
|     cancel: Annuler | ||||
|     last_sign_zone: Zone de signature précédente | ||||
|     next_sign_zone: Zone de signature suivante | ||||
|     add_sign_zone: Ajouter une zone de signature | ||||
|     click_on_document: Cliquer sur le document | ||||
|     last_zone: Zone précédente | ||||
|     next_zone: Zone suivante | ||||
|     add_zone: Ajouter une zone | ||||
|     another_zone: Autre zone | ||||
|     electronic_signature_in_progress: Signature électronique en cours... | ||||
|     loading: Chargement... | ||||
|     remove_sign_zone: Enlever la zone | ||||
|     return: Retour | ||||
|     see_all_pages: Voir toutes les pages | ||||
|     all_pages: Toutes les pages | ||||
|     go_to_signature_unique: Aller à la zone de signature | ||||
|   | ||||
| @@ -33,6 +33,8 @@ | ||||
|  | ||||
| @import './scss/hover.scss'; | ||||
|  | ||||
| @import './scss/comment-editor.scss'; | ||||
|  | ||||
| /* | ||||
|  *    BASE LAYOUT POSITION | ||||
|  */ | ||||
|   | ||||
| @@ -0,0 +1,39 @@ | ||||
| .comment-container { | ||||
|     margin-top: 1.5rem; | ||||
| } | ||||
|  | ||||
| .toggle-button { | ||||
|     background-color: white; | ||||
|     font-size: .8rem; | ||||
|     text-decoration: none; | ||||
|     position: absolute; | ||||
|     bottom: -10px; | ||||
|     left: 20px; | ||||
|     padding: 2px 6px; | ||||
|     cursor: pointer; | ||||
|     z-index: 10; | ||||
|     transition: left 0.1s ease-in-out; | ||||
| } | ||||
|  | ||||
| .rich-editor-active .toggle-button { | ||||
|     bottom: 0; | ||||
| } | ||||
|  | ||||
| .editor-wrapper textarea { | ||||
|     resize: vertical; | ||||
|     min-height: 100px; | ||||
| } | ||||
|  | ||||
| .editor-container { | ||||
|     position: relative; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
| } | ||||
|  | ||||
| .editor-wrapper { | ||||
|     position: relative; | ||||
| } | ||||
|  | ||||
| .hidden-textarea { | ||||
|     display: none; | ||||
| } | ||||
| @@ -25,7 +25,34 @@ div.flex-table { | ||||
|             div.item-col:last-child { | ||||
|                 display: flex; | ||||
|             } | ||||
|  | ||||
|             div.item-two-col-grid { | ||||
|                 display: grid; | ||||
|                 width: 100%; | ||||
|                 justify-content: stretch; | ||||
|  | ||||
|                 @include media-breakpoint-up(lg) { | ||||
|                     grid-template-areas: | ||||
|                         "title aside"; | ||||
|                     grid-template-columns: 1fr minmax(8rem, 1fr); | ||||
|                     column-gap: 0.5em; | ||||
|                 } | ||||
|                 @include media-breakpoint-down(lg) { | ||||
|                     grid-template-areas: | ||||
|                         "aside" | ||||
|                         "title"; | ||||
|                 } | ||||
|  | ||||
|                 & > div.title { | ||||
|                     grid-area: title; | ||||
|                 } | ||||
|  | ||||
|                 & > div.aside { | ||||
|                     grid-area: aside; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     h2, h3, h4, dl, p { | ||||
|   | ||||
| @@ -8,10 +8,10 @@ import { | ||||
|     Heading, | ||||
|     Link, | ||||
|     List, | ||||
| } from "ckeditor5"; | ||||
| import coreTranslations from "ckeditor5/translations/fr.js"; | ||||
| } from 'ckeditor5'; | ||||
| import coreTranslations from 'ckeditor5/translations/fr.js'; | ||||
|  | ||||
| import "ckeditor5/ckeditor5.css"; | ||||
| import 'ckeditor5/ckeditor5.css'; | ||||
|  | ||||
| import "./index.scss"; | ||||
|  | ||||
| @@ -41,6 +41,8 @@ export default { | ||||
|             "redo", | ||||
|         ], | ||||
|     }, | ||||
|     translations: [coreTranslations], | ||||
|     translations: [ | ||||
|         coreTranslations | ||||
|     ], | ||||
|     licenseKey: "GPL", | ||||
| }; | ||||
| } ; | ||||
|   | ||||
| @@ -1,12 +1,23 @@ | ||||
| import config from "./editor_config"; | ||||
| import { ClassicEditor } from "ckeditor5"; | ||||
| import App from "../../vuejs/CommentEditor/App.vue" | ||||
| import { createApp, reactive } from "vue"; | ||||
|  | ||||
| const ckeditorFields: NodeListOf<HTMLTextAreaElement> = | ||||
|     document.querySelectorAll("textarea[ckeditor]"); | ||||
| ckeditorFields.forEach((field: HTMLTextAreaElement): void => { | ||||
|     ClassicEditor.create(field, config).catch((error) => { | ||||
|         console.error(error.stack); | ||||
|         throw error; | ||||
|     }); | ||||
|     document.querySelectorAll("[id^='comment-app']"); | ||||
|  | ||||
| const globalState = reactive({ | ||||
|     isSimple: localStorage.getItem('editorMode') === 'simple' | ||||
| }); | ||||
| window.addEventListener('storage', () => { | ||||
|     globalState.isSimple = localStorage.getItem('editorMode') === 'simple'; | ||||
| }); | ||||
|  | ||||
| ckeditorFields.forEach((field: HTMLTextAreaElement): void => { | ||||
|     const app = createApp(App,{ | ||||
|         fieldName: field.dataset.fieldName, | ||||
|         template: `<app></app>` | ||||
|     }); | ||||
|  | ||||
|     app.provide('globalState', globalState) | ||||
|         .component("app", App) | ||||
|         .mount(field); | ||||
| }); | ||||
| //Fields.push.apply(Fields, document.querySelectorAll('.cf-fields textarea')); | ||||
|   | ||||
| @@ -10,6 +10,10 @@ let appsPerInput = new Map(); | ||||
|  | ||||
| function loadDynamicPicker(element) { | ||||
|   let apps = element.querySelectorAll('[data-module="pick-dynamic"]'); | ||||
|   let suggested; | ||||
|   let as_id; | ||||
|   let submit_on_adding_new_entity; | ||||
|   let label; | ||||
|  | ||||
|   apps.forEach(function (el) { | ||||
|     const isMultiple = parseInt(el.dataset.multiple) === 1, | ||||
|   | ||||
| @@ -1,45 +0,0 @@ | ||||
| import { createApp } from "vue"; | ||||
| import OpenWopiLink from "ChillMainAssets/vuejs/_components/OpenWopiLink"; | ||||
| import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n"; | ||||
|  | ||||
| const i18n = _createI18n({}); | ||||
|  | ||||
| //TODO move to chillDocStore or ChillWopi | ||||
|  | ||||
| /* | ||||
|  | ||||
| tags to load module: | ||||
|  | ||||
| <span data-module="wopi-link" | ||||
|     data-wopi-url="{{ path('chill_wopi_file_edit', {'fileId': document.uuid}) }}" | ||||
|     data-doc-type="{{ document.type|e('html_attr') }}" | ||||
|     data-options="{{ options|json_encode }}" | ||||
|         ></span> | ||||
|  | ||||
| */ | ||||
|  | ||||
| window.addEventListener("DOMContentLoaded", function (e) { | ||||
|   document | ||||
|     .querySelectorAll('span[data-module="wopi-link"]') | ||||
|     .forEach(function (el) { | ||||
|       createApp({ | ||||
|         template: | ||||
|           '<open-wopi-link :wopiUrl="wopiUrl" :type="type" :options="options"></open-wopi-link>', | ||||
|         components: { | ||||
|           OpenWopiLink, | ||||
|         }, | ||||
|         data() { | ||||
|           return { | ||||
|             wopiUrl: el.dataset.wopiUrl, | ||||
|             type: el.dataset.docType, | ||||
|             options: | ||||
|               el.dataset.options !== "null" | ||||
|                 ? JSON.parse(el.dataset.options) | ||||
|                 : {}, | ||||
|           }; | ||||
|         }, | ||||
|       }) | ||||
|         .use(i18n) | ||||
|         .mount(el); | ||||
|     }); | ||||
| }); | ||||
| @@ -0,0 +1,36 @@ | ||||
| <template> | ||||
|     <div> | ||||
|         <div> | ||||
|             <comment-editor | ||||
|                 :isSimple="globalState.isSimple" | ||||
|                 :fieldName="fieldName" | ||||
|                 @toggle="toggleEditorMode" | ||||
|                 ></comment-editor> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { defineComponent, inject } from 'vue'; | ||||
| import CommentEditor from "../CommentEditor/component/CommentEditor.vue"; | ||||
|  | ||||
| export default defineComponent({ | ||||
|     name: "App", | ||||
|     components: { CommentEditor }, | ||||
|     props: { | ||||
|         fieldName: String | ||||
|     }, | ||||
|     setup() { | ||||
|         const globalState = inject('globalState'); | ||||
|         const toggleEditorMode = () => { | ||||
|             globalState.isSimple = !globalState.isSimple; | ||||
|             localStorage.setItem('editorMode', globalState.isSimple ? 'simple' : 'rich'); | ||||
|         }; | ||||
|  | ||||
|         return { | ||||
|             globalState, | ||||
|             toggleEditorMode, | ||||
|         }; | ||||
|     } | ||||
| }); | ||||
| </script> | ||||
| @@ -0,0 +1,58 @@ | ||||
| <template> | ||||
|     <div :class="{'editor-container': true, 'rich-editor-active': !isSimple}"> | ||||
|         <div v-if="!isSimple" class="editor-wrapper"> | ||||
|             <ckeditor | ||||
|                 :name="fieldName" | ||||
|                 :editor="classicEditor" | ||||
|                 :config="editorConfig" | ||||
|                 v-model.lazy="content" | ||||
|                 tag-name="textarea" | ||||
|             /> | ||||
|         </div> | ||||
|         <div v-else class="editor-wrapper"> | ||||
|             <textarea | ||||
|                 v-model.lazy="content" | ||||
|                 :name="fieldName" | ||||
|                 class="form-control" | ||||
|             ></textarea> | ||||
|         </div> | ||||
|         <a @click="toggleSimpleEditor" class="toggle-button">{{ isSimple ? "rich" : "simple" }}</a> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent, ref, computed, toRefs } from 'vue'; | ||||
| import { Ckeditor } from "@ckeditor/ckeditor5-vue"; | ||||
| import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config"; | ||||
| import { ClassicEditor } from "ckeditor5"; | ||||
|  | ||||
| export default defineComponent({ | ||||
|     name: "CommentEditor", | ||||
|     components: { | ||||
|         ckeditor: Ckeditor, | ||||
|     }, | ||||
|     props: { | ||||
|         type: String, | ||||
|         isSimple: Boolean, | ||||
|         fieldName: String, | ||||
|     }, | ||||
|     setup(props, { emit }) { | ||||
|         const { isSimple } = toRefs(props); | ||||
|         const content = ref(""); | ||||
|         const classicEditor = ClassicEditor; | ||||
|         const editorConfig = classicEditorConfig; | ||||
|  | ||||
|         const toggleSimpleEditor = () => { | ||||
|             emit("toggle"); | ||||
|         }; | ||||
|  | ||||
|         return { | ||||
|             isSimple, | ||||
|             content, | ||||
|             classicEditor, | ||||
|             editorConfig, | ||||
|             toggleSimpleEditor, | ||||
|         }; | ||||
|     } | ||||
| }); | ||||
| </script> | ||||
| @@ -0,0 +1,14 @@ | ||||
| import {personMessages} from "ChillPersonAssets/vuejs/_js/i18n"; | ||||
| import {calendarUserSelectorMessages} from "ChillCalendarAssets/vuejs/_components/CalendarUserSelector/js/i18n"; | ||||
| import {activityMessages} from "ChillActivityAssets/vuejs/Activity/i18n"; | ||||
|  | ||||
| const appMessages = { | ||||
|   fr: { | ||||
|     mode: { | ||||
|       simple: "Editeur simple", | ||||
|       rich: "Editeur riche" | ||||
|     } | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export { appMessages }; | ||||
| @@ -26,9 +26,9 @@ | ||||
|                 trans(THIRDPARTY_CONTACT_OF) | ||||
|             }}</span> | ||||
|             <span v-else-if="props.entity.kind === 'company'">{{ | ||||
|                 trans(THIRDPARTY_A_CONTACT) | ||||
|                 trans(THIRDPARTY_A_COMPANY) | ||||
|             }}</span> | ||||
|             <span v-else>{{ $t("thirdparty.contact") }}</span> | ||||
|             <span v-else>{{ trans(THIRDPARTY_A_CONTACT) }}</span> | ||||
|         </template> | ||||
|     </span> | ||||
|  | ||||
| @@ -54,6 +54,7 @@ import { | ||||
|     ACCEPTED_USERS, | ||||
|     THIRDPARTY_A_CONTACT, | ||||
|     THIRDPARTY_CONTACT_OF, | ||||
|     THIRDPARTY_A_COMPANY, | ||||
|     PERSON, | ||||
|     THIRDPARTY, | ||||
| } from "translator"; | ||||
|   | ||||
| @@ -1,214 +0,0 @@ | ||||
| <template> | ||||
|     <a | ||||
|         v-if="isOpenDocument" | ||||
|         class="btn" | ||||
|         :class="[ | ||||
|             isChangeIcon ? 'change-icon' : '', | ||||
|             isChangeClass ? options.changeClass : 'btn-wopilink', | ||||
|         ]" | ||||
|         @click="openModal" | ||||
|     > | ||||
|         <i v-if="isChangeIcon" class="fa me-2" :class="options.changeIcon"></i> | ||||
|  | ||||
|         <span v-if="!noText"> | ||||
|             {{ trans(WOPI_ONLINE_EDIT_DOCUMENT) }} | ||||
|         </span> | ||||
|     </a> | ||||
|  | ||||
|     <teleport to="body"> | ||||
|         <div class="wopi-frame" v-if="isOpenDocument"> | ||||
|             <modal | ||||
|                 v-if="modal.showModal" | ||||
|                 :modalDialogClass="modal.modalDialogClass" | ||||
|                 :hideFooter="true" | ||||
|                 @close="modal.showModal = false" | ||||
|             > | ||||
|                 <template #header> | ||||
|                     <img class="logo" :src="logo" height="45" /> | ||||
|                     <span class="ms-auto me-3"> | ||||
|                         <span v-if="options.title">{{ options.title }}</span> | ||||
|                     </span> | ||||
|                 </template> | ||||
|  | ||||
|                 <template #body> | ||||
|                     <div v-if="loading" class="loading"> | ||||
|                         <i | ||||
|                             class="fa fa-circle-o-notch fa-spin fa-3x" | ||||
|                             :title="trans(WOPI_LOADING)" | ||||
|                         ></i> | ||||
|                     </div> | ||||
|                     <iframe :src="this.wopiUrl" @load="loaded"></iframe> | ||||
|                 </template> | ||||
|             </modal> | ||||
|         </div> | ||||
|         <div v-else> | ||||
|             <Modal | ||||
|                 v-if="modal.showModal" | ||||
|                 modalDialogClass="modal-sm" | ||||
|                 @close="modal.showModal = false" | ||||
|             > | ||||
|                 <template v-slot:header> | ||||
|                     <h3>{{ trans(WOPI_INVALID_TITLE) }}</h3> | ||||
|                 </template> | ||||
|                 <template v-slot:body> | ||||
|                     <div class="alert alert-warning"> | ||||
|                         {{ trans(WOPI_ONLINE_EDIT_DOCUMENT) }} | ||||
|                     </div> | ||||
|                 </template> | ||||
|             </Modal> | ||||
|         </div> | ||||
|     </teleport> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { ref, computed } from "vue"; | ||||
| import { | ||||
|     trans, | ||||
|     WOPI_ONLINE_EDIT_DOCUMENT, | ||||
|     WOPI_INVALID_TITLE, | ||||
|     WOPI_LOADING, | ||||
| } from "translator"; | ||||
| import Modal from "ChillMainAssets/vuejs/_components/Modal"; | ||||
| import logo from "ChillMainAssets/chill/img/logo-chill-sans-slogan_white.png"; | ||||
|  | ||||
| // Props | ||||
| const props = defineProps({ | ||||
|     wopiUrl: { | ||||
|         type: String, | ||||
|         required: true, | ||||
|     }, | ||||
|     type: { | ||||
|         type: String, | ||||
|         required: true, | ||||
|     }, | ||||
|     options: { | ||||
|         type: Object, | ||||
|         required: false, | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| // data | ||||
| const modal = ref({ | ||||
|     showModal: false, | ||||
|     modalDialogClass: "modal-fullscreen", | ||||
| }); | ||||
| const loading = ref(false); | ||||
|  | ||||
| // MIME types | ||||
| const mime = [ | ||||
|     // TODO temporary hardcoded. to be replaced by twig extension or a collabora server query | ||||
|     "application/clarisworks", | ||||
|     "application/coreldraw", | ||||
|     "application/macwriteii", | ||||
|     "application/msword", | ||||
|     "application/pdf", | ||||
|     "application/vnd.lotus-1-2-3", | ||||
|     "application/vnd.ms-excel", | ||||
|     "application/vnd.ms-excel.sheet.binary.macroEnabled.12", | ||||
|     "application/vnd.ms-excel.sheet.macroEnabled.12", | ||||
|     "application/vnd.ms-excel.template.macroEnabled.12", | ||||
|     "application/vnd.ms-powerpoint", | ||||
|     "application/vnd.ms-powerpoint.presentation.macroEnabled.12", | ||||
|     "application/vnd.ms-powerpoint.template.macroEnabled.12", | ||||
|     "application/vnd.ms-visio.drawing", | ||||
|     "application/vnd.ms-word.document.macroEnabled.12", | ||||
|     "application/vnd.ms-word.template.macroEnabled.12", | ||||
|     "application/vnd.ms-works", | ||||
|     "application/vnd.oasis.opendocument.chart", | ||||
|     "application/vnd.oasis.opendocument.formula", | ||||
|     "application/vnd.oasis.opendocument.graphics", | ||||
|     "application/vnd.oasis.opendocument.graphics-flat-xml", | ||||
|     "application/vnd.oasis.opendocument.graphics-template", | ||||
|     "application/vnd.oasis.opendocument.presentation", | ||||
|     "application/vnd.oasis.opendocument.presentation-flat-xml", | ||||
|     "application/vnd.oasis.opendocument.presentation-template", | ||||
|     "application/vnd.oasis.opendocument.spreadsheet", | ||||
|     "application/vnd.oasis.opendocument.spreadsheet-flat-xml", | ||||
|     "application/vnd.oasis.opendocument.spreadsheet-template", | ||||
|     "application/vnd.oasis.opendocument.text", | ||||
|     "application/vnd.oasis.opendocument.text-flat-xml", | ||||
|     "application/vnd.oasis.opendocument.text-master", | ||||
|     "application/vnd.oasis.opendocument.text-master-template", | ||||
|     "application/vnd.oasis.opendocument.text-template", | ||||
|     "application/vnd.oasis.opendocument.text-web", | ||||
|     "application/vnd.openxmlformats-officedocument.presentationml.presentation", | ||||
|     "application/vnd.openxmlformats-officedocument.presentationml.slideshow", | ||||
|     "application/vnd.openxmlformats-officedocument.presentationml.template", | ||||
|     "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", | ||||
|     "application/vnd.openxmlformats-officedocument.spreadsheetml.template", | ||||
|     "application/vnd.openxmlformats-officedocument.wordprocessingml.document", | ||||
|     "application/vnd.openxmlformats-officedocument.wordprocessingml.template", | ||||
|     "application/vnd.sun.xml.calc", | ||||
|     "application/vnd.sun.xml.calc.template", | ||||
|     "application/vnd.sun.xml.chart", | ||||
|     "application/vnd.sun.xml.draw", | ||||
|     "application/vnd.sun.xml.draw.template", | ||||
|     "application/vnd.sun.xml.impress", | ||||
|     "application/vnd.sun.xml.impress.template", | ||||
|     "application/vnd.sun.xml.math", | ||||
|     "application/vnd.sun.xml.writer", | ||||
|     "application/vnd.sun.xml.writer.global", | ||||
|     "application/vnd.sun.xml.writer.template", | ||||
|     "application/vnd.visio", | ||||
|     "application/vnd.visio2013", | ||||
|     "application/vnd.wordperfect", | ||||
|     "application/x-abiword", | ||||
|     "application/x-aportisdoc", | ||||
|     "application/x-dbase", | ||||
|     "application/x-dif-document", | ||||
|     "application/x-fictionbook+xml", | ||||
|     "application/x-gnumeric", | ||||
|     "application/x-hwp", | ||||
|     "application/x-iwork-keynote-sffkey", | ||||
|     "application/x-iwork-numbers-sffnumbers", | ||||
|     "application/x-iwork-pages-sffpages", | ||||
|     "application/x-mspublisher", | ||||
|     "application/x-mswrite", | ||||
|     "application/x-pagemaker", | ||||
|     "application/x-sony-bbeb", | ||||
|     "application/x-t602", | ||||
| ]; | ||||
|  | ||||
| // Computed | ||||
| const isOpenDocument = computed(() => mime.includes(props.type)); | ||||
|  | ||||
| const noText = computed(() => props.options?.noText === true); | ||||
|  | ||||
| const isChangeIcon = computed(() => !!props.options?.changeIcon); | ||||
|  | ||||
| const isChangeClass = computed(() => !!props.options?.changeClass); | ||||
|  | ||||
| // Methods | ||||
| const openModal = () => { | ||||
|     loading.value = true; | ||||
|     modal.value.showModal = true; | ||||
| }; | ||||
|  | ||||
| const loaded = () => { | ||||
|     loading.value = false; | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style lang="scss"> | ||||
| div.wopi-frame { | ||||
|     div.modal-header { | ||||
|         border-bottom: 0; | ||||
|         background-color: var(--bs-primary); | ||||
|         color: white; | ||||
|     } | ||||
|     div.modal-body { | ||||
|         padding: 0; | ||||
|         overflow-y: unset !important; | ||||
|         iframe { | ||||
|             height: 100%; | ||||
|             width: 100%; | ||||
|         } | ||||
|         div.loading { | ||||
|             position: absolute; | ||||
|             color: var(--bs-chill-gray); | ||||
|             top: calc(50% - 30px); | ||||
|             left: calc(50% - 30px); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </style> | ||||
| @@ -54,6 +54,11 @@ const messages = { | ||||
|             residential_address: "Adresse de résidence", | ||||
|             located_at: "réside chez", | ||||
|         }, | ||||
|         comment: { | ||||
|             label: "Commentaire", | ||||
|             editor_simple: "Simple", | ||||
|             editor_rich: "Riche" | ||||
|         } | ||||
|     }, | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -136,6 +136,59 @@ | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| <h2>Fix the title in the flex table</h2> | ||||
|  | ||||
| <p>This will fix the layout of the row, with a "title" element, and an aside element. Using <code>css grid</code>, this is quite safe and won't overflow</p> | ||||
|  | ||||
| <xmp> | ||||
|     <div class="flex-table"> | ||||
|         <div class="item-bloc"> | ||||
|             <div class="item-row"> | ||||
|                 <div class="item-two-col-grid"> | ||||
|                     <div class="title">This is my title</div> | ||||
|                     <div class="aside">Aside value</div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="item-bloc"> | ||||
|             <div class="item-row"> | ||||
|                 <div class="item-two-col-grid"> | ||||
|                     <div class="title"> | ||||
|                         <div><h3>This is my title, which can be very long and take a lot of place. But it is wrapped successfully, and won't disturb the placement of the aside block</h3></div> | ||||
|                         <div>This is a second line</div> | ||||
|                     </div> | ||||
|                     <div class="aside">Aside value</div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </xmp> | ||||
|  | ||||
|     <p>will render:</p> | ||||
|  | ||||
|     <div class="flex-table"> | ||||
|         <div class="item-bloc"> | ||||
|             <div class="item-row"> | ||||
|                 <div class="item-two-col-grid"> | ||||
|                     <div class="title">This is my title</div> | ||||
|                     <div class="aside">Aside value</div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="item-bloc"> | ||||
|             <div class="item-row"> | ||||
|                 <div class="item-two-col-grid"> | ||||
|                     <div class="title"> | ||||
|                         <div><h3>This is my title, which can be very long and take a lot of place. But it is wrapped successfully, and won't disturb the placement of the aside block</h3></div> | ||||
|                         <div>This is a second line</div> | ||||
|                     </div> | ||||
|                     <div class="aside">Aside value</div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|  | ||||
| <h2>Wrap-list</h2> | ||||
| <p>Une liste inline qui s'aligne, puis glisse sous son titre.</p> | ||||
| <div class="wrap-list debug"> | ||||
| @@ -392,4 +445,12 @@ Toutes les classes btn-* de bootstrap sont fonctionnelles | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| <div class="row"> | ||||
|     <h1>Badges</h1> | ||||
|  | ||||
|     <span class="badge-accompanying-work-type-simple">Action d'accompagnement</span> | ||||
|     <span class="badge-activity-type-simple">Type d'échange</span> | ||||
|     <span class="badge-calendar-simple">Rendez-vous</span> | ||||
| </div> | ||||
|  | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -214,7 +214,9 @@ | ||||
|  | ||||
| {% block private_comment_widget %} | ||||
|     {% for entry in form %} | ||||
|         {{ form_widget(entry) }} | ||||
|         <div id="comment-app-{{ form.vars.id }}" data-field-name="{{ form.vars.full_name }}"> | ||||
|             {{ form_widget(entry, { attr: { ckeditor: 'true' } }) }} | ||||
|         </div> | ||||
|     {% endfor %} | ||||
| {% endblock %} | ||||
|  | ||||
| @@ -224,7 +226,9 @@ | ||||
|  | ||||
| {% block comment_widget %} | ||||
|     {% for entry in form %} | ||||
|         {{ form_widget(entry) }} | ||||
|         <div id="comment-app-{{ form.vars.id }}" data-field-name="{{ form.vars.full_name }}"> | ||||
|             {{ form_widget(entry, { attr: { ckeditor: 'true' } }) }} | ||||
|         </div> | ||||
|     {% endfor %} | ||||
| {% endblock comment_widget %} | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,6 @@ | ||||
|     {{ encore_entry_script_tags('mod_pickentity_type') }} | ||||
|     {{ encore_entry_script_tags('mod_entity_workflow_subscribe') }} | ||||
|     {{ encore_entry_script_tags('page_workflow_show') }} | ||||
|     {{ encore_entry_script_tags('mod_wopi_link') }} | ||||
|     {{ encore_entry_script_tags('mod_document_action_buttons_group') }} | ||||
|     {{ encore_entry_script_tags('mod_workflow_attachment') }} | ||||
| {% endblock %} | ||||
| @@ -19,7 +18,6 @@ | ||||
|     {{ encore_entry_link_tags('mod_pickentity_type') }} | ||||
|     {{ encore_entry_link_tags('mod_entity_workflow_subscribe') }} | ||||
|     {{ encore_entry_link_tags('page_workflow_show') }} | ||||
|     {{ encore_entry_link_tags('mod_wopi_link') }} | ||||
|     {{ encore_entry_link_tags('mod_document_action_buttons_group') }} | ||||
|     {{ encore_entry_link_tags('mod_workflow_attachment') }} | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -25,6 +25,8 @@ use Symfony\Component\Workflow\Transition; | ||||
| #[AsMessageHandler] | ||||
| final readonly class CancelStaleWorkflowHandler | ||||
| { | ||||
|     private const LOG_PREFIX = '[CancelStaleWorkflowHandler] '; | ||||
|  | ||||
|     public function __construct( | ||||
|         private EntityWorkflowRepository $workflowRepository, | ||||
|         private Registry $registry, | ||||
| @@ -40,13 +42,13 @@ final readonly class CancelStaleWorkflowHandler | ||||
|  | ||||
|         $workflow = $this->workflowRepository->find($message->getWorkflowId()); | ||||
|         if (null === $workflow) { | ||||
|             $this->logger->alert('Workflow was not found!', [$workflowId]); | ||||
|             $this->logger->alert(self::LOG_PREFIX.'Workflow was not found!', ['entityWorkflowId' => $workflowId]); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (false === $workflow->isStaledAt($olderThanDate)) { | ||||
|             $this->logger->alert('Workflow has transitioned in the meantime.', [$workflowId]); | ||||
|             $this->logger->alert(self::LOG_PREFIX.'Workflow has transitioned in the meantime.', ['entityWorkflowId' => $workflowId]); | ||||
|  | ||||
|             throw new UnrecoverableMessageHandlingException('the workflow is not staled any more'); | ||||
|         } | ||||
| @@ -67,14 +69,14 @@ final readonly class CancelStaleWorkflowHandler | ||||
|                     'transitionAt' => $this->clock->now(), | ||||
|                     'transition' => $transition->getName(), | ||||
|                 ]); | ||||
|                 $this->logger->info('EntityWorkflow has been cancelled automatically.', [$workflowId]); | ||||
|                 $this->logger->info(self::LOG_PREFIX.'EntityWorkflow has been cancelled automatically.', ['entityWorkflowId' => $workflowId]); | ||||
|                 $transitionApplied = true; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (!$transitionApplied) { | ||||
|             $this->logger->error('No valid transition found for EntityWorkflow.', [$workflowId]); | ||||
|             $this->logger->error(self::LOG_PREFIX.'No valid transition found for EntityWorkflow.', ['entityWorkflowId' => $workflowId]); | ||||
|             throw new UnrecoverableMessageHandlingException(sprintf('No valid transition found for EntityWorkflow %d.', $workflowId)); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -86,10 +86,6 @@ module.exports = function (encore, entries) { | ||||
|     "mod_entity_workflow_pick", | ||||
|     __dirname + "/Resources/public/module/entity-workflow-pick/index.js", | ||||
|   ); | ||||
|   encore.addEntry( | ||||
|     "mod_wopi_link", | ||||
|     __dirname + "/Resources/public/module/wopi-link/index.js", | ||||
|   ); | ||||
|   encore.addEntry( | ||||
|     "mod_pick_postal_code", | ||||
|     __dirname + "/Resources/public/module/pick-postal-code/index.js", | ||||
|   | ||||
| @@ -59,6 +59,7 @@ user_group: | ||||
|     inactive: Inactif | ||||
|     with_users: Membres | ||||
|     no_users: Aucun utilisateur associé | ||||
|     no_user_groups: Aucune groupe d'utilisateurs | ||||
|     no_admin_users: Aucun administrateur | ||||
|     Label: Nom du groupe | ||||
|     BackgroundColor: Couleur de fond du badge | ||||
| @@ -112,6 +113,8 @@ Any comment: Aucun commentaire | ||||
| # comment embeddable | ||||
| No comment associated: Aucun commentaire | ||||
| private comment: Notes privées | ||||
| comment_public: Note | ||||
| comment_private: Note privée | ||||
|  | ||||
| #pagination | ||||
| Previous: Précédent | ||||
| @@ -739,6 +742,7 @@ export: | ||||
|         id: Identifiant de l'action | ||||
|         social_issue_id: Identifiant de la problématique sociale | ||||
|         social_issue: Problématique sociale | ||||
|         desactivation_date: Date de désactivation | ||||
|         social_issue_ordering: Ordre de la problématique sociale | ||||
|         action_label: Action d'accompagnement | ||||
|         action_ordering: Ordre | ||||
|   | ||||
| @@ -26,7 +26,7 @@ readonly class AccompanyingPeriodStepChangeCronjob implements CronJobInterface | ||||
|     { | ||||
|         $now = $this->clock->now(); | ||||
|  | ||||
|         if (null !== $cronJobExecution && $now->sub(new \DateInterval('P1D')) < $cronJobExecution->getLastStart()) { | ||||
|         if (null !== $cronJobExecution && $now->sub(new \DateInterval('PT23H45M')) < $cronJobExecution->getLastStart()) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -204,20 +204,24 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle | ||||
|             ['date' => 'DESC', 'id' => 'DESC'], | ||||
|         ); | ||||
|  | ||||
|         $activities = \array_slice($activities, 0, 3); | ||||
|  | ||||
|         $works = $this->workRepository->findByAccompanyingPeriod( | ||||
|             $accompanyingCourse, | ||||
|             ['startDate' => 'DESC', 'endDate' => 'DESC'], | ||||
|             3 | ||||
|         ); | ||||
|  | ||||
|         $counters = [ | ||||
|             'activities' => count($activities), | ||||
|             'openWorks' => count($accompanyingCourse->getOpenWorks()), | ||||
|             'works' => count($works), | ||||
|         ]; | ||||
|  | ||||
|         return $this->render('@ChillPerson/AccompanyingCourse/index.html.twig', [ | ||||
|             'accompanyingCourse' => $accompanyingCourse, | ||||
|             'withoutHousehold' => $withoutHousehold, | ||||
|             'participationsByHousehold' => $accompanyingCourse->actualParticipationsByHousehold(), | ||||
|             'works' => $works, | ||||
|             'activities' => $activities, | ||||
|             'works' => \array_slice($works, 0, 3), | ||||
|             'activities' => \array_slice($activities, 0, 3), | ||||
|             'counters' => $counters, | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -511,6 +511,14 @@ class AccompanyingPeriod implements | ||||
|         return $this->getParticipationsContainsPerson($person)->count() > 0; | ||||
|     } | ||||
|  | ||||
|     public function getOpenWorks(): Collection | ||||
|     { | ||||
|         return $this->getWorks()->filter( | ||||
|             static fn (AccompanyingPeriodWork $work): bool => null === $work->getEndDate() | ||||
|                     or $work->getEndDate() > new \DateTimeImmutable('today') | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Open a new participation for a person. | ||||
|      */ | ||||
|   | ||||
| @@ -58,13 +58,11 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true, options: ['default' => null])] | ||||
|     private ?int $numberOfChildren = null; | ||||
|  | ||||
|     #[Assert\NotNull] | ||||
|     #[Assert\GreaterThanOrEqual(0, groups: ['Default', 'household_composition'])] | ||||
|     #[Serializer\Groups(['docgen:read'])] | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true, options: ['default' => null])] | ||||
|     private ?int $numberOfDependents = null; | ||||
|  | ||||
|     #[Assert\NotNull] | ||||
|     #[Assert\GreaterThanOrEqual(0, groups: ['Default', 'household_composition'])] | ||||
|     #[Serializer\Groups(['docgen:read'])] | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true, options: ['default' => null])] | ||||
|   | ||||
| @@ -22,7 +22,7 @@ use Doctrine\ORM\Mapping as ORM; | ||||
| class MaritalStatus | ||||
| { | ||||
|     #[ORM\Id] | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 7)] | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 15)] | ||||
|     private ?string $id; | ||||
|  | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)] | ||||
|   | ||||
| @@ -71,7 +71,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface | ||||
|                     ->setExtras(['order' => 30]); | ||||
|              */ | ||||
|  | ||||
|             $menu->addChild($this->translator->trans('Accompanying Course Comment'), [ | ||||
|             $menu->addChild($this->translator->trans('Accompanying Course Comments'), [ | ||||
|                 'route' => 'chill_person_accompanying_period_comment_list', | ||||
|                 'routeParameters' => [ | ||||
|                     'accompanying_period_id' => $period->getId(), | ||||
| @@ -80,12 +80,15 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface | ||||
|         } | ||||
|  | ||||
|         if ($this->security->isGranted(AccompanyingPeriodWorkVoter::SEE, $period)) { | ||||
|             $menu->addChild($this->translator->trans('Accompanying Course Action'), [ | ||||
|             $menu->addChild($this->translator->trans('Accompanying Course Actions'), [ | ||||
|                 'route' => 'chill_person_accompanying_period_work_list', | ||||
|                 'routeParameters' => [ | ||||
|                     'id' => $period->getId(), | ||||
|                 ], ]) | ||||
|                 ->setExtras(['order' => 40]); | ||||
|                 ->setExtras([ | ||||
|                     'order' => 40, | ||||
|                     'counter' => count($period->getWorks()) > 0 ? count($period->getWorks()) : null, | ||||
|                 ]); | ||||
|         } | ||||
|  | ||||
|         $workflow = $this->registry->get($period, 'accompanying_period_lifecycle'); | ||||
|   | ||||
| @@ -304,5 +304,14 @@ div#dashboards { | ||||
|                 margin: 0; | ||||
|             } | ||||
|         } | ||||
|         div.count-item { | ||||
|             font-size: 3rem; | ||||
|             text-align: center; | ||||
|         } | ||||
|         div.count-item-label { | ||||
|             font-size: 90%; | ||||
|             font-variant: all-small-caps; | ||||
|             text-align: center; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,36 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| .badge-accompanying-work-type-simple { | ||||
|     @extend .badge; | ||||
|     display: inline-block; | ||||
|     margin: 0.2rem 0; | ||||
|     padding-left: 0; | ||||
|     padding-right: 0.5rem; | ||||
|  | ||||
|     border-left: 20px groove $orange; | ||||
|     border-radius: $badge-border-radius; | ||||
|  | ||||
|     max-width: 100%; | ||||
|     background-color: $gray-100; | ||||
|  | ||||
|     color: black; | ||||
|     font-weight: normal; | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
|     text-indent: 5px hanging; | ||||
|     text-align: left; | ||||
|  | ||||
|     &::before { | ||||
|         margin-right: 3px; | ||||
|         position: relative; | ||||
|         left: -0.5px; | ||||
|         font-family: ForkAwesome; | ||||
|         content: '\f04b'; | ||||
|         color: #e2793d; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// AccompanyingCourse Work Pages | ||||
| div.accompanying-course-work { | ||||
|  | ||||
|   | ||||
| @@ -61,15 +61,15 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { ClassicEditor } from "ckeditor5"; | ||||
| import { Ckeditor } from "@ckeditor/ckeditor5-vue"; | ||||
| import {ClassicEditor} from "ckeditor5"; | ||||
| import {Ckeditor} from "@ckeditor/ckeditor5-vue"; | ||||
| import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config"; | ||||
| import { mapState } from "vuex"; | ||||
|  | ||||
| export default { | ||||
|   name: "Comment", | ||||
|   components: { | ||||
|     ckeditor: Ckeditor, | ||||
|     ckeditor: Ckeditor | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|   | ||||
| @@ -204,7 +204,8 @@ export default { | ||||
|       } else if (payload.type === "thirdparty") { | ||||
|         body.name = payload.data.text; | ||||
|         body.email = payload.data.email; | ||||
|         body.telephone = payload.data.phonenumber; | ||||
|         body.telephone = payload.data.telephone; | ||||
|         body.telephone2 = payload.data.telephone2; | ||||
|         body.address = { id: payload.data.address.address_id }; | ||||
|  | ||||
|         makeFetch( | ||||
|   | ||||
| @@ -385,7 +385,8 @@ export default { | ||||
|       } else if (payload.type === "thirdparty") { | ||||
|         body.name = payload.data.text; | ||||
|         body.email = payload.data.email; | ||||
|         body.telephone = payload.data.phonenumber; | ||||
|         body.telephone = payload.data.telephone; | ||||
|         body.telephone2 = payload.data.telephone2; | ||||
|         if (payload.data.address) { | ||||
|           body.address = { id: payload.data.address.address_id }; | ||||
|         } | ||||
|   | ||||
| @@ -194,6 +194,7 @@ export default { | ||||
|         body.name = payload.data.name; | ||||
|         body.email = payload.data.email; | ||||
|         body.telephone = payload.data.telephone; | ||||
|         body.telephone2 = payload.data.telephone2; | ||||
|         body.address = payload.data.address | ||||
|           ? { id: payload.data.address.address_id } | ||||
|           : null; | ||||
|   | ||||
| @@ -39,15 +39,15 @@ | ||||
| <script> | ||||
| import Modal from "ChillMainAssets/vuejs/_components/Modal.vue"; | ||||
| import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; | ||||
| import { Ckeditor } from "@ckeditor/ckeditor5-vue"; | ||||
| import { Ckeditor }from "@ckeditor/ckeditor5-vue"; | ||||
| import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config"; | ||||
| import { ClassicEditor } from "ckeditor5"; | ||||
| import {ClassicEditor} from "ckeditor5"; | ||||
|  | ||||
| export default { | ||||
|   name: "WriteComment", | ||||
|   components: { | ||||
|     Modal, | ||||
|     ckeditor: Ckeditor, | ||||
|     ckeditor: Ckeditor | ||||
|   }, | ||||
|   props: ["resource"], | ||||
|   emits: ["updateComment"], | ||||
|   | ||||
| @@ -46,8 +46,7 @@ | ||||
|       <label class="col-form-label">{{ $t("comments") }}</label> | ||||
|       <ckeditor | ||||
|         v-model="note" | ||||
|         :editor="classicEditor" | ||||
|         :config="editorConfig" | ||||
|         :editor="classicEditor" :config="editorConfig" | ||||
|         tag-name="textarea" | ||||
|       ></ckeditor> | ||||
|     </div> | ||||
| @@ -208,6 +207,29 @@ | ||||
|             </label> | ||||
|           </div> | ||||
|         </li> | ||||
|         <li | ||||
|           v-for="p in getPreviousPersons" | ||||
|           :key="p.id" | ||||
|           class="alert alert-danger" | ||||
|         > | ||||
|           <div class="form-check"> | ||||
|             <input | ||||
|               v-model="personsPicked" | ||||
|               :value="p.id" | ||||
|               type="checkbox" | ||||
|               class="me-2 form-check-input" | ||||
|               :id="'person_check' + p.id" | ||||
|             /> | ||||
|             <label :for="'person_check' + p.id" class="form-check-label"> | ||||
|               <person-text :person="p"></person-text> | ||||
|             </label> | ||||
|           </div> | ||||
|           <span | ||||
|             ><i class="fa fa-warning"></i> {{ | ||||
|               $t("warning_previous_persons") | ||||
|             }}</span | ||||
|           > | ||||
|         </li> | ||||
|       </ul> | ||||
|     </div> | ||||
|  | ||||
| @@ -440,7 +462,7 @@ | ||||
| import { mapState, mapGetters } from "vuex"; | ||||
| import { Ckeditor } from "@ckeditor/ckeditor5-vue"; | ||||
| import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config"; | ||||
| import { ClassicEditor } from "ckeditor5"; | ||||
| import {ClassicEditor} from "ckeditor5"; | ||||
| import AddResult from "./components/AddResult.vue"; | ||||
| import AddEvaluation from "./components/AddEvaluation.vue"; | ||||
| import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue"; | ||||
| @@ -497,6 +519,8 @@ const i18n = { | ||||
|       notification_notify_referrer: "Notifier le référent", | ||||
|       notification_notify_any: "Notifier d'autres utilisateurs", | ||||
|       notification_send: "Envoyer une notification", | ||||
|       warning_previous_persons: | ||||
|         "Cet usager n'est désormais plus concerné par le parcours, bien qu'il ait été associé à l'action par le passé.", | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| @@ -583,6 +607,7 @@ export default { | ||||
|       "hasHandlingThirdParty", | ||||
|       "hasThirdParties", | ||||
|       "hasReferrers", | ||||
|       "getPreviousPersons", | ||||
|     ]), | ||||
|     classicEditor: () => ClassicEditor, | ||||
|     editorConfig: () => classicEditorConfig, | ||||
| @@ -745,7 +770,8 @@ export default { | ||||
|       let body = { type: payload.type }; | ||||
|       body.name = payload.data.text; | ||||
|       body.email = payload.data.email; | ||||
|       body.telephone = payload.data.phonenumber; | ||||
|       body.telephone = payload.data.telephone; | ||||
|       body.telephone2 = payload.data.telephone2; | ||||
|       body.address = { id: payload.data.address.address_id }; | ||||
|  | ||||
|       makeFetch( | ||||
| @@ -755,7 +781,9 @@ export default { | ||||
|       ) | ||||
|         .then((response) => { | ||||
|           this.$store.dispatch("updateThirdParty", response); | ||||
|           this.$refs.onTheFly.closeModal(); | ||||
|           for (let otf of this.$refs.onTheFly) { | ||||
|             otf.closeModal(); | ||||
|           } | ||||
|         }) | ||||
|         .catch((error) => { | ||||
|           if (error.name === "ValidationException") { | ||||
|   | ||||
| @@ -273,7 +273,7 @@ | ||||
|  | ||||
| <script> | ||||
| import { ISOToDatetime } from "ChillMainAssets/chill/js/date"; | ||||
| import { Ckeditor } from "@ckeditor/ckeditor5-vue"; | ||||
| import {Ckeditor} from "@ckeditor/ckeditor5-vue"; | ||||
| import { ClassicEditor } from "ckeditor5"; | ||||
| import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config"; | ||||
| import { mapState } from "vuex"; | ||||
| @@ -535,11 +535,11 @@ export default { | ||||
|         title: title, | ||||
|       }); | ||||
|     }, | ||||
|     addDocument({ stored_object, stored_object_version }) { | ||||
|     addDocument({ stored_object, stored_object_version, file_name }) { | ||||
|       let document = { | ||||
|         type: "accompanying_period_work_evaluation_document", | ||||
|         storedObject: stored_object, | ||||
|         title: "Nouveau document", | ||||
|         title: file_name, | ||||
|       }; | ||||
|       this.$store.commit("addDocument", { | ||||
|         key: this.evaluation.key, | ||||
|   | ||||
| @@ -87,6 +87,11 @@ const store = createStore({ | ||||
|  | ||||
|       return []; | ||||
|     }, | ||||
|     getPreviousPersons(state) { | ||||
|       return state.personsPicked.filter( | ||||
|         (p) => !state.personsReachables.map((pr) => pr.id).includes(p.id), | ||||
|       ); | ||||
|     }, | ||||
|     buildPayload(state) { | ||||
|       return { | ||||
|         type: "accompanying_period_work", | ||||
| @@ -607,8 +612,7 @@ const store = createStore({ | ||||
|     submit({ getters, state, commit }, callback) { | ||||
|       let payload = getters.buildPayload, | ||||
|         params = new URLSearchParams({ entity_version: state.version }), | ||||
|         url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json?${params}`, | ||||
|         errors = []; | ||||
|         url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json?${params}`; | ||||
|       commit("setIsPosting", true); | ||||
|  | ||||
|       // console.log('the social action', payload); | ||||
|   | ||||
| @@ -30,8 +30,7 @@ | ||||
|  | ||||
|     <div class="item-row comment"> | ||||
|       <ckeditor | ||||
|         :editor="classicEditor" | ||||
|         :config="editorConfig" | ||||
|         :editor="classicEditor" :config="editorConfig" | ||||
|         v-model="comment" | ||||
|         tag-name="textarea" | ||||
|       /> | ||||
| @@ -103,9 +102,9 @@ div.participation-details { | ||||
| <script> | ||||
| import { mapGetters } from "vuex"; | ||||
| import PersonRenderBox from "ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue"; | ||||
| import { Ckeditor } from "@ckeditor/ckeditor5-vue"; | ||||
| import {Ckeditor} from "@ckeditor/ckeditor5-vue"; | ||||
| import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config"; | ||||
| import { ClassicEditor } from "ckeditor5"; | ||||
| import {ClassicEditor} from "ckeditor5"; | ||||
|  | ||||
| export default { | ||||
|   name: "MemberDetails", | ||||
|   | ||||
| @@ -1,25 +1,25 @@ | ||||
| <template> | ||||
|   <ckeditor | ||||
|     name="content" | ||||
|     :placeholder=" | ||||
|     <ckeditor | ||||
|         name="content" | ||||
|         :placeholder=" | ||||
|       $t('household_members_editor.positioning.comment_placeholder') | ||||
|     " | ||||
|     :editor="editor" | ||||
|     :config="editorConfig" | ||||
|     v-model="content" | ||||
|     tag-name="textarea" | ||||
|   /> | ||||
|         :editor="editor" | ||||
|         :config="editorConfig" | ||||
|         v-model="content" | ||||
|         tag-name="textarea" | ||||
|     /> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { Ckeditor } from "@ckeditor/ckeditor5-vue"; | ||||
| import  { Ckeditor } from "@ckeditor/ckeditor5-vue"; | ||||
| import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config"; | ||||
| import { ClassicEditor } from "ckeditor5"; | ||||
| import {ClassicEditor} from "ckeditor5"; | ||||
|  | ||||
| export default { | ||||
|   name: "PersonComment.vue", | ||||
|   components: { | ||||
|     ckeditor: Ckeditor, | ||||
|     ckeditor: Ckeditor | ||||
|   }, | ||||
|   props: ["conc"], | ||||
|   computed: { | ||||
|   | ||||
| @@ -201,7 +201,7 @@ | ||||
|         {% endif %} | ||||
|  | ||||
|         {% if accompanyingCourse.step != 'DRAFT' %} | ||||
|             <div class="mbloc col col-sm-6 col-lg-4"> | ||||
|             <div class="mbloc col col-sm-6 col-lg-8 col-xxl-4"> | ||||
|                 <div class="notification-counter"> | ||||
|                     <h4 class="item-key">{{ 'notification.Notifications'|trans }}</h4> | ||||
|                     {% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) %} | ||||
| @@ -238,6 +238,31 @@ | ||||
|                 </div> | ||||
|             </div> | ||||
|         {% endif %} | ||||
|  | ||||
|         {% if counters.activities > 0 %} | ||||
|             <div class="mbloc col col-sm-6 col-lg-4"> | ||||
|                 <div class="count-activities"> | ||||
|                     <div class="count-item">{{ counters.activities }}</div> | ||||
|                     <div class="count-item-label"> | ||||
|                         {% if counters.activities == 1 %} | ||||
|                             {{ 'Activity'|trans }} | ||||
|                         {% else %} | ||||
|                             {{ 'Activities'|trans }} | ||||
|                         {% endif %} | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         {% endif %} | ||||
|  | ||||
|         {% if counters.works > 0 %} | ||||
|             <div class="mbloc col col-sm-6 col-lg-4"> | ||||
|                 <div class="count-works"> | ||||
|                     <div class="count-item">{{ counters.openWorks }} / {{ counters.works }}</div> | ||||
|                     <div class="count-item-label">{{ 'accompanying_course_work.On-going works over total'|trans }}</div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         {% endif %} | ||||
|  | ||||
|     </div> | ||||
|  | ||||
|     <div class="social-actions my-4"> | ||||
|   | ||||
| @@ -8,7 +8,7 @@ L'usager {{ oldPersonLocation|chill_entity_render_string }} a déménagé. | ||||
| Son adresse était utilisée pour localiser le parcours n°{{ period.id }}, dont vous êtes | ||||
| le référent. | ||||
|  | ||||
| En conséquence de ce déménage, le parcours est toujours localisé à cette adresse, mais à l'aide d'une | ||||
| En conséquence de ce déménagement, le parcours est toujours localisé à cette adresse, mais à l'aide d'une | ||||
| adresse temporaire. | ||||
|  | ||||
| Si vous continuez à suivre le parcours, vous pouvez le localiser à nouveau auprès de l'adresse de | ||||
|   | ||||
| @@ -5,44 +5,49 @@ | ||||
| {% set w = document.accompanyingPeriodWorkEvaluation.accompanyingPeriodWork %} | ||||
|  | ||||
| <div class="item-row"> | ||||
|     <div class="item-col" style="width: unset"> | ||||
|         {% if document.storedObject.isPending %} | ||||
|             <div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div> | ||||
|         {% elseif document.storedObject.isFailure %} | ||||
|             <div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div> | ||||
|         {% endif %} | ||||
|         <div> | ||||
|             {% if context == 'person' %} | ||||
|                 <span class="badge bg-primary"> | ||||
|                         <i class="fa fa-random"></i> {{ w.accompanyingPeriod.id }} | ||||
|                     </span>  | ||||
|     <!-- evaluation document --> | ||||
|     <div class="item-two-col-grid" style="width: unset"> | ||||
|         <div class="title"> | ||||
|             {% if document.storedObject.isPending %} | ||||
|                 <div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div> | ||||
|             {% elseif document.storedObject.isFailure %} | ||||
|                 <div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div> | ||||
|             {% endif %} | ||||
|             <div class="badge-accompanying-work-type"> | ||||
|                 <span class="title_label"></span> | ||||
|                 <span class="title_action">{{ w.socialAction|chill_entity_render_string }} > {{ document.accompanyingPeriodWorkEvaluation.evaluation.title|localize_translatable_string }}</span> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="denomination h2"> | ||||
|             {{ document.title|chill_print_or_message("No title") }} | ||||
|         </div> | ||||
|         {% if document.storedObject.type is not empty %} | ||||
|             <div> | ||||
|                 {{ mm.mimeIcon(document.storedObject.type) }} | ||||
|                 <div> | ||||
|                     <div class="badge-accompanying-work-type-simple"> | ||||
|                         {{ w.socialAction|chill_entity_render_string }} > {{ document.accompanyingPeriodWorkEvaluation.evaluation.title|localize_translatable_string }} | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="denomination h2"> | ||||
|                 {{ document.title|chill_print_or_message("No title") }} | ||||
|             </div> | ||||
|             {% if document.storedObject.type is not empty %} | ||||
|                 <div> | ||||
|                     {{ mm.mimeIcon(document.storedObject.type) }} | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|             {% if document.storedObject.hasTemplate %} | ||||
|                 <div> | ||||
|                     <p>{{ document.storedObject.template.name|localize_translatable_string }}</p> | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|         </div> | ||||
|         {% if document.storedObject.createdAt is not null %} | ||||
|             <div class="aside"> | ||||
|                 <div class="dates row text-end"> | ||||
|                     <span>{{ document.storedObject.createdAt|format_date('short') }}</span> | ||||
|                 </div> | ||||
|                 {% if context == 'person' %} | ||||
|                     <div class="text-end"> | ||||
|                         <span class="badge bg-primary"> | ||||
|                             <i class="fa fa-random"></i> {{ w.accompanyingPeriod.id }} | ||||
|                         </span>  | ||||
|                     </div> | ||||
|                 {% endif %} | ||||
|             </div> | ||||
|         {% endif %} | ||||
|         {% if document.storedObject.hasTemplate %} | ||||
|             <div> | ||||
|                 <p>{{ document.storedObject.template.name|localize_translatable_string }}</p> | ||||
|             </div> | ||||
|         {% endif %} | ||||
|     </div> | ||||
|  | ||||
|     <div class="item-col"> | ||||
|         <div class="container"> | ||||
|             <div class="dates row text-end"> | ||||
|                 <span>{{ document.storedObject.createdAt|format_date('short') }}</span> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
|   | ||||
| @@ -30,9 +30,6 @@ final readonly class SocialActionCSVExportService | ||||
|         private TranslatorInterface $translator, | ||||
|     ) {} | ||||
|  | ||||
|     /** | ||||
|      * @param list<SocialAction> $actions | ||||
|      */ | ||||
|     public function generateCsv(array $actions): Writer | ||||
|     { | ||||
|         // CSV headers | ||||
| @@ -84,7 +81,8 @@ final readonly class SocialActionCSVExportService | ||||
|             'action_id' => $action->getId(), | ||||
|             'social_issue_id' => $action->getIssue()?->getId(), | ||||
|             'problematique_label' => null !== $action->getIssue() ? $this->socialIssueRender->renderString($action->getIssue(), []) : null, | ||||
|             'social_issue_ordering' => null !== $action->getIssue() ? $action->getIssue()->getOrdering() : null, | ||||
|             'desactivation_date' => $action->getDesactivationDate()?->format('Y-m-d'), | ||||
|             'social_issue_ordering' => $action->getIssue()?->getOrdering(), | ||||
|             'action_label' => $this->socialActionRender->renderString($action, []), | ||||
|             'action_ordering' => $action->getOrdering(), | ||||
|             'goal_label' => null !== $goal ? $this->stringHelper->localize($goal->getTitle()) : null, | ||||
|   | ||||
| @@ -44,6 +44,7 @@ readonly class SocialIssueCSVExportService | ||||
|                     'Id', | ||||
|                     'Label', | ||||
|                     'Social issue', | ||||
|                     'export.social_action_list.desactivation_date', | ||||
|                     'socialIssue.isParent?', | ||||
|                     'socialIssue.Parent id', | ||||
|                 ] | ||||
| @@ -66,6 +67,7 @@ readonly class SocialIssueCSVExportService | ||||
|                 'id' => $issue->getId(), | ||||
|                 'label' => $this->stringHelper->localize($issue->getTitle()), | ||||
|                 'title' => $this->socialIssueRender->renderString($issue, []), | ||||
|                 'export.social_action_list.desactivation_date' => $issue->getDesactivationDate()?->format('Y-m-d'), | ||||
|                 'isParent' => $issue->hasChildren() ? 'X' : '', | ||||
|                 'parent_id' => null !== $issue->getParent() ? $issue->getParent()->getId() : '', | ||||
|             ]; | ||||
|   | ||||
| @@ -52,6 +52,7 @@ class SocialActionCsvExporterTest extends TestCase | ||||
|         // Création d'une instance réelle de SocialAction sans objectifs ni résultats | ||||
|         $actionWithoutGoalsOrResults = new SocialAction(); | ||||
|         $actionWithoutGoalsOrResults->setIssue($socialIssue); | ||||
|         $actionWithoutGoalsOrResults->setDesactivationDate(new \DateTime('2025-05-21')); | ||||
|         $actionWithoutGoalsOrResults->setTitle(['fr' => 'Action without goals or results']); | ||||
|  | ||||
|         // Création d'une instance réelle de SocialAction avec des objectifs et des résultats | ||||
| @@ -61,6 +62,7 @@ class SocialActionCsvExporterTest extends TestCase | ||||
|  | ||||
|         $actionWithGoalsAndResults = new SocialAction(); | ||||
|         $actionWithGoalsAndResults->setIssue($socialIssue); | ||||
|         $actionWithGoalsAndResults->setDesactivationDate(new \DateTime('2025-05-21')); | ||||
|         $actionWithGoalsAndResults->setTitle(['fr' => 'Action with goals and results']); | ||||
|         $actionWithGoalsAndResults->addGoal($goalWithResult); | ||||
|  | ||||
| @@ -68,6 +70,7 @@ class SocialActionCsvExporterTest extends TestCase | ||||
|         $goalWithoutResult = new Goal(); | ||||
|         $actionWithGoalsNoResults = new SocialAction(); | ||||
|         $actionWithGoalsNoResults->setIssue($socialIssue); | ||||
|         $actionWithGoalsNoResults->setDesactivationDate(new \DateTime('2025-05-21')); | ||||
|         $actionWithGoalsNoResults->setTitle(['fr' => 'Action with goals and no results']); | ||||
|         $actionWithGoalsNoResults->addGoal($goalWithoutResult); | ||||
|  | ||||
| @@ -76,6 +79,7 @@ class SocialActionCsvExporterTest extends TestCase | ||||
|         $resultWithNoAction->setTitle(['fr' => 'Result without objectives']); | ||||
|         $actionWithResultsNoGoals = new SocialAction(); | ||||
|         $actionWithResultsNoGoals->setIssue($socialIssue); | ||||
|         $actionWithResultsNoGoals->setDesactivationDate(new \DateTime('2025-05-21')); | ||||
|         $actionWithResultsNoGoals->setTitle(['fr' => 'Action with results and no goals']); | ||||
|         $actionWithResultsNoGoals->addResult($resultWithNoAction); | ||||
|  | ||||
| @@ -91,11 +95,11 @@ class SocialActionCsvExporterTest extends TestCase | ||||
|         $this->assertStringContainsString('Action with results and no goals', $content); | ||||
|  | ||||
|         self::assertEquals(<<<'CSV' | ||||
|         export.social_action_list.action_id,export.social_action_list.social_issue_id,export.social_action_list.problematique_label,export.social_action_list.social_issue_ordering,export.social_action_list.action_label,export.social_action_list.action_ordering,export.social_action_list.goal_label,export.social_action_list.goal_id,export.social_action_list.goal_result_label,export.social_action_list.goal_result_id,export.social_action_list.result_without_goal_label,export.social_action_list.result_without_goal_id,export.social_action_list.evaluation_title,export.social_action_list.evaluation_id,export.social_action_list.evaluation_url,export.social_action_list.evaluation_delay_month,export.social_action_list.evaluation_delay_week,export.social_action_list.evaluation_delay_day | ||||
|         ,,"Issue Title",0,"Action with goals and results",0,"not found",,"not found",,,,,,,,, | ||||
|         ,,"Issue Title",0,"Action without goals or results",0,,,,,,,,,,,, | ||||
|         ,,"Issue Title",0,"Action with goals and no results",0,"not found",,,,,,,,,,, | ||||
|         ,,"Issue Title",0,"Action with results and no goals",0,,,,,"Result without objectives",,,,,,, | ||||
|         export.social_action_list.action_id,export.social_action_list.social_issue_id,export.social_action_list.problematique_label,export.social_action_list.desactivation_date,export.social_action_list.social_issue_ordering,export.social_action_list.action_label,export.social_action_list.action_ordering,export.social_action_list.goal_label,export.social_action_list.goal_id,export.social_action_list.goal_result_label,export.social_action_list.goal_result_id,export.social_action_list.result_without_goal_label,export.social_action_list.result_without_goal_id,export.social_action_list.evaluation_title,export.social_action_list.evaluation_id,export.social_action_list.evaluation_url,export.social_action_list.evaluation_delay_month,export.social_action_list.evaluation_delay_week,export.social_action_list.evaluation_delay_day | ||||
|         ,,"Issue Title",2025-05-21,0,"Action with goals and results",0,"not found",,"not found",,,,,,,,, | ||||
|         ,,"Issue Title",2025-05-21,0,"Action without goals or results",0,,,,,,,,,,,, | ||||
|         ,,"Issue Title",2025-05-21,0,"Action with goals and no results",0,"not found",,,,,,,,,,, | ||||
|         ,,"Issue Title",2025-05-21,0,"Action with results and no goals",0,,,,,"Result without objectives",,,,,,, | ||||
|  | ||||
|         CSV, $content); | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,43 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\Migrations\Person; | ||||
|  | ||||
| use Doctrine\DBAL\Schema\Schema; | ||||
| use Doctrine\Migrations\AbstractMigration; | ||||
|  | ||||
| final class Version20250514115009 extends AbstractMigration | ||||
| { | ||||
|     public function getDescription(): string | ||||
|     { | ||||
|         return 'Allow more characters for maritalstatus id'; | ||||
|     } | ||||
|  | ||||
|     public function up(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql(<<<'SQL' | ||||
|             ALTER TABLE chill_person_marital_status ALTER id TYPE VARCHAR(15) | ||||
|         SQL); | ||||
|         $this->addSql(<<<'SQL' | ||||
|             ALTER TABLE chill_person_person ALTER maritalstatus_id TYPE VARCHAR(15) | ||||
|         SQL); | ||||
|     } | ||||
|  | ||||
|     public function down(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql(<<<'SQL' | ||||
|             ALTER TABLE chill_person_person ALTER maritalStatus_id TYPE VARCHAR(7) | ||||
|         SQL); | ||||
|         $this->addSql(<<<'SQL' | ||||
|             ALTER TABLE chill_person_marital_status ALTER id TYPE VARCHAR(7) | ||||
|         SQL); | ||||
|     } | ||||
| } | ||||
| @@ -804,7 +804,7 @@ person_admin: | ||||
|  | ||||
| # specific to accompanying period | ||||
| accompanying_period: | ||||
|     deleted: Parcours d'accompagnment supprimé | ||||
|     deleted: Parcours d'accompagnement supprimé | ||||
|     dates: Période | ||||
|     dates_from_%opening_date%: Ouvert depuis le %opening_date% | ||||
|     dates_from_%opening_date%_to_%closing_date%: Ouvert du %opening_date% au %closing_date% | ||||
| @@ -843,6 +843,7 @@ accompanying_course: | ||||
|     administrative_location: Localisation administrative | ||||
|     comment is pinned: Le commentaire est épinglé | ||||
|     comment is unpinned: Le commentaire est désépinglé | ||||
|  | ||||
| show: Montrer | ||||
| hide: Masquer | ||||
| closed periods: parcours clôturés | ||||
| @@ -851,6 +852,7 @@ Social work configuration: Gestion des actions d'accompagnement social | ||||
|  | ||||
| # Accompanying Course comments | ||||
| Accompanying Course Comment: Commentaire | ||||
| Accompanying Course Comments: Commentaires | ||||
| Accompanying Course Comment list: Commentaires du parcours | ||||
| pinned: épinglé | ||||
| Pin comment: Épingler | ||||
| @@ -919,6 +921,7 @@ accompanying_course_work: | ||||
|     date_filter: Filtrer par date | ||||
|     types_filter: Filtrer par type d'action | ||||
|     user_filter: Filtrer par intervenant | ||||
|     On-going works over total: Actions en cours / Actions du parcours | ||||
|  | ||||
|  | ||||
| # | ||||
|   | ||||
| @@ -624,7 +624,7 @@ final class SingleTaskController extends AbstractController | ||||
|             ->addCheckbox('status', $statuses, $statuses, $statusTrans); | ||||
|  | ||||
|         $states = $this->singleTaskStateRepository->findAllExistingStates(); | ||||
|         $checked = array_values(array_filter($states, fn (string $state) => !in_array($state, ['closed', 'canceled', 'validated'], true))); | ||||
|         $checked = array_values(array_filter($states, fn (string $state) => !in_array($state, ['in_progress', 'closed', 'canceled', 'validated'], true))); | ||||
|  | ||||
|         if ([] !== $states) { | ||||
|             $filterBuilder | ||||
|   | ||||
| @@ -65,6 +65,7 @@ class ThirdpartyCSVExportController extends AbstractController | ||||
|                     'Name', | ||||
|                     'Profession', | ||||
|                     'Telephone', | ||||
|                     'Telephone2', | ||||
|                     'Email', | ||||
|                     'Address', | ||||
|                     'Comment', | ||||
| @@ -76,6 +77,7 @@ class ThirdpartyCSVExportController extends AbstractController | ||||
|                     'Contact name', | ||||
|                     'Contact  firstname', | ||||
|                     'Contact phone', | ||||
|                     'Contact phone2', | ||||
|                     'Contact email', | ||||
|                     'Contact address', | ||||
|                     'Contact profession', | ||||
|   | ||||
| @@ -209,6 +209,11 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface, \Strin | ||||
|     #[PhonenumberConstraint(type: 'any')] | ||||
|     private ?PhoneNumber $telephone = null; | ||||
|  | ||||
|     #[Groups(['read', 'write', 'docgen:read', 'docgen:read:3party:parent'])] | ||||
|     #[ORM\Column(name: 'telephone2', type: 'phone_number', nullable: true)] | ||||
|     #[PhonenumberConstraint(type: 'any')] | ||||
|     private ?PhoneNumber $telephone2 = null; | ||||
|  | ||||
|     #[ORM\Column(name: 'types', type: \Doctrine\DBAL\Types\Types::JSON, nullable: true)] | ||||
|     private ?array $thirdPartyTypes = []; | ||||
|  | ||||
| @@ -429,6 +434,11 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface, \Strin | ||||
|         return $this->telephone; | ||||
|     } | ||||
|  | ||||
|     public function getTelephone2(): ?PhoneNumber | ||||
|     { | ||||
|         return $this->telephone2; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get type. | ||||
|      */ | ||||
| @@ -712,6 +722,13 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface, \Strin | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setTelephone2(?PhoneNumber $telephone2 = null): self | ||||
|     { | ||||
|         $this->telephone2 = $telephone2; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set type. | ||||
|      * | ||||
|   | ||||
| @@ -59,6 +59,10 @@ class ThirdPartyType extends AbstractType | ||||
|                 'label' => 'Phonenumber', | ||||
|                 'required' => false, | ||||
|             ]) | ||||
|             ->add('telephone2', ChillPhoneNumberType::class, [ | ||||
|                 'label' => 'telephone2', | ||||
|                 'required' => false, | ||||
|             ]) | ||||
|             ->add('email', EmailType::class, [ | ||||
|                 'required' => false, | ||||
|             ]) | ||||
|   | ||||
| @@ -42,6 +42,7 @@ class ThirdPartyRepository implements ObjectRepository | ||||
|             parent.name AS name, | ||||
|             parent.profession AS profession, | ||||
|             parent.telephone AS telephone, | ||||
|             parent.telephone2 AS telephone2, | ||||
|             parent.email AS email, | ||||
|             CONCAT_WS(' ', parent_address.street, parent_address.streetnumber, parent_postal.code, parent_postal.label) AS address, | ||||
|             parent.comment AS comment, | ||||
| @@ -55,6 +56,7 @@ class ThirdPartyRepository implements ObjectRepository | ||||
|             contact.name AS contact_name, | ||||
|             contact.firstname AS contact_firstname, | ||||
|             contact.telephone AS contact_phone, | ||||
|             contact.telephone2 AS contact_phone2, | ||||
|             contact.email AS contact_email, | ||||
|             contact.profession AS contact_profession, | ||||
|             CONCAT_WS(' ', contact_address.street, contact_address.streetnumber, contact_postal.code, contact_postal.label) AS contact_address | ||||
|   | ||||
| @@ -91,6 +91,18 @@ | ||||
|                                                     }}</a | ||||
|                                                 > | ||||
|                                             </li> | ||||
|                                             <li v-if="thirdparty.telephone2"> | ||||
|                                                 <i class="fa fa-li fa-mobile" /> | ||||
|                                                 <a | ||||
|                                                     :href=" | ||||
|                                                         'tel: ' + | ||||
|                                                         thirdparty.telephone2 | ||||
|                                                     " | ||||
|                                                     >{{ | ||||
|                                                         thirdparty.telephone2 | ||||
|                                                     }}</a | ||||
|                                                 > | ||||
|                                             </li> | ||||
|                                             <li v-if="thirdparty.email"> | ||||
|                                                 <i | ||||
|                                                     class="fa fa-li fa-envelope-o" | ||||
| @@ -121,6 +133,12 @@ | ||||
|                                         thirdparty.telephone | ||||
|                                     }}</a> | ||||
|                                 </li> | ||||
|                                 <li v-if="thirdparty.telephone2"> | ||||
|                                     <i class="fa fa-li fa-mobile" /> | ||||
|                                     <a :href="'tel: ' + thirdparty.telephone2" | ||||
|                                         >{{ thirdparty.telephone2 }} | ||||
|                                     </a> | ||||
|                                 </li> | ||||
|                                 <li v-if="thirdparty.email"> | ||||
|                                     <i class="fa fa-li fa-envelope-o" /> | ||||
|                                     <a :href="'mailto: ' + thirdparty.email">{{ | ||||
|   | ||||
| @@ -223,6 +223,19 @@ | ||||
|             /> | ||||
|         </div> | ||||
|  | ||||
|         <div class="input-group mb-3"> | ||||
|             <span class="input-group-text" id="phonenumber2" | ||||
|                 ><i class="fa fa-fw fa-phone" | ||||
|             /></span> | ||||
|             <input | ||||
|                 class="form-control form-control-lg" | ||||
|                 v-model="thirdparty.telephone2" | ||||
|                 :placeholder="$t('thirdparty.phonenumber2')" | ||||
|                 :aria-label="$t('thirdparty.phonenumber2')" | ||||
|                 aria-describedby="phonenumber2" | ||||
|             /> | ||||
|         </div> | ||||
|  | ||||
|         <div v-if="parent"> | ||||
|             <div class="input-group mb-3"> | ||||
|                 <span class="input-group-text" id="comment" | ||||
| @@ -263,6 +276,7 @@ export default { | ||||
|                 firstname: "", | ||||
|                 name: "", | ||||
|                 telephone: "", | ||||
|                 telephone2: "", | ||||
|                 civility: null, | ||||
|                 profession: "", | ||||
|             }, | ||||
| @@ -368,9 +382,11 @@ export default { | ||||
|         addQueryItem(field, queryItem) { | ||||
|             switch (field) { | ||||
|                 case "name": | ||||
|                     this.thirdparty.name | ||||
|                         ? (this.thirdparty.name += ` ${queryItem}`) | ||||
|                         : (this.thirdparty.name = queryItem); | ||||
|                     if (this.thirdparty.name) { | ||||
|                         this.thirdparty.name += ` ${queryItem}`; | ||||
|                     } else { | ||||
|                         this.thirdparty.name = queryItem; | ||||
|                     } | ||||
|                     break; | ||||
|                 case "firstName": | ||||
|                     this.thirdparty.firstname = queryItem; | ||||
|   | ||||
| @@ -6,6 +6,7 @@ const thirdpartyMessages = { | ||||
|       name: "Dénomination", | ||||
|       email: "Courriel", | ||||
|       phonenumber: "Téléphone", | ||||
|       phonenumber2: "Autre numéro de téléphone", | ||||
|       comment: "Commentaire", | ||||
|       profession: "Qualité", | ||||
|       civility: "Civilité", | ||||
|   | ||||
| @@ -115,6 +115,10 @@ | ||||
|                             {% else %} | ||||
|                                 <span class="chill-no-data-statement">{{ 'thirdparty.No_phonenumber'|trans }}</span> | ||||
|                             {% endif %} | ||||
|                             {% if thirdparty.telephone2 is not null %} | ||||
|                                 {% if thirdparty.telephone is not null %}, {% endif %} | ||||
|                                 <a href="{{ 'tel:' ~ thirdparty.telephone2|phone_number_format('E164') }}">{{ thirdparty.telephone2|chill_format_phonenumber }}</a> | ||||
|                             {% endif %} | ||||
|                         </li> | ||||
|                         <li><i class="fa fa-li fa-envelope-o"></i> | ||||
|                             <a href="{{ 'mailto:' ~ thirdparty.email }}"> | ||||
| @@ -135,8 +139,14 @@ | ||||
|                     }) }} | ||||
|                 </li> | ||||
|                 <li><i class="fa fa-li fa-phone"></i> | ||||
|                     {% if thirdparty.telephone %} | ||||
|                         <a href="{{ 'tel:' ~ thirdparty.telephone|phone_number_format('E164') }}">{{ thirdparty.telephone|chill_format_phonenumber }}</a> | ||||
|                     {% if thirdparty.telephone or thirdparty.telephone2 %} | ||||
|                         {% if thirdparty.telephone is not null %} | ||||
|                             <a href="{{ 'tel:' ~ thirdparty.telephone|phone_number_format('E164') }}">{{ thirdparty.telephone|chill_format_phonenumber }}</a> | ||||
|                         {% endif %} | ||||
|                         {% if thirdparty.telephone2 is not null %} | ||||
|                             {% if thirdparty.telephone is not null %}, {% endif %} | ||||
|                             <a href="{{ 'tel:' ~ thirdparty.telephone2|phone_number_format('E164') }}">{{ thirdparty.telephone2|chill_format_phonenumber }}</a> | ||||
|                         {% endif %} | ||||
|                     {% else %} | ||||
|                         <span class="chill-no-data-statement">{{ 'thirdparty.No_phonenumber'|trans }}</span> | ||||
|                     {% endif %} | ||||
|   | ||||
| @@ -22,6 +22,7 @@ | ||||
| {{ form_row(form.typesAndCategories) }} | ||||
|  | ||||
| {{ form_row(form.telephone) }} | ||||
| {{ form_row(form.telephone2) }} | ||||
| {{ form_row(form.email) }} | ||||
|  | ||||
| {% if form.contactDataAnonymous is defined %} | ||||
|   | ||||
| @@ -24,17 +24,24 @@ | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="row"> | ||||
|             <div class="form-group col-md-5 mb-3"> | ||||
|             <div class="form-group col-md-6 mb-3"> | ||||
|                 {{ form_widget(form.telephone) }} | ||||
|                 {{ form_errors(form.telephone) }} | ||||
|                 {{ form_label(form.telephone) }} | ||||
|             </div> | ||||
|             <div class="form-group col-md-5 mb-3"> | ||||
|             <div class="form-group col-md-6 mb-3"> | ||||
|                 {{ form_widget(form.telephone2) }} | ||||
|                 {{ form_errors(form.telephone2) }} | ||||
|                 {{ form_label(form.telephone2) }} | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="row"> | ||||
|             <div class="form-group col-md-6 mb-3"> | ||||
|                 {{ form_widget(form.email) }} | ||||
|                 {{ form_errors(form.email) }} | ||||
|                 {{ form_label(form.email) }} | ||||
|             </div> | ||||
|             <div class="form-group col-md-2 mb-3"> | ||||
|             <div class="form-group col-md-6 mb-3"> | ||||
|                 {{ form_widget(form.contactDataAnonymous) }} | ||||
|                 {{ form_label(form.contactDataAnonymous) }} | ||||
|                 {{ form_errors(form.contactDataAnonymous) }} | ||||
|   | ||||
| @@ -76,6 +76,18 @@ | ||||
|                         {% endif %} | ||||
|                     </dd> | ||||
|  | ||||
|  | ||||
|                     <dt>{{ 'Phonenumber2'|trans }}</dt> | ||||
|                     <dd> | ||||
|                         {% if thirdParty.telephone2 == null %} | ||||
|                             <span class="chill-no-data-statement">{{ 'thirdparty.No_phonenumber'|trans }}</span> | ||||
|                         {%  else %} | ||||
|                             <a href="{{ 'tel:' ~ thirdParty.telephone2|phone_number_format('E164') }}"> | ||||
|                                 {{ thirdParty.telephone2|chill_format_phonenumber }} | ||||
|                             </a> | ||||
|                         {% endif %} | ||||
|                     </dd> | ||||
|  | ||||
|                     <dt>{{ 'email'|trans }}<dt> | ||||
|                     <dd> | ||||
|                         {% if thirdParty.email == null %} | ||||
|   | ||||
| @@ -55,6 +55,7 @@ class ThirdPartyNormalizer implements NormalizerAwareInterface, NormalizerInterf | ||||
|             'profession' => $this->normalizer->normalize($thirdParty->getProfession(), $format, $context), | ||||
|             'address' => $this->normalizer->normalize($thirdParty->getAddress(), $format, ['address_rendering' => 'short']), | ||||
|             'telephone' => $this->normalizer->normalize($thirdParty->getTelephone(), $format, $context), | ||||
|             'telephone2' => $this->normalizer->normalize($thirdParty->getTelephone2(), $format, $context), | ||||
|             'email' => $thirdParty->getEmail(), | ||||
|             'isChild' => $thirdParty->isChild(), | ||||
|             'parent' => $this->normalizer->normalize($thirdParty->getParent(), $format, $context), | ||||
|   | ||||
| @@ -28,6 +28,8 @@ components: | ||||
|           type: string | ||||
|         telephone: | ||||
|           type: string | ||||
|         telephone2: | ||||
|           type: string | ||||
|         address: | ||||
|           $ref: "#/components/schemas/Address" | ||||
|     Address: | ||||
|   | ||||
| @@ -0,0 +1,34 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\Migrations\ThirdParty; | ||||
|  | ||||
| use Doctrine\DBAL\Schema\Schema; | ||||
| use Doctrine\Migrations\AbstractMigration; | ||||
|  | ||||
| final class Version20250325085950 extends AbstractMigration | ||||
| { | ||||
|     public function getDescription(): string | ||||
|     { | ||||
|         return 'Add a second telephone number to ThirdParty'; | ||||
|     } | ||||
|  | ||||
|     public function up(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql('ALTER TABLE chill_3party.third_party ADD telephone2 VARCHAR(35) DEFAULT NULL'); | ||||
|         $this->addSql('COMMENT ON COLUMN chill_3party.third_party.telephone2 IS \'(DC2Type:phone_number)\''); | ||||
|     } | ||||
|  | ||||
|     public function down(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql('ALTER TABLE chill_3party.third_party DROP telephone2'); | ||||
|     } | ||||
| } | ||||
| @@ -4,6 +4,7 @@ third parties: tiers | ||||
| firstname: Prénom | ||||
| name: Nom | ||||
| telephone: Téléphone | ||||
| telephone2: Autre numéro de téléphone | ||||
| adress: Adresse | ||||
| email: Courriel | ||||
| comment: Commentaire | ||||
| @@ -39,7 +40,7 @@ thirdparty.A contact: Une personne physique | ||||
| thirdparty.contact: Personne physique | ||||
| thirdparty.Contact of: Contact de | ||||
| thirdparty.a_company_explanation: >- | ||||
|     Les personnes morales peuvent compter un ou plusieurs contacts, interne à l'instution. Il est également possible de | ||||
|     Les personnes morales peuvent compter un ou plusieurs contacts, interne à l'institution. Il est également possible de | ||||
|     leur associer un acronyme, et le nom d'un service. | ||||
| thirdparty.a_contact_explanation: >- | ||||
|     Les personnes physiques ne disposent pas d'acronyme, de service, ou de contacts sous-jacents. Il est possible de leur | ||||
| @@ -149,6 +150,8 @@ Contact id: Identifiant du contact | ||||
| Contact name: Nom du contact | ||||
| Contact  firstname: Prénom du contact | ||||
| Contact phone: Téléphone du contact | ||||
| Contact phone2: Autre téléphone du contact | ||||
| Telephone2: Autre téléphone | ||||
| Contact email: Courrier électronique du contact | ||||
| Contact address: Adresse du contact | ||||
| Contact profession: Profession du contact | ||||
|   | ||||
		Reference in New Issue
	
	Block a user