Compare commits

..

100 Commits

Author SHA1 Message Date
b5c9e65986 Merge branch 'refs/heads/master' into 321-text-editor
# Conflicts:
#	src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/editor_config.ts
#	src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts
#	src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue
#	src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue
#	src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue
#	src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue
#	src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/MemberDetails.vue
#	src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue
#	yarn.lock
2025-05-21 20:03:14 +02:00
8b2af35e97 Fix typo 2025-05-21 17:57:35 +02:00
dc44c46667 Fix SocialActionCSVExporterTest.php 2025-05-21 09:29:25 +02:00
ba571c1a69 Merge branch 'master' of https://gitlab.com/Chill-Projet/chill-bundles 2025-05-20 10:04:53 +02:00
6a364705f2 Git add desactivation date for social issue csv export 2025-05-20 09:57:07 +02:00
b6d454691a Git add desactivation date for social action csv export 2025-05-20 09:38:39 +02:00
6d7a6932a9 Merge branch '365-correct-activities-works-counters' into 'master'
#365 correct works counter in acc course summary

Closes #365

See merge request Chill-Projet/chill-bundles!826
2025-05-16 14:40:20 +00:00
juminet
2faf194b15 #365 correct works counter in acc course summary 2025-05-16 14:40:19 +00:00
f207599d86 Merge branch '352-remove-wopi-link-module' into 'master'
Resolve "Code mort: module wopi-link semble inutilisé"

Closes #352

See merge request Chill-Projet/chill-bundles!824
2025-05-16 08:33:57 +00:00
b0959f8cc5 Resolve "Code mort: module wopi-link semble inutilisé" 2025-05-16 08:33:56 +00:00
4c5dee5f0a Fix pipeline 2025-05-14 17:31:48 +02:00
f6c98aa0d5 Add missing translation for user_group.no_user_groups 2025-05-14 14:53:50 +02:00
6d13d184d5 Merge branch 'master' of https://gitlab.com/Chill-Projet/chill-bundles 2025-05-14 13:57:22 +02:00
af36eccfaf Allow more characters for maritalstatus id 2025-05-14 13:56:36 +02:00
483a20a43f Merge branch '374-task-default-filters' into 'master'
Resolve "Module tâche: enlever les filtres par défaut"

Closes #374

See merge request Chill-Projet/chill-bundles!819
2025-05-13 08:59:01 +00:00
6d8e2ad825 Resolve "Module tâche: enlever les filtres par défaut" 2025-05-13 08:59:01 +00:00
86388a63a8 Merge branch '381-uncoherent-display-persons-acc-course-works' into 'master'
381 display previous person participation in acc course work

Closes #381

See merge request Chill-Projet/chill-bundles!823
2025-05-13 08:45:38 +00:00
juminet
5ea55ebfe5 381 display previous person participation in acc course work 2025-05-13 08:45:38 +00:00
f97dc8f931 Merge branch '377-document-file-name' into 'master'
377 - add the document filename to document title when uploading a new document

Closes #377

See merge request Chill-Projet/chill-bundles!821
2025-05-13 08:30:31 +00:00
juminet
a9c3aab528 377 - add the document filename to document title when uploading a new document 2025-05-13 08:30:30 +00:00
1181377bd6 Merge branch '376-typeerror-doc-history' into 'master'
#376 prevent typerror in doc-history + improve display of document history

Closes #376

See merge request Chill-Projet/chill-bundles!820
2025-05-13 07:50:53 +00:00
juminet
2275b7c560 #376 prevent typerror in doc-history + improve display of document history 2025-05-13 07:50:52 +00:00
4a8d298ae5 Fix typing error for the display of text in calendar events 2025-05-05 10:42:04 +02:00
3e7f03d331 Add possibility to cancel a workflow if there is a pending signature [ci-skip] 2025-04-17 14:23:32 +02:00
b830952b9e Release v3.11.0 2025-04-17 14:22:35 +02:00
ad17313c61 Merge branch '380-remove-not-null-constraint-household-composition' into 'master'
Remove "not null" validation on HouseholdComposition properties

Closes #380

See merge request Chill-Projet/chill-bundles!818
2025-04-17 09:03:15 +00:00
620515ad15 Remove "not null" validation on HouseholdComposition properties
This change removes the "not null" constraint on specific properties in the HouseholdComposition entity to allow null values. The adjustment addresses Issue #380 and ensures better flexibility without impacting the schema.
2025-04-17 10:56:45 +02:00
50c377ee22 Merge branch 'fix/fix-stored-object-version-not-delete-when-not-exists-on-disk' into 'master'
Fix error when cleaning non-existent stored object versions

See merge request Chill-Projet/chill-bundles!816
2025-04-17 08:36:36 +00:00
cc7e7a90ee Merge branch 'fix/cancel-stale-workflow-handle-fails-silently' into 'master'
Add consistent LOG_PREFIX and key to CancelStaleWorkflowHandler logs

See merge request Chill-Projet/chill-bundles!817
2025-04-16 19:10:37 +00:00
1d4ef19051 Add key to log messages context and add log prefix to enhance CancelStaleWorkflowHandler messages
Introduced a consistent LOG_PREFIX to all log messages in CancelStaleWorkflowHandler. This ensures clearer contextual identification in logs and improves traceability when debugging or monitoring workflow actions.
2025-04-16 21:03:29 +02:00
8337a724d1 Fix error when cleaning non-existent stored object versions
Prevent the `RemoveOldVersionMessageHandler` from throwing errors when the stored object version is missing on disk. Introduced a check to log a notice instead of attempting deletion in such cases and added corresponding test coverage.
2025-04-16 16:26:02 +02:00
8ca377d5d4 Merge branch 'button-signature-zone' into 'master'
Add button unique signature zone

See merge request Chill-Projet/chill-bundles!812
2025-04-15 13:09:54 +00:00
224e0bae43 Add button unique signature zone 2025-04-15 13:09:54 +00:00
3aa4fac80d Merge branch '364-tel2-third-party' into 'master'
Adding a second phone number to thirdparty entity

Closes #364

See merge request Chill-Projet/chill-bundles!810
2025-04-15 12:59:58 +00:00
juminet
a7517eb647 Adding a second phone number to thirdparty entity 2025-04-15 12:59:57 +00:00
e278e636e0 Merge branch '365-add-activities-works-counter' into 'master'
#365 Add works and activities counter

Closes #365

See merge request Chill-Projet/chill-bundles!809
2025-04-14 09:34:02 +00:00
juminet
40e373a9c7 #365 Add works and activities counter 2025-04-14 09:34:02 +00:00
1c1f418b18 Merge branch 'fix/acc-period-step-change-shorten-elapsed' into 'master'
Adjust cronjob interval to ensure daily execution

See merge request Chill-Projet/chill-bundles!814
2025-04-10 13:49:11 +00:00
bf0e14b43a Merge branch '102-liste-des-document-titre-long' into 'master'
Fix graphical bug in document list with title overflowing the frame, and add new classes to display title and aside in flex-table (DX + Fix)

Closes #102 and #22

See merge request Chill-Projet/chill-bundles!815
2025-04-10 13:47:40 +00:00
203a098054 Refactor document row layouts to use CSS grid
Replaced the old 'item-col' structure with a 'item-two-col-grid' layout across multiple templates, improving consistency and responsiveness. Introduced CSS grid styles ensuring proper alignment and wrapping of titles and aside elements in different viewport sizes. This enhances the overall readability and maintainability of the views.
2025-04-10 15:41:06 +02:00
d58acff541 Add css layout for badges for accompanying period work, activity and calendar 2025-04-10 15:41:06 +02:00
5858e05a42 Replace grid in person_list document to fit the whole width of the page 2025-04-10 15:31:06 +02:00
b9b4fafe14 Adjust cronjob interval to ensure daily execution
The interval for `AccompanyingPeriodStepChangeCronjob` was reduced from 24 hours to 23 hours and 45 minutes. This change guarantees at least one execution per day, addressing potential issues with timing overlaps.
2025-04-09 21:30:41 +02:00
fe6949ea26 Update chill bundles to v3.10.3 2025-03-18 13:47:31 +01:00
8a444a12f4 Turn off eslint error ban-ts-comment 2025-03-18 10:57:24 +01:00
8b7b5ceed7 Release v3.10.2 2025-03-17 22:30:17 +01:00
b0e826d05a Revert "Remove unnecessary ts-expect-error"
This reverts commit 7f101ba616.
2025-03-17 22:22:37 +01:00
6fb9c3af3f Release v3.10.1 2025-03-17 21:59:32 +01:00
7f101ba616 Remove unnecessary ts-expect-error 2025-03-17 21:59:16 +01:00
cea82fac10 Merge branch '333-removing-node-deps-to-symfony-ux-translator' into 'master'
Copy Symfony UX Translator module into to chill-bundles

Closes #333

See merge request Chill-Projet/chill-bundles!808
2025-03-17 20:45:48 +00:00
1344fc33e1 Copy Symfony UX Translator module into to chill-bundles 2025-03-17 20:45:48 +00:00
c8e09a28e6 Eslint fixes 2025-03-17 17:32:28 +01:00
c52d4b2a0e Release v3.10.0 2025-03-17 16:41:07 +01:00
7f326d5441 Fix typing of argument in FullCAlendar slot 2025-03-17 16:29:59 +01:00
2840c06476 Merge branch '368-fix-user-search-engine' into 'master'
Fix search query for user-groups

Closes #368

See merge request Chill-Projet/chill-bundles!806
2025-03-14 14:10:53 +00:00
7ddf84ea5a Fix ts errors upon prod compilation 2025-03-14 15:07:23 +01:00
f202625ea8 Fix LIKE clause logic in UserGroupRepository query
Adjusted the parameter order and ensured consistent use of LOWER and UNACCENT functions in the LIKE clause. This resolves potential mismatches and improves query reliability for pattern matching.
2025-03-14 15:04:22 +01:00
7a9168fcdb Refactor variable declarations in pick-entity module.
Consolidated variable declarations into a single statement using const. This improves code readability and aligns with modern JavaScript best practices.
2025-03-14 15:02:09 +01:00
40eb71f95a Merge branch '362-bug-manager-registry-fix2' into 'master'
Resolve loading of manager registry

Closes #362

See merge request Chill-Projet/chill-bundles!803
2025-03-05 16:45:08 +00:00
84b7cc8145 Php cs fixes 2025-03-05 10:46:36 +01:00
08af530726 Replace deprecated Statement::create() method use constructor directly 2025-03-05 10:26:22 +01:00
03b2496817 Fix dependency injection issues in AbstractCRUDController
Replaced incorrect service definitions in AbstractCRUDController to ensure proper dependency injection. Specifically, fixed retrieval of the ManagerRegistry and Validator services to resolve CalendarRange save errors (Issue #362). No schema changes were introduced.
2025-03-04 23:02:19 +01:00
f638ce71fd Merge branch '363-display_actions_by_issue' into 'master'
Resolve "Change display of social issues and social actions"

Closes #363

See merge request Chill-Projet/chill-bundles!802
2025-03-04 14:40:48 +00:00
b39997f00a Resolve "Change display of social issues and social actions" 2025-03-04 14:40:48 +00:00
38b21a2159 Update chill to version v3.9.2 2025-02-27 16:20:03 +01:00
17db571244 Add changie and linter fixes 2025-02-27 16:19:21 +01:00
8c8c16c1a1 use fetchResult instead of makeFetch for acpw creation 2025-02-27 16:16:54 +01:00
046d3ec9f1 use fetchResult instead of makeFetch for SocialIssuesAcc.vue component 2025-02-27 16:12:00 +01:00
596833f1a5 use fetchResult instead of makeFetch 2025-02-27 15:58:53 +01:00
66e4bab558 Fix pipeline phpunit and lint 2025-02-27 13:50:35 +01:00
d1c9926bb1 Update bundles to version v3.9.1 2025-02-27 12:21:26 +01:00
a71d066765 Fix post/path requests with missing 'type' property for gender 2025-02-27 12:19:10 +01:00
217ac7b9e7 Linter fixes 2025-02-27 11:54:48 +01:00
be901822bc Update chill version to v3.9.0 2025-02-27 11:26:25 +01:00
2dcce7b826 Remove dumps 2025-02-25 15:29:45 +01:00
dcd1777a70 Pass content of the textEditor to the symfony form 2025-02-17 16:25:17 +01:00
a6eb28175a Revert backend to original state and load commentEditor vue app anytime CommentType form field is used 2025-02-17 14:13:07 +01:00
7d78512823 Change symfony form back to original 2025-02-17 13:11:03 +01:00
d0cd4792d6 Fix dynamicEntityPicker: declare variables 2025-02-13 15:02:06 +01:00
6d196ead94 Merge branch '321-text-editor' of https://gitlab.com/Chill-Projet/chill-bundles into 321-text-editor 2025-02-13 14:49:51 +01:00
4047d5fd5b WIP Allow for comment content to be submitted to backend 2025-02-13 14:49:33 +01:00
9aac80d834 Change logic to allow as many comment vue apps to be charged on same page as needed 2025-02-13 14:49:33 +01:00
7560dc57c6 Remove comment editor component from Activity vue app 2025-02-13 14:49:33 +01:00
10314845f6 Turn component into a small vue app and add public private comment logic 2025-02-13 14:49:33 +01:00
9b84bc4d69 Use update ckeditor5 2025-02-13 14:49:33 +01:00
a2fcf039be Refactor CKEditor integration across application
Replaced `ClassicEditor` with a consistent `classicEditor` and added centralized editor configuration using `editorConfig`. Simplified webpack configuration by removing CKEditor-specific setups and dependencies, improving maintainability.
2025-02-13 14:49:33 +01:00
b4d887a372 upgrade ckeditor 2025-02-13 14:49:32 +01:00
0aaa7122da upgrade ckeditor/vue 2025-02-13 14:49:32 +01:00
1bc7f85874 Implement localStorage and toggling of simple/rich text editor 2025-02-13 14:49:32 +01:00
1d2fd000aa Setup new component for text editor 2025-02-13 14:49:32 +01:00
506df432b0 WIP Allow for comment content to be submitted to backend 2025-02-06 16:07:40 +01:00
c32c18b0e2 Change logic to allow as many comment vue apps to be charged on same page as needed 2025-02-06 15:10:49 +01:00
321d569ee9 Remove comment editor component from Activity vue app 2025-02-06 14:32:22 +01:00
cd40eb3932 Turn component into a small vue app and add public private comment logic 2025-02-06 12:46:19 +01:00
f0f2531fa3 Use update ckeditor5 2025-02-06 10:33:32 +01:00
183a220e7b Refactor CKEditor integration across application
Replaced `ClassicEditor` with a consistent `classicEditor` and added centralized editor configuration using `editorConfig`. Simplified webpack configuration by removing CKEditor-specific setups and dependencies, improving maintainability.
2025-02-06 10:33:32 +01:00
9df127a82c upgrade ckeditor 2025-02-06 10:33:32 +01:00
04a1412562 upgrade ckeditor/vue 2025-02-06 10:33:32 +01:00
3aef0a185e Implement localStorage and toggling of simple/rich text editor 2025-02-06 10:33:32 +01:00
578bce31b9 Setup new component for text editor 2025-02-06 10:33:32 +01:00
173 changed files with 1848 additions and 997 deletions

View File

@@ -1,6 +0,0 @@
kind: DX
body: Create an unique source of trust for translations
time: 2025-01-31T13:18:01.239211506+01:00
custom:
Issue: "333"
SchemaChange: No schema change

View 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

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Suggest all referrers within actions of the accompanying period when creating an activity
time: 2025-01-30T12:02:07.053587034+01:00
custom:
Issue: "349"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Add possibility to export a csv with all social issues and social actions
time: 2025-01-30T12:04:59.754998541+01:00
custom:
Issue: "343"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Restore document to previous kept version when a workflow is canceled
time: 2025-02-14T15:03:28.707250207+01:00
custom:
Issue: "360"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Add a list of third parties from within the admin (csv download)
time: 2025-02-19T12:09:28.487991703+01:00
custom:
Issue: "341"
SchemaChange: No schema change

View 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

View 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

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: fix generation of document with accompanying period context, and list of activities and works
time: 2025-02-14T12:10:10.920355454+01:00
custom:
Issue: ""
SchemaChange: No schema change

View 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

View 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

View 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

View 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

View 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

6
.changes/v3.10.0.md Normal file
View File

@@ -0,0 +1,6 @@
## v3.10.0 - 2025-03-17
### Feature
* ([#363](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/363)) Display social actions grouped per social issue within activity form
### Fixed
* ([#362](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/362)) Fix Dependency Injection, which prevented to save the CalendarRange
* ([#368](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/368)) fix search query for user groups

3
.changes/v3.10.1.md Normal file
View File

@@ -0,0 +1,3 @@
## v3.10.1 - 2025-03-17
### DX
* Remove yarn dependency to symfony/ux-translator, to ease the build process

3
.changes/v3.10.2.md Normal file
View File

@@ -0,0 +1,3 @@
## v3.10.2 - 2025-03-17
### Fixed
* Replace a ts-expect-error with a ts-ignore

3
.changes/v3.10.3.md Normal file
View File

@@ -0,0 +1,3 @@
## v3.10.3 - 2025-03-18
### DX
* Eslint fixes

19
.changes/v3.11.0.md Normal file
View 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

10
.changes/v3.9.0.md Normal file
View File

@@ -0,0 +1,10 @@
## v3.9.0 - 2025-02-27
### Feature
* ([#349](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/349)) Suggest all referrers within actions of the accompanying period when creating an activity
* ([#343](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/343)) Add possibility to export a csv with all social issues and social actions
* ([#360](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/360)) Restore document to previous kept version when a workflow is canceled
* ([#341](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/341)) Add a list of third parties from within the admin (csv download)
### Fixed
* fix generation of document with accompanying period context, and list of activities and works
### DX
* ([#333](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/333)) Create an unique source of trust for translations

3
.changes/v3.9.1.md Normal file
View File

@@ -0,0 +1,3 @@
## v3.9.1 - 2025-02-27
### Fixed
* Fix post/patch request with missing 'type' property for gender

3
.changes/v3.9.2.md Normal file
View File

@@ -0,0 +1,3 @@
## v3.9.2 - 2025-02-27
### Fixed
* Use fetchResults method to fetch all social issues instead of only the first page

1
.gitignore vendored
View File

@@ -13,6 +13,7 @@ docker/rabbitmq/data
# in this development bundle, we want to ignore directories related to a real app
assets/*
!assets/translator.ts
!assets/ux-translator
migrations/*
templates/*
translations/*

View File

@@ -6,6 +6,44 @@ 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
## v3.10.1 - 2025-03-17
### DX
* Remove yarn dependency to symfony/ux-translator, to ease the build process
## v3.10.0 - 2025-03-17
### Feature
* ([#363](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/363)) Display social actions grouped per social issue within activity form
### Fixed
* ([#362](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/362)) Fix Dependency Injection, which prevented to save the CalendarRange
* ([#368](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/368)) fix search query for user groups
## v3.9.2 - 2025-02-27
### Fixed
* Use fetchResults method to fetch all social issues instead of only the first page
## v3.9.1 - 2025-02-27
### Fixed
* Fix post/patch request with missing 'type' property for gender
## v3.9.0 - 2025-02-27
### Feature
* ([#349](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/349)) Suggest all referrers within actions of the accompanying period when creating an activity
* ([#343](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/343)) Add possibility to export a csv with all social issues and social actions
* ([#360](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/360)) Restore document to previous kept version when a workflow is canceled
* ([#341](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/341)) Add a list of third parties from within the admin (csv download)
### Fixed
* fix generation of document with accompanying period context, and list of activities and works
### DX
* ([#333](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/333)) Create an unique source of trust for translations
## v3.8.2 - 2025-02-10
### Fixed
* ([#358](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/358)) Remove "filter" button on list of documents in the workflow's "add attachement" modal

View File

@@ -1,9 +1,7 @@
// @ts-ignore Cannot find module (when used within an app)
import { trans, getLocale, setLocale, setLocaleFallbacks } from "@symfony/ux-translator";
import { trans, setLocale, setLocaleFallbacks } from "./ux-translator";
setLocaleFallbacks({"en": "fr", "nl": "fr", "fr": "en"});
setLocale('fr');
export { trans };
// @ts-ignore Cannot find module (when used within an app)
export * from '../var/translations';

View File

@@ -0,0 +1,3 @@
This directory import the symfony ux-translator files directly into chill-bundles.
This remove the yarn dependencies from the real package, which breaks our installation.

View File

@@ -0,0 +1 @@
export declare function format(id: string, parameters: Record<string, string | number>, locale: string): string;

View File

@@ -0,0 +1 @@
export declare function formatIntl(id: string, parameters: Record<string, string | number>, locale: string): string;

View File

@@ -0,0 +1,27 @@
export type DomainType = string;
export type LocaleType = string;
export type TranslationsType = Record<DomainType, {
parameters: ParametersType;
}>;
export type NoParametersType = Record<string, never>;
export type ParametersType = Record<string, string | number | Date> | NoParametersType;
export type RemoveIntlIcuSuffix<T> = T extends `${infer U}+intl-icu` ? U : T;
export type DomainsOf<M> = M extends Message<infer Translations, LocaleType> ? keyof Translations : never;
export type LocaleOf<M> = M extends Message<TranslationsType, infer Locale> ? Locale : never;
export type ParametersOf<M, D extends DomainType> = M extends Message<infer Translations, LocaleType> ? Translations[D] extends {
parameters: infer Parameters;
} ? Parameters : never : never;
export interface Message<Translations extends TranslationsType, Locale extends LocaleType> {
id: string;
translations: {
[domain in DomainType]: {
[locale in Locale]: string;
};
};
}
export declare function setLocale(locale: LocaleType | null): void;
export declare function getLocale(): LocaleType;
export declare function throwWhenNotFound(enabled: boolean): void;
export declare function setLocaleFallbacks(localeFallbacks: Record<LocaleType, LocaleType>): void;
export declare function getLocaleFallbacks(): Record<LocaleType, LocaleType>;
export declare function trans<M extends Message<TranslationsType, LocaleType>, D extends DomainsOf<M>, P extends ParametersOf<M, D>>(...args: P extends NoParametersType ? [message: M, parameters?: P, domain?: RemoveIntlIcuSuffix<D>, locale?: LocaleOf<M>] : [message: M, parameters: P, domain?: RemoveIntlIcuSuffix<D>, locale?: LocaleOf<M>]): string;

View File

@@ -0,0 +1 @@
export * from './translator';

View File

@@ -0,0 +1,283 @@
import { IntlMessageFormat } from 'intl-messageformat';
function strtr(string, replacePairs) {
const regex = Object.entries(replacePairs).map(([from]) => {
return from.replace(/([-[\]{}()*+?.\\^$|#,])/g, '\\$1');
});
if (regex.length === 0) {
return string;
}
return string.replace(new RegExp(regex.join('|'), 'g'), (matched) => replacePairs[matched].toString());
}
function format(id, parameters, locale) {
if (null === id || '' === id) {
return '';
}
if (typeof parameters['%count%'] === 'undefined' || Number.isNaN(parameters['%count%'])) {
return strtr(id, parameters);
}
const number = Number(parameters['%count%']);
let parts = [];
if (/^\|+$/.test(id)) {
parts = id.split('|');
}
else {
parts = id.match(/(?:\|\||[^|])+/g) || [];
}
const intervalRegex = /^(?<interval>({\s*(-?\d+(\.\d+)?[\s*,\s*\-?\d+(.\d+)?]*)\s*})|(?<left_delimiter>[[\]])\s*(?<left>-Inf|-?\d+(\.\d+)?)\s*,\s*(?<right>\+?Inf|-?\d+(\.\d+)?)\s*(?<right_delimiter>[[\]]))\s*(?<message>.*?)$/s;
const standardRules = [];
for (let part of parts) {
part = part.trim().replace(/\|\|/g, '|');
const matches = part.match(intervalRegex);
if (matches) {
const matchGroups = matches.groups || {};
if (matches[2]) {
for (const n of matches[3].split(',')) {
if (number === Number(n)) {
return strtr(matchGroups.message, parameters);
}
}
}
else {
const leftNumber = '-Inf' === matchGroups.left ? Number.NEGATIVE_INFINITY : Number(matchGroups.left);
const rightNumber = ['Inf', '+Inf'].includes(matchGroups.right)
? Number.POSITIVE_INFINITY
: Number(matchGroups.right);
if (('[' === matchGroups.left_delimiter ? number >= leftNumber : number > leftNumber) &&
(']' === matchGroups.right_delimiter ? number <= rightNumber : number < rightNumber)) {
return strtr(matchGroups.message, parameters);
}
}
}
else {
const ruleMatch = part.match(/^\w+:\s*(.*?)$/);
standardRules.push(ruleMatch ? ruleMatch[1] : part);
}
}
const position = getPluralizationRule(number, locale);
if (typeof standardRules[position] === 'undefined') {
if (1 === parts.length && typeof standardRules[0] !== 'undefined') {
return strtr(standardRules[0], parameters);
}
throw new Error(`Unable to choose a translation for "${id}" with locale "${locale}" for value "${number}". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %count% apples").`);
}
return strtr(standardRules[position], parameters);
}
function getPluralizationRule(number, locale) {
number = Math.abs(number);
let _locale = locale;
if (locale === 'pt_BR' || locale === 'en_US_POSIX') {
return 0;
}
_locale = _locale.length > 3 ? _locale.substring(0, _locale.indexOf('_')) : _locale;
switch (_locale) {
case 'af':
case 'bn':
case 'bg':
case 'ca':
case 'da':
case 'de':
case 'el':
case 'en':
case 'en_US_POSIX':
case 'eo':
case 'es':
case 'et':
case 'eu':
case 'fa':
case 'fi':
case 'fo':
case 'fur':
case 'fy':
case 'gl':
case 'gu':
case 'ha':
case 'he':
case 'hu':
case 'is':
case 'it':
case 'ku':
case 'lb':
case 'ml':
case 'mn':
case 'mr':
case 'nah':
case 'nb':
case 'ne':
case 'nl':
case 'nn':
case 'no':
case 'oc':
case 'om':
case 'or':
case 'pa':
case 'pap':
case 'ps':
case 'pt':
case 'so':
case 'sq':
case 'sv':
case 'sw':
case 'ta':
case 'te':
case 'tk':
case 'ur':
case 'zu':
return 1 === number ? 0 : 1;
case 'am':
case 'bh':
case 'fil':
case 'fr':
case 'gun':
case 'hi':
case 'hy':
case 'ln':
case 'mg':
case 'nso':
case 'pt_BR':
case 'ti':
case 'wa':
return number < 2 ? 0 : 1;
case 'be':
case 'bs':
case 'hr':
case 'ru':
case 'sh':
case 'sr':
case 'uk':
return 1 === number % 10 && 11 !== number % 100
? 0
: number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20)
? 1
: 2;
case 'cs':
case 'sk':
return 1 === number ? 0 : number >= 2 && number <= 4 ? 1 : 2;
case 'ga':
return 1 === number ? 0 : 2 === number ? 1 : 2;
case 'lt':
return 1 === number % 10 && 11 !== number % 100
? 0
: number % 10 >= 2 && (number % 100 < 10 || number % 100 >= 20)
? 1
: 2;
case 'sl':
return 1 === number % 100 ? 0 : 2 === number % 100 ? 1 : 3 === number % 100 || 4 === number % 100 ? 2 : 3;
case 'mk':
return 1 === number % 10 ? 0 : 1;
case 'mt':
return 1 === number
? 0
: 0 === number || (number % 100 > 1 && number % 100 < 11)
? 1
: number % 100 > 10 && number % 100 < 20
? 2
: 3;
case 'lv':
return 0 === number ? 0 : 1 === number % 10 && 11 !== number % 100 ? 1 : 2;
case 'pl':
return 1 === number
? 0
: number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 12 || number % 100 > 14)
? 1
: 2;
case 'cy':
return 1 === number ? 0 : 2 === number ? 1 : 8 === number || 11 === number ? 2 : 3;
case 'ro':
return 1 === number ? 0 : 0 === number || (number % 100 > 0 && number % 100 < 20) ? 1 : 2;
case 'ar':
return 0 === number
? 0
: 1 === number
? 1
: 2 === number
? 2
: number % 100 >= 3 && number % 100 <= 10
? 3
: number % 100 >= 11 && number % 100 <= 99
? 4
: 5;
default:
return 0;
}
}
function formatIntl(id, parameters, locale) {
if (id === '') {
return '';
}
const intlMessage = new IntlMessageFormat(id, [locale.replace('_', '-')], undefined, { ignoreTag: true });
parameters = { ...parameters };
Object.entries(parameters).forEach(([key, value]) => {
if (key.includes('%') || key.includes('{')) {
delete parameters[key];
parameters[key.replace(/[%{} ]/g, '').trim()] = value;
}
});
return intlMessage.format(parameters);
}
let _locale = null;
let _localeFallbacks = {};
let _throwWhenNotFound = false;
function setLocale(locale) {
_locale = locale;
}
function getLocale() {
return (_locale ||
document.documentElement.getAttribute('data-symfony-ux-translator-locale') ||
(document.documentElement.lang ? document.documentElement.lang.replace('-', '_') : null) ||
'en');
}
function throwWhenNotFound(enabled) {
_throwWhenNotFound = enabled;
}
function setLocaleFallbacks(localeFallbacks) {
_localeFallbacks = localeFallbacks;
}
function getLocaleFallbacks() {
return _localeFallbacks;
}
function trans(message, parameters = {}, domain = 'messages', locale = null) {
if (typeof domain === 'undefined') {
domain = 'messages';
}
if (typeof locale === 'undefined' || null === locale) {
locale = getLocale();
}
if (typeof message.translations === 'undefined') {
return message.id;
}
const localesFallbacks = getLocaleFallbacks();
const translationsIntl = message.translations[`${domain}+intl-icu`];
if (typeof translationsIntl !== 'undefined') {
while (typeof translationsIntl[locale] === 'undefined') {
locale = localesFallbacks[locale];
if (!locale) {
break;
}
}
if (locale) {
return formatIntl(translationsIntl[locale], parameters, locale);
}
}
const translations = message.translations[domain];
if (typeof translations !== 'undefined') {
while (typeof translations[locale] === 'undefined') {
locale = localesFallbacks[locale];
if (!locale) {
break;
}
}
if (locale) {
return format(translations[locale], parameters, locale);
}
}
if (_throwWhenNotFound) {
throw new Error(`No translation message found with id "${message.id}".`);
}
return message.id;
}
export { getLocale, getLocaleFallbacks, setLocale, setLocaleFallbacks, throwWhenNotFound, trans };

1
assets/ux-translator/dist/utils.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export declare function strtr(string: string, replacePairs: Record<string, string | number>): string;

View File

@@ -0,0 +1,34 @@
{
"name": "@symfony/ux-translator",
"description": "Symfony Translator for JavaScript",
"license": "MIT",
"version": "1.0.0",
"main": "dist/translator_controller.js",
"types": "dist/translator_controller.d.ts",
"scripts": {
"build": "node ../../../bin/build_package.js .",
"watch": "node ../../../bin/build_package.js . --watch",
"test": "../../../bin/test_package.sh .",
"check": "biome check",
"ci": "biome ci"
},
"symfony": {
"importmap": {
"intl-messageformat": "^10.5.11",
"@symfony/ux-translator": "path:%PACKAGE%/dist/translator_controller.js",
"@app/translations": "path:var/translations/index.js",
"@app/translations/configuration": "path:var/translations/configuration.js"
}
},
"peerDependencies": {
"intl-messageformat": "^10.5.11"
},
"peerDependenciesMeta": {
"intl-messageformat": {
"optional": false
}
},
"devDependencies": {
"intl-messageformat": "^10.5.11"
}
}

View File

@@ -220,6 +220,7 @@ framework:
- attenteModification
- attenteMiseEnForme
- attenteValidationMiseEnForme
- attenteSignature
- attenteVisa
- postSignature
- attenteTraitement

View File

@@ -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"
},
},
);

View File

@@ -59,6 +59,7 @@
"bootstrap-icons": "^1.11.3",
"dropzone": "^5.7.6",
"es6-promise": "^4.2.8",
"intl-messageformat": "^10.5.11",
"leaflet": "^1.7.1",
"marked": "^12.0.2",
"masonry-layout": "^4.2.2",

View File

@@ -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]);
}
}

View File

@@ -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]);
}
}

View File

@@ -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;
}
}

View File

@@ -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,

View File

@@ -68,14 +68,23 @@
socialActionsSelected.length)
"
>
<check-social-action
v-for="action in socialActionsList"
:key="action.id"
:action="action"
:selection="socialActionsSelected"
@updateSelected="updateActionsSelected"
<div
id="actionsList"
v-for="group in socialActionsList"
:key="group.issue"
>
</check-social-action>
<span class="badge bg-chill-l-gray text-dark">{{
group.issue
}}</span>
<check-social-action
v-for="action in group.actions"
:key="action.id"
:action="action"
:selection="socialActionsSelected"
@updateSelected="updateActionsSelected"
>
</check-social-action>
</div>
</template>
<span
@@ -149,53 +158,44 @@ export default {
},
},
mounted() {
/* Load others issues in multiselect
*/
/* Load other issues in multiselect */
this.issueIsLoading = true;
this.actionAreLoaded = false;
getSocialIssues().then(
(response) =>
new Promise((resolve) => {
this.$store.commit("updateIssuesOther", response.results);
/* Add in list the issues already associated (if not yet listed)
*/
this.socialIssuesSelected.forEach((issue) => {
if (
this.socialIssuesList.filter(
(i) => i.id === issue.id,
).length !== 1
) {
this.$store.commit("addIssueInList", issue);
}
}, this);
getSocialIssues().then((response) => {
/* Add issues to the store */
this.$store.commit("updateIssuesOther", response);
/* Remove from multiselect the issues that are not yet in checkbox list
*/
this.socialIssuesList.forEach((issue) => {
this.$store.commit("removeIssueInOther", issue);
}, this);
/* Add in list the issues already associated (if not yet listed) */
this.socialIssuesSelected.forEach((issue) => {
if (
this.socialIssuesList.filter((i) => i.id === issue.id)
.length !== 1
) {
this.$store.commit("addIssueInList", issue);
}
});
/* Filter issues
*/
this.$store.commit("filterList", "issues");
/* Remove from multiselect the issues that are not yet in the checkbox list */
this.socialIssuesList.forEach((issue) => {
this.$store.commit("removeIssueInOther", issue);
});
/* Add in list the actions already associated (if not yet listed)
*/
this.socialActionsSelected.forEach((action) => {
this.$store.commit("addActionInList", action);
}, this);
/* Filter issues */
this.$store.commit("filterList", "issues");
/* Filter issues
*/
this.$store.commit("filterList", "actions");
/* Add in list the actions already associated (if not yet listed) */
this.socialActionsSelected.forEach((action) => {
this.$store.commit("addActionInList", action);
});
this.issueIsLoading = false;
this.actionAreLoaded = true;
this.updateActionsList();
resolve();
}),
);
/* Filter actions */
this.$store.commit("filterList", "actions");
this.issueIsLoading = false;
this.actionAreLoaded = true;
this.updateActionsList();
});
},
methods: {
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
@@ -258,7 +258,23 @@ export default {
</script>
<style lang="scss" scoped>
@import "ChillMainAssets/module/bootstrap/shared";
@import "ChillPersonAssets/chill/scss/mixins";
@import "ChillMainAssets/chill/scss/chill_variables";
span.multiselect__single {
display: none !important;
}
#actionsList {
border-radius: 0.5rem;
padding: 1rem;
margin: 0.5rem;
background-color: whitesmoke;
}
span.badge {
margin-bottom: 0.5rem;
@include badge_social($social-issue-color);
}
</style>

View File

@@ -10,7 +10,9 @@
:value="action"
/>
<label class="form-check-label" :for="action.id">
<span class="badge bg-light text-dark">{{ action.text }}</span>
<span class="badge bg-light text-dark" :title="action.text">{{
action.text
}}</span>
</label>
</div>
</span>
@@ -43,5 +45,9 @@ span.badge {
font-size: 95%;
margin-bottom: 5px;
margin-right: 1em;
max-width: 100%; /* Adjust as needed */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@@ -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
};
},
})

View File

@@ -124,9 +124,19 @@ const store = createStore({
);
},
socialActionsListSorted(state) {
return [...state.socialActionsList].sort(
(a, b) => a.ordering - b.ordering,
);
return [...state.socialActionsList]
.sort((a, b) => a.ordering - b.ordering)
.reduce((acc, action) => {
const issueText = action.issue?.text || "Uncategorized";
// Find if the group for the issue already exists
let group = acc.find((item) => item.issue === issueText);
if (!group) {
group = { issue: issueText, actions: [] };
acc.push(group);
}
group.actions.push(action);
return acc;
}, []);
},
},
mutations: {

View File

@@ -126,4 +126,4 @@
{% block css %}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}
{% endblock %}

View File

@@ -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>&nbsp;
<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>&nbsp;
</div>
{% endif %}
</div>
</div>
</div>

View File

@@ -15,7 +15,6 @@ use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -24,10 +23,7 @@ use Symfony\Component\Routing\Annotation\Route;
class CalendarAPIController extends ApiController
{
public function __construct(private readonly CalendarRepository $calendarRepository, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
public function __construct(private readonly CalendarRepository $calendarRepository) {}
#[Route(path: '/api/1.0/calendar/calendar/by-user/{id}.{_format}', name: 'chill_api_single_calendar_list_by-user', requirements: ['_format' => 'json'])]
public function listByUser(User $user, Request $request, string $_format): JsonResponse

View File

@@ -15,7 +15,6 @@ use Chill\CalendarBundle\Repository\CalendarRangeRepository;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -24,10 +23,7 @@ use Symfony\Component\Routing\Annotation\Route;
class CalendarRangeAPIController extends ApiController
{
public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository) {}
#[Route(path: '/api/1.0/calendar/calendar-range-available/{id}.{_format}', name: 'chill_api_single_calendar_range_available', requirements: ['_format' => 'json'])]
public function availableRanges(User $user, Request $request, string $_format): JsonResponse

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -96,23 +96,23 @@
</div>
</div>
<FullCalendar :options="calendarOptions" ref="calendarRef">
<template v-slot:eventContent="arg: 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.timeText }} -
{{ 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,

View File

@@ -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>&nbsp;
<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>&nbsp;
</div>
{% endif %}
</div>
</div>
</div>

View File

@@ -14,7 +14,6 @@ namespace Chill\DocStoreBundle\Controller;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
@@ -28,10 +27,7 @@ class StoredObjectApiController extends ApiController
private readonly Security $security,
private readonly SerializerInterface $serializer,
private readonly EntityManagerInterface $entityManager,
ManagerRegistry $managerRegistry,
) {
parent::__construct($managerRegistry);
}
) {}
/**
* Creates a new stored object.

View File

@@ -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);

View File

@@ -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,33 +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="
userSignatureZone === null ||
userSignatureZone?.index < 1
"
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="
userSignatureZone?.index >= signature.zones.length - 1
"
: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'">
@@ -117,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"
@@ -127,7 +135,7 @@
@click="undoSign"
v-else
>
{{ $t("cancel") }}
{{ trans(SIGNATURES_CANCEL) }}
</button>
<button
v-if="userSignatureZone === null"
@@ -139,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
@@ -191,58 +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="
userSignatureZone === null ||
userSignatureZone?.index < 1
"
class="btn btn-light btn-sm"
@click="turnSignature(-1)"
@click="goToSignatureZoneUnique"
>
{{ $t("last_zone") }}
</button>
<span>|</span>
<button
:disabled="
userSignatureZone?.index >= signature.zones.length - 1
"
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="
userSignatureZone === null ||
userSignatureZone?.index < 1
"
:disabled="isFirstSignatureZone()"
class="btn btn-light btn-sm"
@click="turnSignature(-1)"
>
{{ $t("last_sign_zone") }}
{{ trans(SIGNATURES_LAST_ZONE) }}
</button>
<span>|</span>
<button
:disabled="
userSignatureZone?.index >= signature.zones.length - 1
"
: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'">
@@ -252,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"
@@ -260,7 +280,7 @@
@click="undoSign"
v-else
>
{{ $t("cancel") }}
{{ trans(SIGNATURES_CANCEL) }}
</button>
<button
v-if="userSignatureZone === null"
@@ -272,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"
@@ -312,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'">
@@ -324,7 +344,7 @@
:disabled="!userSignatureZone"
@click="sign"
>
{{ $t("sign") }}
{{ trans(SIGNATURES_SIGN) }}
</button>
</div>
<div class="col-4" v-else></div>
@@ -333,7 +353,7 @@
</template>
<script setup lang="ts">
import { ref, Ref, reactive } from "vue";
import { ref, Ref } from "vue";
import { useToast } from "vue-toast-notification";
import "vue-toast-notification/dist/theme-sugar.css";
import {
@@ -344,25 +364,47 @@ 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";
// @ts-ignore
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); // incredible but this is needed
console.log(PdfWorker);
// import { PdfWorker } from 'pdfjs-dist/build/pdf.worker.mjs'
// pdfjsLib.GlobalWorkerOptions.workerSrc = PdfWorker;
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import {
download_and_decrypt_doc,
download_doc_as_pdf,
} from "../StoredObjectButton/helpers";
import { download_doc_as_pdf } from "../StoredObjectButton/helpers";
pdfjsLib.GlobalWorkerOptions.workerSrc = "pdfjs-dist/build/pdf.worker.mjs";
@@ -433,6 +475,16 @@ const $toast = useToast();
const signature = window.signature;
const isFirstSignatureZone = () =>
userSignatureZone.value?.index != null
? userSignatureZone.value.index < 1
: false;
const isLastSignatureZone = () =>
userSignatureZone.value?.index
? userSignatureZone.value.index >= signature.zones.length - 1
: false;
const setZoomLevel = async (zoomLevel: string) => {
zoom.value = Number.parseFloat(zoomLevel);
await resetPages();
@@ -604,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) {
@@ -616,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);
}
};
@@ -754,7 +812,7 @@ const confirmSign = () => {
zone: userSignatureZone.value,
};
makeFetch("POST", url, body)
.then((r) => {
.then(() => {
checkForReady();
})
.catch((error) => {
@@ -776,9 +834,7 @@ const undoSign = async () => {
};
const toggleAddZone = () => {
canvasEvent.value === "select"
? (canvasEvent.value = "add")
: (canvasEvent.value = "select");
canvasEvent.value = canvasEvent.value === "select" ? "add" : "select";
};
const addZoneEvent = async (e: PointerEvent, canvas: HTMLCanvasElement) => {

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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 => {

View File

@@ -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"

View File

@@ -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>&nbsp;#{{ version.version + 1 }}&nbsp;</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

View File

@@ -28,6 +28,10 @@ const open = () => {
state.opened = true;
};
const onRestoreVersion = (payload: {
newVersion: StoredObjectVersionWithPointInTime;
}) => emit("restoreVersion", payload);
defineExpose({ open });
</script>
<template>
@@ -42,9 +46,7 @@ defineExpose({ open });
:versions="props.versions"
:can-edit="canEdit"
:stored-object="storedObject"
@restore-version="
(payload) => emit('restoreVersion', payload)
"
@restore-version="onRestoreVersion"
></history-button-list>
</template>
</modal>

View File

@@ -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>

View File

@@ -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>&nbsp;
<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>&nbsp;
</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>&nbsp;
</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>&nbsp;
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
{% if document.description is not empty %}
<div class="item-row">

View File

@@ -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);

View File

@@ -14,7 +14,6 @@ namespace Chill\DocStoreBundle\Tests\Controller;
use Chill\DocStoreBundle\Controller\StoredObjectApiController;
use Chill\DocStoreBundle\Entity\StoredObject;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Security;
@@ -46,9 +45,7 @@ class StoredObjectApiControllerTest extends TestCase
{"type": "stored-object", "id": 1}
JSON);
$managerRegistry = $this->createMock(ManagerRegistry::class);
$controller = new StoredObjectApiController($security, $serializer, $entityManager, $managerRegistry);
$controller = new StoredObjectApiController($security, $serializer, $entityManager);
$actual = $controller->createStoredObject();

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -39,8 +39,6 @@ abstract class AbstractCRUDController extends AbstractController
*/
protected array $crudConfig = [];
public function __construct(protected ManagerRegistry $managerRegistry) {}
/**
* get the role given from the config.
*/
@@ -65,7 +63,6 @@ abstract class AbstractCRUDController extends AbstractController
parent::getSubscribedServices(),
[
'chill_main.paginator_factory' => PaginatorFactory::class,
ManagerRegistry::class => ManagerRegistry::class,
'translator' => TranslatorInterface::class,
AuthorizationHelper::class => AuthorizationHelper::class,
EventDispatcherInterface::class => EventDispatcherInterface::class,
@@ -215,7 +212,7 @@ abstract class AbstractCRUDController extends AbstractController
protected function getManagerRegistry(): ManagerRegistry
{
return $this->managerRegistry;
return $this->container->get('doctrine');
}
/**
@@ -228,7 +225,7 @@ abstract class AbstractCRUDController extends AbstractController
protected function getValidator(): ValidatorInterface
{
return $this->get('validator');
return $this->container->get('validator');
}
/**

View File

@@ -13,7 +13,6 @@ namespace Chill\MainBundle\CRUD\Controller;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -24,11 +23,6 @@ use Symfony\Component\Validator\ConstraintViolationListInterface;
class ApiController extends AbstractCRUDController
{
public function __construct(ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
/**
* Base method for handling api action.
*

View File

@@ -13,7 +13,6 @@ namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\Address;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
@@ -21,10 +20,7 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
class AddressApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
public function __construct(private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry) {}
/**
* Duplicate an existing address.

View File

@@ -17,7 +17,6 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Repository\AddressReferenceRepository;
use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -27,10 +26,7 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
final class AddressReferenceAPIController extends ApiController
{
public function __construct(private readonly AddressReferenceRepository $addressReferenceRepository, private readonly PaginatorFactory $paginatorFactory, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
public function __construct(private readonly AddressReferenceRepository $addressReferenceRepository, private readonly PaginatorFactory $paginatorFactory) {}
#[Route(path: '/api/1.0/main/address-reference/by-postal-code/{id}/search.json')]
public function search(PostalCode $postalCode, Request $request): JsonResponse

View File

@@ -13,16 +13,10 @@ namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
class CivilityApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
{
return $query->addOrderBy('e.order', 'ASC');

View File

@@ -12,12 +12,5 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
class CountryApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
}
class CountryApiController extends ApiController {}

View File

@@ -13,16 +13,10 @@ namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
class GenderApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void
{
$query

View File

@@ -12,12 +12,5 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
class GeographicalUnitApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
}
class GeographicalUnitApiController extends ApiController {}

View File

@@ -14,7 +14,6 @@ namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
/**
@@ -22,11 +21,6 @@ use Symfony\Component\HttpFoundation\Request;
*/
class LocationApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void
{
$query

View File

@@ -12,7 +12,6 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
/**
@@ -20,11 +19,6 @@ use Symfony\Component\HttpFoundation\Request;
*/
class LocationTypeApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
public function customizeQuery(string $action, Request $request, $query): void
{
$query->andWhere(

View File

@@ -16,7 +16,6 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\CountryRepository;
use Chill\MainBundle\Repository\PostalCodeRepositoryInterface;
use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -27,10 +26,7 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
final class PostalCodeAPIController extends ApiController
{
public function __construct(private readonly CountryRepository $countryRepository, private readonly PostalCodeRepositoryInterface $postalCodeRepository, private readonly PaginatorFactory $paginatorFactory, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
public function __construct(private readonly CountryRepository $countryRepository, private readonly PostalCodeRepositoryInterface $postalCodeRepository, private readonly PaginatorFactory $paginatorFactory) {}
#[Route(path: '/api/1.0/main/postal-code/search.json')]
public function search(Request $request): JsonResponse

View File

@@ -12,16 +12,10 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
class ScopeApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void
{
if ('_index' === $action) {

View File

@@ -15,7 +15,6 @@ use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Security\ChillSecurity;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@@ -23,10 +22,7 @@ use Symfony\Component\Routing\Annotation\Route;
class UserApiController extends ApiController
{
public function __construct(private readonly ChillSecurity $security, ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
public function __construct(private readonly ChillSecurity $security) {}
#[Route(path: '/api/1.0/main/user-current-location.{_format}', name: 'chill_main_user_current_location', requirements: ['_format' => 'json'])]
public function currentLocation(mixed $_format): JsonResponse

View File

@@ -12,12 +12,5 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
class UserGroupApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
}
class UserGroupApiController extends ApiController {}

View File

@@ -12,16 +12,10 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
class UserJobApiController extends ApiController
{
public function __construct(protected ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry);
}
protected function customizeQuery(string $action, Request $request, $query): void
{
if ('_index' === $action) {

View File

@@ -75,8 +75,8 @@ final class UserGroupRepository implements UserGroupRepositoryInterface, LocaleA
->setWhereClauses('
ug.active AND (
SIMILARITY(LOWER(UNACCENT(?)), ug.label->>?) > 0.15
OR ug.label->>? LIKE \'%\' || LOWER(UNACCENT(?)) || \'%\')
', [$pattern, $lang, $pattern, $lang]);
OR LOWER(UNACCENT(ug.label->>?)) LIKE \'%\' || LOWER(UNACCENT(?)) || \'%\')
', [$pattern, $lang, $lang, $pattern]);
return $query;
}

View File

@@ -33,6 +33,8 @@
@import './scss/hover.scss';
@import './scss/comment-editor.scss';
/*
* BASE LAYOUT POSITION
*/

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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",
};
} ;

View File

@@ -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'));

View File

@@ -10,12 +10,12 @@ 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) {
let suggested;
let as_id;
let submit_on_adding_new_entity;
let label;
const isMultiple = parseInt(el.dataset.multiple) === 1,
uniqId = el.dataset.uniqid,
input = element.querySelector(
@@ -26,12 +26,12 @@ function loadDynamicPicker(element) {
? JSON.parse(input.value)
: input.value === "[]" || input.value === ""
? null
: [JSON.parse(input.value)];
suggested = JSON.parse(el.dataset.suggested);
as_id = parseInt(el.dataset.asId) === 1;
submit_on_adding_new_entity =
parseInt(el.dataset.submitOnAddingNewEntity) === 1;
label = el.dataset.label;
: [JSON.parse(input.value)],
suggested = JSON.parse(el.dataset.suggested),
as_id = parseInt(el.dataset.asId) === 1,
submit_on_adding_new_entity =
parseInt(el.dataset.submitOnAddingNewEntity) === 1,
label = el.dataset.label;
if (!isMultiple) {
if (input.value === "[]") {

View File

@@ -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);
});
});

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 };

View File

@@ -45,6 +45,10 @@ const onPickGenericDoc = ({
}) => {
emit("pickGenericDoc", { genericDoc });
};
const onRemoveAttachment = (payload: { attachment: WorkflowAttachment }) => {
emit("removeAttachment", payload);
};
</script>
<template>
@@ -56,7 +60,7 @@ const onPickGenericDoc = ({
></pick-generic-doc-modal>
<attachment-list
:attachments="props.attachments"
@removeAttachment="(payload) => emit('removeAttachment', payload)"
@removeAttachment="onRemoveAttachment"
></attachment-list>
<ul class="record_actions">
<li>

View File

@@ -72,6 +72,14 @@ const placeTrans = (str: string): string => {
}
};
const onPickDocument = (payload: {
genericDoc: GenericDocForAccompanyingPeriod;
}) => emit("pickGenericDoc", payload);
const onRemoveGenericDoc = (payload: {
genericDoc: GenericDocForAccompanyingPeriod;
}) => emit("removeGenericDoc", payload);
const filteredDocuments = computed<GenericDocForAccompanyingPeriod[]>(() => {
if (false === loaded.value) {
return [];
@@ -245,10 +253,8 @@ const filteredDocuments = computed<GenericDocForAccompanyingPeriod[]>(() => {
:accompanying-period-id="accompanyingPeriodId"
:genericDoc="g"
:is-picked="isPicked(g)"
@pickGenericDoc="(payload) => emit('pickGenericDoc', payload)"
@removeGenericDoc="
(payload) => emit('removeGenericDoc', payload)
"
@pickGenericDoc="onPickDocument"
@removeGenericDoc="onRemoveGenericDoc"
></pick-generic-doc-item>
</div>
<div v-else class="text-center chill-no-data-statement">

View File

@@ -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";

View File

@@ -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>

View File

@@ -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"
}
},
};

View File

@@ -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 %}

View File

@@ -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 %}

Some files were not shown because too many files have changed in this diff Show More