Compare commits

...

128 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
7ed10efcd1 Fix declaration of variables for dynamic picker 2025-02-24 13:49:33 +01:00
350661a4fa Merge branch '341-export-csv-thirdparties' into 'master'
Resolve "Extraction de la liste des tiers au format CSV"

Closes #341

See merge request Chill-Projet/chill-bundles!789
2025-02-19 11:37:46 +00:00
08207b656a Resolve "Extraction de la liste des tiers au format CSV" 2025-02-19 11:37:46 +00:00
8de63de6d6 Merge branch '343-social_issue_action_export' into 'master'
Resolve "Permettre de télécharger la liste des problématiques et la liste des actions en CSV"

Closes #343

See merge request Chill-Projet/chill-bundles!785
2025-02-19 11:18:05 +00:00
51804b10c0 Resolve "Permettre de télécharger la liste des problématiques et la liste des actions en CSV" 2025-02-19 11:18:04 +00:00
02f555efae Merge branch '333-working-with-translations' into 'master'
Setup alias for use in standalone chill-bundles project and replace relative paths

Closes #333

See merge request Chill-Projet/chill-bundles!766
2025-02-19 10:57:59 +00:00
d2fcb6945b Setup alias for use in standalone chill-bundles project and replace relative paths 2025-02-19 10:57:59 +00: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
c89e3785ef Merge branch '360-restore-document-on-workflow-cancel' into 'master'
Add event subscriber for document restoration on cancel

Closes #360

See merge request Chill-Projet/chill-bundles!798
2025-02-17 12:22:57 +00:00
9f17ec4841 Merge branch '353-doc_require_to_populate_languages' into 'master'
doc: add languages population command

Closes #353

See merge request Chill-Projet/chill-bundles!792
2025-02-17 12:22:25 +00:00
b277a7749a Merge branch '357-doc_address-ref-requires-language' into 'master'
doc: chill:main:address-ref-from-best-addresse requires a language code

Closes #357

See merge request Chill-Projet/chill-bundles!793
2025-02-17 12:21:48 +00:00
c8b6b6e33a Merge branch '349-suggest-agents-traitants' into 'master'
Resolve "Proposer en plus du référent de parcours les agents traitants saisis dans toutes les cations d'accompagnement du parcours"

Closes #349

See merge request Chill-Projet/chill-bundles!787
2025-02-17 12:21:10 +00:00
10eaebf610 Resolve "Proposer en plus du référent de parcours les agents traitants saisis dans toutes les cations d'accompagnement du parcours" 2025-02-17 12:21:10 +00:00
7d78512823 Change symfony form back to original 2025-02-17 13:11:03 +01:00
0a34f9086f Add event subscriber for document restoration on cancel
Implement an event subscriber to restore documents to their last kept version when a workflow transition ends in a non-positive final state. Includes corresponding unit tests and an unreleased feature change log entry.
2025-02-14 15:08:42 +01:00
739e0b1692 Merge branch 'fix/docgen-accompanying-period-context-with-list-aworks' into 'master'
Refactor username mapping in activity context filter.

See merge request Chill-Projet/chill-bundles!797
2025-02-14 12:20:07 +00:00
8db8f5fdf5 Refactor username mapping in activity context filter.
Replaced array_map with a foreach loop for clarity and maintainability when extracting usernames from work referrers. This ensures better readability and aligns with coding standards.
2025-02-14 12:11:28 +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
fc32f9eca9 Merge branch 'add-dependents' into 'master'
add household properties :  number of dependents and number of dependents with disabilities

See merge request Chill-Projet/chill-bundles!778
2025-02-13 12:41:29 +00:00
ab35e8c034 Release v3.8.2 2025-02-10 14:52:22 +01:00
2aded2974f Merge branch '358-remove-filter-button' into 'master'
Remove "filter" button from attachment modal in workflows

Closes #358

See merge request Chill-Projet/chill-bundles!794
2025-02-10 13:49:09 +00:00
f84c1632b2 Remove "filter" button from attachment modal in workflows
The "filter" button was unnecessary in the document list within the "add attachment" modal and has been removed for simplicity. This change does not involve any schema modifications and resolves issue #358.
2025-02-10 14:41:53 +01:00
Christophe Siraut
03717a1a87 ChillPersonBundle: move numberOfDependents configuration to a new household node; extend AdministrativeStatusRepository and EmploymentStatusRepository from ServiceEntityRepository 2025-02-07 17:45:33 +01:00
Christophe Siraut
02c524dd79 doc: chill:main:address-ref-from-best-addresse requires a language code 2025-02-07 10:53:53 +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
99e4824137 Release bundles v.3.8.1 2025-02-05 18:14:22 +01:00
dacaaea235 Fix household link in parcours banner 2025-02-05 18:09:34 +01:00
Christophe Siraut
bc7f0907ab doc: add languages population command 2025-02-03 14:05:08 +01:00
Christophe Siraut
fbdc0d32f0 ChillPersonBundle: Add numberOfDependents and numberOfDependentsWithDisabilities 2025-01-31 10:13:47 +01:00
Christophe Siraut
5f31473c90 ChillMainBundle: optionnaly mask aggegators in ExportType 2025-01-31 09:37:46 +01:00
Christophe Siraut
98cf167040 ChillPersonBundle: add aggregators for administrative status and employment status 2025-01-31 09:37:46 +01:00
Christophe Siraut
6c37d798bf ChillPersonBundle: add administrativeStatus property to Person 2025-01-31 09:06:45 +01:00
207 changed files with 4428 additions and 10203 deletions

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

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

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

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

@@ -0,0 +1,3 @@
## v3.8.1 - 2025-02-05
### Fixed
* Fix household link in the parcours banner

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

@@ -0,0 +1,3 @@
## 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

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

2
.gitignore vendored
View File

@@ -12,6 +12,8 @@ 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

@@ -113,7 +113,7 @@ lint:
- export PATH="./node_modules/.bin:$PATH"
script:
- yarn install --ignore-optional
- npx eslint-baseline "**/*.{js,vue}"
- npx eslint-baseline "src/**/*.{js,ts,vue}"
cache:
paths:
- node_modules/

View File

@@ -6,6 +6,52 @@ 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
## v3.8.1 - 2025-02-05
### Fixed
* Fix household link in the parcours banner
## v3.8.0 - 2025-02-03
### Feature
* Improve the UX of the news item admin form to prevent wrong usage

7
assets/translator.ts Normal file
View File

@@ -0,0 +1,7 @@
import { trans, setLocale, setLocaleFallbacks } from "./ux-translator";
setLocaleFallbacks({"en": "fr", "nl": "fr", "fr": "en"});
setLocale('fr');
export { trans };
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

@@ -75,6 +75,7 @@
"symfony/templating": "^5.4",
"symfony/translation": "^5.4",
"symfony/twig-bundle": "^5.4",
"symfony/ux-translator": "^2.22",
"symfony/validator": "^5.4",
"symfony/webpack-encore-bundle": "^1.11",
"symfony/workflow": "^5.4",

View File

@@ -36,4 +36,5 @@ return [
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
];

View File

@@ -0,0 +1,3 @@
ux_translator:
# The directory where the JavaScript translations are dumped
dump_directory: '%kernel.project_dir%/var/translations'

View File

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

View File

@@ -29,8 +29,7 @@ We strongly encourage you to initialize a git repository at this step, to track
# add the flex endpoints required for custom recipes
cat <<< "$(jq '.extra.symfony += {"endpoint": ["flex://defaults", "https://gitlab.com/api/v4/projects/57371968/repository/files/index.json/raw?ref=main"]}' composer.json)" > composer.json
# install chill and some dependencies
# TODO fix the suffix "alpha1" and replace by ^3.0.0 when version 3.0.0 will be released
symfony composer require chill-project/chill-bundles v3.0.0-RC3 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev
symfony composer require chill-project/chill-bundles ^3.7.1 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev symfony/amqp-messenger
We encourage you to accept the inclusion of the "Docker configuration from recipes": this is the documented way to run the database.
You must also accept to configure recipes from the contrib repository, unless you want to configure the bundles manually).
@@ -48,7 +47,7 @@ You must also accept to configure recipes from the contrib repository, unless yo
If you encounter this error during assets compilation (:code:`yarn run encore production`) (repeated multiple times):
.. code-block:: txt
.. code-block::
[tsl] ERROR in /tmp/chill/v1/public/bundles/chillcalendar/types.ts(2,65)
TS2307: Cannot find module '../../../ChillMainBundle/Resources/public/types' or its corresponding type declarations.
@@ -74,14 +73,22 @@ or in the :code:`.env.local` file, which should not be committed to the git repo
You do not need to set variables for the smtp server, redis server and relatorio server, as they are generated automatically
by the symfony server, from the docker compose services.
The only required variable is the :code:`ADMIN_PASSWORD`. You can generate a hashed and salted admin password using the command
:code:`symfony console security:hash-password <your password> 'Symfony\Component\Security\Core\User\User'`. Then,
The required variables are:
- the :code:`ADMIN_PASSWORD`;
- the :code:`OVHCLOUD_DSN` variable;
:code:`ADMIN_PASSWORD`
^^^^^^^^^^^^^^^^^^^^^^
You can generate a hashed and salted admin password using the command
:code:`symfony console security:hash-password <your password> 'Symfony\Component\Security\Core\User\User'`.Then,
you can either:
- add this password to the :code:`.env.local` file, you must escape the character :code:`$`: if the generated password
is :code:`$2y$13$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm`, your :code:`.env.local` file will be:
.. code-block:: env
.. code-block:: bash
ADMIN_PASSWORD=\$2y\$13\$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm
# note: if you copy-paste the line above, the password will be "admin".
@@ -89,12 +96,24 @@ you can either:
- add the generated password to the secrets manager (**note**: you must add the generated hashed password to the secrets env,
not the password in clear text).
- set up the jwt authentication bundle
:code:`OVHCLOUD_DSN` and sending SMS messages
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is a temporary dependency, for ensuring compatibility for previous behaviour.
You can set it to :code:`null://null` if you do not plan to use sending SMS.
.. code-block:: bash
OVHCLOUD_DSN=null://null
If you plan to do it, you can configure the notifier component `as described in the symfony documentation <https://symfony.com/doc/current/notifier.html#notifier-sms-channel>`_.
Some environment variables are available for the JWT authentication bundle in the :code:`.env` file.
Prepare migrations and other tools
----------------------------------
Prepare database, messenger queue, and other configuration
----------------------------------------------------------
To continue the installation process, you will have to run migrations:
@@ -109,17 +128,22 @@ To continue the installation process, you will have to run migrations:
symfony console messenger:setup-transports
# prepare some views
symfony console chill:db:sync-views
# load languages data
symfony console chill:main:languages:populate
# generate jwt token, required for some api features (webdav access, ...)
symfony console lexik:jwt:generate-keypair
.. warning::
.. note::
If you encounter an error while running :code:`symfony console messenger:setup-transports`, you can set up the messenger
transport to redis, by adding this in the :code:`.env.local` or :code:`.env` file:
If you encounter this error:
.. code-block::
No transport supports the given Messenger DSN.
Please check that you installed the package `symfony/amqp-messenger`.
.. code-block:: env
MESSENGER_TRANSPORT_DSN=redis://${REDIS_HOST}:${REDIS_PORT}/messages
Start your web server locally
-----------------------------

View File

@@ -41,16 +41,18 @@ Postal code are loaded from this database. There is no need to load postal codes
The data are prepared for Chill (`See this repository <https://gitea.champs-libres.be/Chill-project/belgian-bestaddresses-transform/releases>`_).
One can select postal code by his first number (:code:`1xxx` for postal codes from 1000 to 1999), or a limited list for development purpose.
The command expects a language code as first argument.
.. code-block:: bash
# load postal code from 1000 to 3999:
bin/console chill:main:address-ref-from-best-addresse 1xxx 2xxx 3xxx
bin/console chill:main:address-ref-from-best-addresse fr 1xxx 2xxx 3xxx
# load only an extract (for dev purposes)
bin/console chill:main:address-ref-from-best-addresse extract
bin/console chill:main:address-ref-from-best-addresse fr extract
# load full addresses (discouraged)
bin/console chill:main:address-ref-from-best-addresse full
bin/console chill:main:address-ref-from-best-addresse fr full
.. note::

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

@@ -6,15 +6,12 @@
"@apidevtools/swagger-cli": "^4.0.4",
"@babel/core": "^7.20.5",
"@babel/preset-env": "^7.20.2",
"@ckeditor/ckeditor5-build-classic": "^41.4.2",
"@ckeditor/ckeditor5-dev-translations": "^40.2.0",
"@ckeditor/ckeditor5-dev-utils": "^40.2.0",
"@ckeditor/ckeditor5-dev-webpack-plugin": "^31.1.13",
"@ckeditor/ckeditor5-markdown-gfm": "^41.4.2",
"@ckeditor/ckeditor5-theme-lark": "^41.4.2",
"@ckeditor/ckeditor5-vue": "^5.1.0",
"@ckeditor/ckeditor5-vue": "^7.3.0",
"@eslint/js": "^9.14.0",
"@hotwired/stimulus": "^3.0.0",
"@luminateone/eslint-baseline": "^1.0.9",
"@symfony/stimulus-bridge": "^3.2.0",
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
"@symfony/webpack-encore": "^4.1.0",
"@tsconfig/node20": "^20.1.4",
"@types/dompurify": "^3.0.5",
@@ -23,12 +20,14 @@
"bindings": "^1.5.0",
"bootstrap": "5.2.3",
"chokidar": "^3.5.1",
"ckeditor5": "^44.1.0",
"dompurify": "^3.1.0",
"eslint": "^9.14.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.30.0",
"fork-awesome": "^1.1.7",
"intl-messageformat": "^10.5.11",
"jquery": "^3.6.0",
"node-sass": "^8.0.0",
"popper.js": "^1.16.1",
@@ -54,11 +53,13 @@
"@fullcalendar/timegrid": "^6.1.4",
"@fullcalendar/vue3": "^6.1.4",
"@popperjs/core": "^2.9.2",
"@tsconfig/node20": "^20.1.4",
"@types/dompurify": "^3.0.5",
"@types/leaflet": "^1.9.3",
"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",
@@ -72,7 +73,7 @@
"vuex": "^4.0.0"
},
"browserslist": [
"Firefox ESR"
"defaults and fully supports es6-module and not dead"
],
"scripts": {
"dev-server": "encore dev-server",

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

@@ -30,13 +30,14 @@
<ul class="record_actions">
<li class="add-persons">
<add-persons
button-title="activity.add_persons"
modal-title="activity.add_persons"
:key="addPersons.key"
:options="addPersonsOptions"
@add-new-persons="addNewPersons"
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
v-bind:key="addPersons.key"
v-bind:options="addPersonsOptions"
@addNewPersons="addNewPersons"
ref="addPersons"
/>
>
</add-persons>
</li>
</ul>
</teleport>
@@ -47,6 +48,14 @@ import { mapState, mapGetters } from "vuex";
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
import {
ACTIVITY_BLOC_PERSONS,
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
ACTIVITY_BLOC_THIRDPARTY,
ACTIVITY_BLOC_USERS,
ACTIVITY_ADD_PERSONS,
trans,
} from "translator";
export default {
name: "ConcernedGroups",
@@ -55,18 +64,24 @@ export default {
PersonsBloc,
PersonText,
},
setup() {
return {
trans,
ACTIVITY_ADD_PERSONS,
};
},
data() {
return {
personsBlocs: [
{
key: "persons",
title: "activity.bloc_persons",
title: trans(ACTIVITY_BLOC_PERSONS),
persons: [],
included: false,
},
{
key: "personsAssociated",
title: "activity.bloc_persons_associated",
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
persons: [],
included: window.activity
? window.activity.activityType.personsVisible !== 0
@@ -82,7 +97,7 @@ export default {
},
{
key: "thirdparty",
title: "activity.bloc_thirdparty",
title: trans(ACTIVITY_BLOC_THIRDPARTY),
persons: [],
included: window.activity
? window.activity.activityType.thirdPartiesVisible !== 0
@@ -90,7 +105,7 @@ export default {
},
{
key: "users",
title: "activity.bloc_users",
title: trans(ACTIVITY_BLOC_USERS),
persons: [],
included: window.activity
? window.activity.activityType.usersVisible !== 0

View File

@@ -2,7 +2,7 @@
<teleport to="#location">
<div class="mb-3 row">
<label :class="locationClassList">
{{ $t("activity.location") }}
{{ trans(ACTIVITY_LOCATION) }}
</label>
<div class="col-sm-8">
<VueMultiselect
@@ -13,17 +13,17 @@
open-direction="top"
:multiple="false"
:searchable="true"
:placeholder="$t('activity.choose_location')"
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
:custom-label="customLabel"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
:select-label="trans(MULTISELECT_SELECT_LABEL)"
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
:options="availableLocations"
group-values="locations"
group-label="locationGroup"
v-model="location"
/>
<new-location :available-locations="availableLocations" />
<new-location v-bind:available-locations="availableLocations" />
</div>
</div>
</teleport>
@@ -33,6 +33,14 @@
import { mapState, mapGetters } from "vuex";
import VueMultiselect from "vue-multiselect";
import NewLocation from "./Location/NewLocation.vue";
import {
trans,
ACTIVITY_LOCATION,
ACTIVITY_CHOOSE_LOCATION,
MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL,
MULTISELECT_SELECTED_LABEL,
} from "translator";
export default {
name: "Location",
@@ -40,6 +48,16 @@ export default {
NewLocation,
VueMultiselect,
},
setup() {
return {
trans,
ACTIVITY_LOCATION,
ACTIVITY_CHOOSE_LOCATION,
MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL,
MULTISELECT_SELECTED_LABEL,
};
},
data() {
return {
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,

View File

@@ -3,7 +3,7 @@
<ul class="record_actions">
<li>
<a class="btn btn-sm btn-create" @click="openModal">
{{ $t("activity.create_new_location") }}
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
</a>
</li>
</ul>
@@ -11,12 +11,12 @@
<teleport to="body">
<modal
v-if="modal.showModal"
:modal-dialog-class="modal.modalDialogClass"
:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false"
>
<template #header>
<h3 class="modal-title">
{{ $t("activity.create_new_location") }}
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
</h3>
</template>
<template #body>
@@ -37,7 +37,7 @@
v-model="selectType"
>
<option selected disabled value="">
{{ $t("activity.choose_location_type") }}
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
</option>
<option
v-for="t in locationTypes"
@@ -48,7 +48,7 @@
</option>
</select>
<label>{{
$t("activity.location_fields.type")
trans(ACTIVITY_LOCATION_FIELDS_TYPE)
}}</label>
</div>
@@ -60,14 +60,14 @@
placeholder
/>
<label for="name">{{
$t("activity.location_fields.name")
trans(ACTIVITY_LOCATION_FIELDS_NAME)
}}</label>
</div>
<add-address
:context="addAddress.context"
:options="addAddress.options"
:address-changed-callback="submitNewAddress"
:addressChangedCallback="submitNewAddress"
v-if="showAddAddress"
ref="addAddress"
/>
@@ -80,7 +80,7 @@
placeholder
/>
<label for="phonenumber1">{{
$t("activity.location_fields.phonenumber1")
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
}}</label>
</div>
<div class="form-floating mb-3" v-if="hasPhonenumber1">
@@ -91,7 +91,7 @@
placeholder
/>
<label for="phonenumber2">{{
$t("activity.location_fields.phonenumber2")
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
}}</label>
</div>
<div class="form-floating mb-3" v-if="showContactData">
@@ -102,7 +102,7 @@
placeholder
/>
<label for="email">{{
$t("activity.location_fields.email")
trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
}}</label>
</div>
</form>
@@ -112,7 +112,7 @@
class="btn btn-save"
@click.prevent="saveNewLocation"
>
{{ $t("action.save") }}
{{ trans(SAVE) }}
</button>
</template>
</modal>
@@ -126,6 +126,17 @@ import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue"
import { mapState } from "vuex";
import { getLocationTypes } from "../../api";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import {
SAVE,
ACTIVITY_LOCATION_FIELDS_EMAIL,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
ACTIVITY_LOCATION_FIELDS_NAME,
ACTIVITY_LOCATION_FIELDS_TYPE,
ACTIVITY_CHOOSE_LOCATION_TYPE,
ACTIVITY_CREATE_NEW_LOCATION,
trans,
} from "translator";
export default {
name: "NewLocation",
@@ -133,6 +144,19 @@ export default {
Modal,
AddAddress,
},
setup() {
return {
trans,
SAVE,
ACTIVITY_LOCATION_FIELDS_EMAIL,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
ACTIVITY_LOCATION_FIELDS_NAME,
ACTIVITY_LOCATION_FIELDS_TYPE,
ACTIVITY_CHOOSE_LOCATION_TYPE,
ACTIVITY_CREATE_NEW_LOCATION,
};
},
props: ["availableLocations"],
data() {
return {

View File

@@ -3,7 +3,7 @@
<div class="mb-3 row">
<div class="col-4">
<label :class="socialIssuesClassList">{{
$t("activity.social_issues")
trans(ACTIVITY_SOCIAL_ISSUES)
}}</label>
</div>
<div class="col-8">
@@ -12,8 +12,9 @@
:key="issue.id"
:issue="issue"
:selection="socialIssuesSelected"
@update-selected="updateIssuesSelected"
/>
@updateSelected="updateIssuesSelected"
>
</check-social-issue>
<div class="my-3">
<VueMultiselect
@@ -31,10 +32,11 @@
:allow-empty="true"
:show-labels="false"
:loading="issueIsLoading"
:placeholder="$t('activity.choose_other_social_issue')"
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
:options="socialIssuesOther"
@select="addIssueInList"
/>
>
</VueMultiselect>
</div>
</div>
</div>
@@ -42,35 +44,46 @@
<div class="mb-3 row">
<div class="col-4">
<label :class="socialActionsClassList">{{
$t("activity.social_actions")
trans(ACTIVITY_SOCIAL_ACTIONS)
}}</label>
</div>
<div class="col-8">
<div v-if="actionIsLoading === true">
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg" />
<i
class="chill-green fa fa-circle-o-notch fa-spin fa-lg"
></i>
</div>
<span
v-else-if="socialIssuesSelected.length === 0"
class="inline-choice chill-no-data-statement mt-3"
>
{{ $t("activity.select_first_a_social_issue") }}
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
</span>
<template v-else-if="socialActionsList.length > 0">
<template
v-else-if="
socialActionsList.length > 0 &&
(socialIssuesSelected.length ||
socialActionsSelected.length)
"
>
<div
v-if="
socialIssuesSelected.length ||
socialActionsSelected.length
"
id="actionsList"
v-for="group in socialActionsList"
:key="group.issue"
>
<span class="badge bg-chill-l-gray text-dark">{{
group.issue
}}</span>
<check-social-action
v-for="action in socialActionsList"
v-for="action in group.actions"
:key="action.id"
:action="action"
:selection="socialActionsSelected"
@update-selected="updateActionsSelected"
/>
@updateSelected="updateActionsSelected"
>
</check-social-action>
</div>
</template>
@@ -80,7 +93,7 @@
"
class="inline-choice chill-no-data-statement mt-3"
>
{{ $t("activity.social_action_list_empty") }}
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
</span>
</div>
</div>
@@ -92,6 +105,14 @@ import VueMultiselect from "vue-multiselect";
import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
import {
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
ACTIVITY_SOCIAL_ACTIONS,
ACTIVITY_SOCIAL_ISSUES,
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
trans,
} from "translator";
export default {
name: "SocialIssuesAcc",
@@ -100,6 +121,16 @@ export default {
CheckSocialAction,
VueMultiselect,
},
setup() {
return {
trans,
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
ACTIVITY_SOCIAL_ACTIONS,
ACTIVITY_SOCIAL_ISSUES,
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
};
},
data() {
return {
issueIsLoading: false,
@@ -127,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, reject) => {
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),
@@ -208,7 +230,7 @@ export default {
this.actionIsLoading = true;
getSocialActionByIssue(item.id).then(
(actions) =>
new Promise((resolve, reject) => {
new Promise((resolve) => {
actions.results.forEach((action) => {
this.$store.commit("addActionInList", action);
}, this);
@@ -235,9 +257,24 @@ export default {
};
</script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
<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

@@ -26,6 +26,7 @@ const store = createStore({
state: {
me: null,
activity: window.activity,
accompanyingPeriodWorks: [],
socialIssuesOther: [],
socialActionsList: [],
availableLocations: [],
@@ -41,7 +42,7 @@ const store = createStore({
const allEntities = [
...store.getters.suggestedPersons,
...store.getters.suggestedRequestor,
...store.getters.suggestedUser,
...store.getters.suggestedUsers,
...store.getters.suggestedResources,
];
const uniqueIds = [
@@ -80,8 +81,7 @@ const store = createStore({
state.activity.activityType.thirdPartiesVisible !== 0),
);
},
suggestedUser(state) {
// console.log('current user', state.me)
suggestedUsers(state) {
const existingUserIds = state.activity.users.map((p) => p.id);
let suggestedUsers =
state.activity.activityType.usersVisible === 0
@@ -90,11 +90,18 @@ const store = createStore({
(u) => u !== null && !existingUserIds.includes(u.id),
);
state.accompanyingPeriodWorks.forEach((work) => {
work.referrers.forEach((r) => {
if (!existingUserIds.includes(r.id)) {
suggestedUsers.push(r);
}
});
});
// Add the current user from the state
if (state.me && !existingUserIds.includes(state.me.id)) {
suggestedUsers.push(state.me);
}
console.log("suggested users", suggestedUsers);
// console.log("suggested users", suggestedUsers);
return suggestedUsers;
},
@@ -117,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: {
@@ -223,6 +240,9 @@ const store = createStore({
addAvailableLocationGroup(state, group) {
state.availableLocations.push(group);
},
setAccompanyingPeriodWorks(state, works) {
state.accompanyingPeriodWorks = works;
},
},
actions: {
addIssueSelected({ commit }, issue) {
@@ -341,6 +361,17 @@ const store = createStore({
}
commit("updateLocation", value);
},
async fetchAccompanyingPeriodWorks({ state, commit }) {
const accompanyingPeriodId = state.activity.accompanyingPeriod.id;
const url = `/api/1.0/person/accompanying-course/${accompanyingPeriodId}/works.json`;
try {
const works = await makeFetch("GET", url);
// console.log("works", works);
commit("setAccompanyingPeriodWorks", works);
} catch (error) {
console.error("Failed to fetch accompanying period works:", error);
}
},
getWhoAmI({ commit }) {
const url = `/api/1.0/main/whoami.json`;
makeFetch("GET", url).then((user) => {
@@ -353,5 +384,6 @@ const store = createStore({
store.dispatch("getWhoAmI");
prepareLocations(store);
store.dispatch("fetchAccompanyingPeriodWorks");
export default store;

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

@@ -143,7 +143,10 @@ class ListActivitiesByAccompanyingPeriodContext implements
array_filter(
$works,
function ($work) use ($user) {
$workUsernames = array_map(static fn (User $user) => $user['username'], $work['referrers'] ?? []);
$workUsernames = [];
foreach ($work['referrers'] as $referrer) {
$workUsernames[] = $referrer['username'];
}
return \in_array($user->getUserIdentifier(), $workUsernames, true);
}

View File

@@ -102,6 +102,32 @@ activity:
Remove a document: Supprimer le document
comment: Commentaire
deleted: Échange supprimé
errors: Le formulaire contient des erreurs
social_issues: Problématiques sociales
choose_other_social_issue: Ajouter une autre problématique sociale...
social_actions: Actions d'accompagnement
select_first_a_social_issue: Sélectionnez d'abord une problématique sociale
social_action_list_empty: Aucune action sociale disponible
add_persons: Ajouter des personnes concernées
bloc_persons: Usagers
bloc_persons_associated: Usagers du parcours
bloc_persons_not_associated: Tiers non-pro.
bloc_thirdparty: Tiers professionnels
bloc_users: T(M)S
location: Localisation
choose_location: Choisissez une localisation
choose_location_type: Choisissez un type de localisation
create_new_location: Créer une nouvelle localisation
location_fields:
name: Nom
type: Type
phonenumber1: Téléphone
phonenumber2: Autre téléphone
email: Adresse courriel
create_address: Créer une adresse
edit_address: Modifier l'adresse
No documents: Aucun document
# activity filter in list page

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

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

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

@@ -1,9 +1,5 @@
#events
Name: Nom
Label: Nom
Date: Date
Event type : Type d'événement
See: Voir
Event: Événement
Events: Événements
'Event : %label%': Événement "%label%"
@@ -123,7 +119,6 @@ Role: Rôles
Role creation: Nouveau rôle
Role edit: Modifier un rôle
'': ''
xlsx: xlsx
ods: ods
csv: csv

View File

@@ -63,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,
@@ -213,7 +212,7 @@ abstract class AbstractCRUDController extends AbstractController
protected function getManagerRegistry(): ManagerRegistry
{
return $this->container->get(ManagerRegistry::class);
return $this->container->get('doctrine');
}
/**
@@ -226,7 +225,7 @@ abstract class AbstractCRUDController extends AbstractController
protected function getValidator(): ValidatorInterface
{
return $this->get('validator');
return $this->container->get('validator');
}
/**

View File

@@ -18,6 +18,7 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class ExportType extends AbstractType
{
@@ -29,7 +30,15 @@ class ExportType extends AbstractType
final public const PICK_FORMATTER_KEY = 'pick_formatter';
public function __construct(private readonly ExportManager $exportManager, private readonly SortExportElement $sortExportElement) {}
private array $personFieldsConfig;
public function __construct(
private readonly ExportManager $exportManager,
private readonly SortExportElement $sortExportElement,
protected ParameterBagInterface $parameterBag,
) {
$this->personFieldsConfig = $parameterBag->get('chill_person.person_fields');
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
@@ -77,6 +86,17 @@ class ExportType extends AbstractType
);
foreach ($aggregators as $alias => $aggregator) {
/*
* eventually mask aggregator
*/
if (str_starts_with((string) $alias, 'person_') and str_ends_with((string) $alias, '_aggregator')) {
$field = preg_replace(['/person_/', '/_aggregator/'], '', (string) $alias);
if (array_key_exists($field, $this->personFieldsConfig) and 'visible' !== $this->personFieldsConfig[$field]) {
continue;
}
}
$aggregatorBuilder->add($alias, AggregatorType::class, [
'aggregator_alias' => $alias,
'export_manager' => $this->exportManager,

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

@@ -1,30 +1,32 @@
import ClassicEditorBase from "@ckeditor/ckeditor5-editor-classic/src/classiceditor";
import EssentialsPlugin from "@ckeditor/ckeditor5-essentials/src/essentials";
import MarkdownPlugin from "@ckeditor/ckeditor5-markdown-gfm/src/markdown";
import BoldPlugin from "@ckeditor/ckeditor5-basic-styles/src/bold";
import ItalicPlugin from "@ckeditor/ckeditor5-basic-styles/src/italic";
import BlockQuotePlugin from "@ckeditor/ckeditor5-block-quote/src/blockquote";
import HeadingPlugin from "@ckeditor/ckeditor5-heading/src/heading";
import LinkPlugin from "@ckeditor/ckeditor5-link/src/link";
import ListPlugin from "@ckeditor/ckeditor5-list/src/list";
import ParagraphPlugin from "@ckeditor/ckeditor5-paragraph/src/paragraph";
import {
Essentials,
Bold,
Italic,
Paragraph,
Markdown,
BlockQuote,
Heading,
Link,
List,
} from 'ckeditor5';
import coreTranslations from 'ckeditor5/translations/fr.js';
import 'ckeditor5/ckeditor5.css';
import "./index.scss";
export default class ClassicEditor extends ClassicEditorBase {}
ClassicEditor.builtinPlugins = [
EssentialsPlugin,
MarkdownPlugin,
BoldPlugin,
ItalicPlugin,
BlockQuotePlugin,
HeadingPlugin,
LinkPlugin,
ListPlugin,
ParagraphPlugin,
];
ClassicEditor.defaultConfig = {
export default {
plugins: [
Essentials,
Markdown,
Bold,
Italic,
BlockQuote,
Heading,
Link,
List,
Paragraph,
],
toolbar: {
items: [
"heading",
@@ -39,5 +41,8 @@ ClassicEditor.defaultConfig = {
"redo",
],
},
language: "fr",
};
translations: [
coreTranslations
],
licenseKey: "GPL",
} ;

View File

@@ -1,14 +1,23 @@
import ClassicEditor from "./editor_config";
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)
.then((editor) => {
//console.log( 'CkEditor was initialized', editor );
})
.catch((error) => {
console.error(error.stack);
});
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

@@ -1,11 +1,10 @@
import { createApp } from "vue";
import NotificationReadToggle from "ChillMainAssets/vuejs/_components/Notification/NotificationReadToggle.vue";
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
import NotificationReadAllToggle from "ChillMainAssets/vuejs/_components/Notification/NotificationReadAllToggle.vue";
const i18n = _createI18n({});
window.addEventListener("DOMContentLoaded", function (e) {
window.addEventListener("DOMContentLoaded", function () {
document
.querySelectorAll(".notification_toggle_read_status")
.forEach(function (el, i) {

View File

@@ -10,6 +10,10 @@ let appsPerInput = new Map();
function loadDynamicPicker(element) {
let apps = element.querySelectorAll('[data-module="pick-dynamic"]');
let suggested;
let as_id;
let submit_on_adding_new_entity;
let label;
apps.forEach(function (el) {
const isMultiple = parseInt(el.dataset.multiple) === 1,
@@ -22,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 === "[]") {
@@ -173,7 +177,7 @@ document.addEventListener("pick-entity-type-action", function (e) {
}
});
document.addEventListener("DOMContentLoaded", function (e) {
document.addEventListener("DOMContentLoaded", function () {
loadDynamicPicker(document);
});

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 [];
@@ -232,19 +240,8 @@ const filteredDocuments = computed<GenericDocForAccompanyingPeriod[]>(() => {
</div>
</div>
</div>
<div class="row my-2">
<button
type="submit"
class="btn btn-sm btn-misc"
>
<i class="fa fa-fw fa-filter"></i>Filtrer
</button>
</div>
</div>
</div>
<div></div>
</div>
</form>
</div>
@@ -256,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

@@ -70,7 +70,8 @@ const clickOnAddButton = () => {
<style scoped lang="scss">
.item-bloc {
&.isPicked {
background: linear-gradient(
background:
linear-gradient(
180deg,
rgba(25, 135, 84, 1) 0px,
rgba(25, 135, 84, 0) 9px

View File

@@ -1,61 +1,66 @@
<template>
<span v-if="entity.type === 'person'" class="badge rounded-pill bg-person">
{{ $t("person") }}
<span
v-if="props.entity.type === 'person'"
class="badge rounded-pill bg-person"
>
{{ trans(PERSON) }}
</span>
<span
v-if="entity.type === 'thirdparty'"
v-if="props.entity.type === 'thirdparty'"
class="badge rounded-pill bg-thirdparty"
>
<template v-if="options.displayLong !== true">
{{ $t("thirdparty.thirdparty") }}
<template v-if="props.options.displayLong !== true">
{{ trans(THIRDPARTY) }}
</template>
<i class="fa fa-fw fa-user" v-if="entity.kind === 'child'" />
<i class="fa fa-fw fa-user" v-if="props.entity.kind === 'child'" />
<i
class="fa fa-fw fa-hospital-o"
v-else-if="entity.kind === 'company'"
v-else-if="props.entity.kind === 'company'"
/>
<i class="fa fa-fw fa-user-md" v-else />
<template v-if="options.displayLong === true">
<span v-if="entity.kind === 'child'">{{
$t("thirdparty.child")
<template v-if="props.options.displayLong === true">
<span v-if="props.entity.kind === 'child'">{{
trans(THIRDPARTY_CONTACT_OF)
}}</span>
<span v-else-if="entity.kind === 'company'">{{
$t("thirdparty.company")
<span v-else-if="props.entity.kind === 'company'">{{
trans(THIRDPARTY_A_COMPANY)
}}</span>
<span v-else>{{ $t("thirdparty.contact") }}</span>
<span v-else>{{ trans(THIRDPARTY_A_CONTACT) }}</span>
</template>
</span>
<span v-if="entity.type === 'user'" class="badge rounded-pill bg-user">
{{ $t("user") }}
<span
v-if="props.entity.type === 'user'"
class="badge rounded-pill bg-user"
>
{{ trans(ACCEPTED_USERS) }}
</span>
<span v-if="entity.type === 'household'" class="badge rounded-pill bg-user">
{{ $t("household") }}
<span
v-if="props.entity.type === 'household'"
class="badge rounded-pill bg-user"
>
{{ trans(HOUSEHOLD) }}
</span>
</template>
<script>
export default {
name: "BadgeEntity",
props: ["options", "entity"],
i18n: {
messages: {
fr: {
person: "Usager",
thirdparty: {
thirdparty: "Tiers",
child: "Personne de contact",
company: "Personne morale",
contact: "Personne physique",
},
user: "TMS",
household: "Ménage",
},
},
},
};
<script setup>
import {
trans,
HOUSEHOLD,
ACCEPTED_USERS,
THIRDPARTY_A_CONTACT,
THIRDPARTY_CONTACT_OF,
THIRDPARTY_A_COMPANY,
PERSON,
THIRDPARTY,
} from "translator";
const props = defineProps({
options: Object,
entity: Object,
});
</script>

View File

@@ -66,13 +66,13 @@
<div v-if="useDatePane === true" class="address-more">
<div v-if="address.validFrom">
<span class="validFrom">
<b>{{ $t("validFrom") }}</b
<b>{{ trans(ADDRESS_VALID_FROM) }}</b
>: {{ $d(address.validFrom.date) }}
</span>
</div>
<div v-if="address.validTo">
<span class="validTo">
<b>{{ $t("validTo") }}</b
<b>{{ trans(ADDRESS_VALID_TO) }}</b
>: {{ $d(address.validTo.date) }}
</span>
</div>
@@ -83,6 +83,7 @@
<script>
import Confidential from "ChillMainAssets/vuejs/_components/Confidential.vue";
import AddressDetailsButton from "ChillMainAssets/vuejs/_components/AddressDetails/AddressDetailsButton.vue";
import { trans, ADDRESS_VALID_FROM, ADDRESS_VALID_TO } from "translator";
export default {
name: "AddressRenderBox",
@@ -107,6 +108,9 @@ export default {
type: Boolean,
},
},
setup() {
return { trans, ADDRESS_VALID_FROM, ADDRESS_VALID_TO };
},
computed: {
component() {
return this.isMultiline === true ? "div" : "span";

View File

@@ -6,8 +6,8 @@
v-if="!subscriberFinal"
@click="subscribeTo('subscribe', 'final')"
>
<i class="fa fa-check fa-fw" />
{{ $t("subscribe_final") }}
<i class="fa fa-check fa-fw"></i>
{{ trans(WORKFLOW_SUBSCRIBE_FINAL) }}
</button>
<button
class="btn btn-misc"
@@ -15,8 +15,8 @@
v-if="subscriberFinal"
@click="subscribeTo('unsubscribe', 'final')"
>
<i class="fa fa-times fa-fw" />
{{ $t("unsubscribe_final") }}
<i class="fa fa-times fa-fw"></i>
{{ trans(WORKFLOW_UNSUBSCRIBE_FINAL) }}
</button>
<button
class="btn btn-misc"
@@ -24,8 +24,8 @@
v-if="!subscriberStep"
@click="subscribeTo('subscribe', 'step')"
>
<i class="fa fa-check fa-fw" />
{{ $t("subscribe_all_steps") }}
<i class="fa fa-check fa-fw"></i>
{{ trans(WORKFLOW_SUBSCRIBE_ALL_STEPS) }}
</button>
<button
class="btn btn-misc"
@@ -33,94 +33,55 @@
v-if="subscriberStep"
@click="subscribeTo('unsubscribe', 'step')"
>
<i class="fa fa-times fa-fw" />
{{ $t("unsubscribe_all_steps") }}
<i class="fa fa-times fa-fw"></i>
{{ trans(WORKFLOW_UNSUBSCRIBE_ALL_STEPS) }}
</button>
</div>
</template>
<script>
<script setup>
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
import { defineProps, defineEmits } from "vue";
import {
trans,
WORKFLOW_SUBSCRIBE_FINAL,
WORKFLOW_UNSUBSCRIBE_FINAL,
WORKFLOW_SUBSCRIBE_ALL_STEPS,
WORKFLOW_UNSUBSCRIBE_ALL_STEPS,
} from "translator";
export default {
name: "EntityWorkflowVueSubscriber",
i18n: {
messages: {
fr: {
subscribe_final: "Recevoir une notification à l'étape finale",
unsubscribe_final:
"Ne plus recevoir de notification à l'étape finale",
subscribe_all_steps:
"Recevoir une notification à chaque étape du suivi",
unsubscribe_all_steps:
"Ne plus recevoir de notification à chaque étape du suivi",
},
},
// props
const props = defineProps({
entityWorkflowId: {
type: Number,
required: true,
},
props: {
entityWorkflowId: {
type: Number,
required: true,
},
subscriberStep: {
type: Boolean,
required: true,
},
subscriberFinal: {
type: Boolean,
required: true,
},
subscriberStep: {
type: Boolean,
required: true,
},
emits: ["subscriptionUpdated"],
methods: {
subscribeTo(step, to) {
let params = new URLSearchParams();
params.set("subscribe", to);
subscriberFinal: {
type: Boolean,
required: true,
},
});
const url =
`/api/1.0/main/workflow/${this.entityWorkflowId}/${step}?` +
params.toString();
//methods
const subscribeTo = (step, to) => {
let params = new URLSearchParams();
params.set("subscribe", to);
makeFetch("POST", url).then((response) => {
this.$emit("subscriptionUpdated", response);
});
},
},
const url =
`/api/1.0/main/workflow/${props.entityWorkflowId}/${step}?` +
params.toString();
makeFetch("POST", url).then((response) => {
emit("subscriptionUpdated", response);
});
};
/*
* ALTERNATIVES
*
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="laststep">
<label class="form-check-label" for="laststep">{{ $t('subscribe_final') }}</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="allsteps">
<label class="form-check-label" for="allsteps">{{ $t('subscribe_all_steps') }}</label>
</div>
<div class="list-group my-3">
<label class="list-group-item">
<input class="form-check-input me-1" type="checkbox" value="">
{{ $t('subscribe_final') }}
</label>
<label class="list-group-item">
<input class="form-check-input me-1" type="checkbox" value="">
{{ $t('subscribe_all_steps') }}
</label>
</div>
<div class="btn-group-vertical my-3" role="group">
<button type="button" class="btn btn-outline-primary">
<i class="fa fa-check fa-fw"></i>
{{ $t('subscribe_final') }}
</button>
<button type="button" class="btn btn-outline-primary">
<i class="fa fa-check fa-fw"></i>
{{ $t('subscribe_all_steps') }}
</button>
</div>
*/
// emit
const emit = defineEmits(["subscriptionUpdated"]);
</script>
<style scoped></style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="flex-table workflow" id="workflow-list">
<div
v-for="(w, i) in workflows"
v-for="(w, i) in props.workflows"
:key="`workflow-${i}`"
class="item-bloc"
>
@@ -48,7 +48,7 @@
<span
v-if="w.isOnHoldAtCurrentStep"
class="badge bg-success rounded-pill"
>{{ $t("on_hold") }}</span
>{{ trans(WORKFLOW_ON_HOLD) }}</span
>
</div>
@@ -56,11 +56,11 @@
<div class="item-col flex-grow-1">
<p v-if="isUserSubscribedToStep(w)">
<i class="fa fa-check fa-fw"></i>
{{ $t("you_subscribed_to_all_steps") }}
{{ trans(WORKFLOW_YOU_SUBSCRIBED_TO_ALL_STEPS) }}
</p>
<p v-if="isUserSubscribedToFinal(w)">
<i class="fa fa-check fa-fw"></i>
{{ $t("you_subscribed_to_final_step") }}
{{ trans(WORKFLOW_YOU_SUBSCRIBED_TO_FINAL_STEP) }}
</p>
</div>
<div class="item-col">
@@ -69,7 +69,7 @@
<a
:href="goToUrl(w)"
class="btn btn-sm btn-show"
:title="$t('action.show')"
:title="trans(SEE)"
></a>
</li>
</ul>
@@ -79,85 +79,65 @@
</div>
</template>
<script>
<script setup>
import Popover from "bootstrap/js/src/popover";
import { onMounted } from "vue";
import {
trans,
BY_USER,
SEE,
WORKFLOW_YOU_SUBSCRIBED_TO_ALL_STEPS,
WORKFLOW_YOU_SUBSCRIBED_TO_FINAL_STEP,
WORKFLOW_ON_HOLD,
WORKFLOW_AT,
} from "translator";
const i18n = {
messages: {
fr: {
you_subscribed_to_all_steps:
"Vous recevrez une notification à chaque étape",
you_subscribed_to_final_step:
"Vous recevrez une notification à l'étape finale",
by: "Par",
at: "Le",
on_hold: "En attente",
},
// props
const props = defineProps({
workflows: {
type: Array,
required: true,
},
});
// methods
const goToUrl = (w) => `/fr/main/workflow/${w.id}/show`;
const getPopTitle = (step) => {
if (step.transitionPrevious != null) {
//console.log(step.transitionPrevious.text);
let freezed = step.isFreezed
? `<i class="fa fa-snowflake-o fa-sm me-1"></i>`
: ``;
return `${freezed}${step.transitionPrevious.text}`;
}
};
const getPopContent = (step) => {
if (step.transitionPrevious != null) {
if (step.transitionPreviousBy !== null) {
return `<ul class="small_in_title">
<li><span class="item-key">${trans(BY_USER)} : </span><b>${step.transitionPreviousBy.text}</b></li>
<li><span class="item-key">${trans(WORKFLOW_AT)} : </span><b>${formatDate(step.transitionPreviousAt.datetime)}</b></li>
</ul>`;
} else {
return `<ul class="small_in_title">
<li><span class="item-key">${trans(WORKFLOW_AT)} : </span><b>${formatDate(step.transitionPreviousAt.datetime)}</b></li>
</ul>`;
}
}
};
const formatDate = (datetime) =>
datetime.split("T")[0] + " " + datetime.split("T")[1].substring(0, 5);
const isUserSubscribedToStep = () => false;
const isUserSubscribedToFinal = () => false;
export default {
name: "ListWorkflow",
i18n: i18n,
props: {
workflows: {
type: Array,
required: true,
},
},
methods: {
goToUrl(w) {
return `/fr/main/workflow/${w.id}/show`;
},
getPopTitle(step) {
if (step.transitionPrevious != null) {
//console.log(step.transitionPrevious.text);
let freezed = step.isFreezed
? `<i class="fa fa-snowflake-o fa-sm me-1"></i>`
: ``;
return `${freezed}${step.transitionPrevious.text}`;
}
},
getPopContent(step) {
if (step.transitionPrevious != null) {
if (step.transitionPreviousBy !== null) {
return `<ul class="small_in_title">
<li><span class="item-key">${i18n.messages.fr.by} : </span><b>${step.transitionPreviousBy.text}</b></li>
<li><span class="item-key">${i18n.messages.fr.at} : </span><b>${this.formatDate(step.transitionPreviousAt.datetime)}</b></li>
</ul>`;
} else {
return `<ul class="small_in_title">
<li><span class="item-key">${i18n.messages.fr.at} : </span><b>${this.formatDate(step.transitionPreviousAt.datetime)}</b></li>
</ul>`;
}
}
},
formatDate(datetime) {
return (
datetime.split("T")[0] +
" " +
datetime.split("T")[1].substring(0, 5)
);
},
isUserSubscribedToStep(w) {
// todo
return false;
},
isUserSubscribedToFinal(w) {
// todo
return false;
},
},
mounted() {
const triggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="popover"]'),
);
const popoverList = triggerList.map(function (el) {
//console.log('popover', el)
return new Popover(el, {
html: true,
});
onMounted(() => {
const triggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="popover"]'),
);
triggerList.map(function (el) {
return new Popover(el, {
html: true,
});
},
};
});
});
</script>

View File

@@ -1,23 +1,24 @@
<template>
<pick-workflow
:relatedEntityClass="this.relatedEntityClass"
:relatedEntityId="this.relatedEntityId"
:workflowsAvailables="workflowsAvailables"
:preventDefaultMoveToGenerate="this.$props.preventDefaultMoveToGenerate"
:goToGenerateWorkflowPayload="this.goToGenerateWorkflowPayload"
<Pick-workflow
:relatedEntityClass="props.relatedEntityClass"
:relatedEntityId="props.relatedEntityId"
:workflowsAvailables="props.workflowsAvailables"
:preventDefaultMoveToGenerate="props.preventDefaultMoveToGenerate"
:goToGenerateWorkflowPayload="props.goToGenerateWorkflowPayload"
:countExistingWorkflows="countWorkflows"
:embedded-within-list-modal="false"
@go-to-generate-workflow="goToGenerateWorkflow"
@click-open-list="openModal"
></pick-workflow>
></Pick-workflow>
<teleport to="body">
<modal
<Modal
v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false"
>
<template v-slot:header>
<h2 class="modal-title">{{ $t("workflow_list") }}</h2>
<h2 class="modal-title">{{ trans(WORKFLOW_LIST) }}</h2>
</template>
<template v-slot:body>
@@ -27,103 +28,80 @@
<template v-slot:footer>
<pick-workflow
v-if="allowCreate"
:relatedEntityClass="this.relatedEntityClass"
:relatedEntityId="this.relatedEntityId"
:workflowsAvailables="workflowsAvailables"
:relatedEntityClass="props.relatedEntityClass"
:relatedEntityId="props.relatedEntityId"
:workflowsAvailables="props.workflowsAvailables"
:preventDefaultMoveToGenerate="
this.$props.preventDefaultMoveToGenerate
props.preventDefaultMoveToGenerate
"
:goToGenerateWorkflowPayload="
this.goToGenerateWorkflowPayload
props.goToGenerateWorkflowPayload
"
:countExistingWorkflows="countWorkflows"
:embedded-within-list-modal="true"
@go-to-generate-workflow="this.goToGenerateWorkflow"
@go-to-generate-workflow="goToGenerateWorkflow"
></pick-workflow>
</template>
</modal>
</Modal>
</teleport>
</template>
<script>
<script setup>
import { ref, computed, defineProps, defineEmits } from "vue";
import Modal from "ChillMainAssets/vuejs/_components/Modal";
import PickWorkflow from "ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue";
import ListWorkflowVue from "ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflow.vue";
import { trans, WORKFLOW_LIST } from "translator";
export default {
name: "ListWorkflowModal",
components: {
Modal,
PickWorkflow,
ListWorkflowVue,
// Define props
const props = defineProps({
workflows: {
type: Array,
required: true,
},
emits: ["goToGenerateWorkflow"],
props: {
workflows: {
type: Array,
required: true,
},
allowCreate: {
type: Boolean,
required: true,
},
relatedEntityClass: {
type: String,
required: true,
},
relatedEntityId: {
type: Number,
required: false,
},
workflowsAvailables: {
type: Array,
required: true,
},
preventDefaultMoveToGenerate: {
type: Boolean,
required: false,
default: false,
},
goToGenerateWorkflowPayload: {
required: false,
default: {},
},
allowCreate: {
type: Boolean,
required: true,
},
data() {
return {
modal: {
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl",
},
};
relatedEntityClass: {
type: String,
required: true,
},
computed: {
countWorkflows() {
return this.workflows.length;
},
hasWorkflow() {
return this.countWorkflows > 0;
},
relatedEntityId: {
type: Number,
required: false,
},
methods: {
openModal() {
this.modal.showModal = true;
},
goToGenerateWorkflow(data) {
console.log("go to generate workflow intercepted", data);
this.$emit("goToGenerateWorkflow", data);
},
workflowsAvailables: {
type: Array,
required: true,
},
i18n: {
messages: {
fr: {
workflow_list: "Liste des workflows associés",
workflow: " workflow associé",
workflows: " workflows associés",
},
},
preventDefaultMoveToGenerate: {
type: Boolean,
required: false,
default: false,
},
};
goToGenerateWorkflowPayload: {
required: false,
default: () => ({}),
},
});
// Define emits
const emit = defineEmits(["goToGenerateWorkflow"]);
// Reactive data
const modal = ref({
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl",
});
// Computed properties
const countWorkflows = computed(() => props.workflows.length);
// Methods
const openModal = () => (modal.value.showModal = true);
const goToGenerateWorkflow = (data) => emit("goToGenerateWorkflow", data);
</script>
<style scoped></style>

View File

@@ -8,28 +8,28 @@
aria-modal="true"
role="dialog"
>
<div class="modal-dialog" :class="modalDialogClass">
<div class="modal-dialog" :class="props.modalDialogClass || {}">
<div class="modal-content">
<div class="modal-header">
<slot name="header" />
<button class="close btn" @click="$emit('close')">
<i class="fa fa-times" aria-hidden="true" />
<slot name="header"></slot>
<button class="close btn" @click="emits('close')">
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</div>
<div class="modal-body">
<div class="body-head">
<slot name="body-head" />
<slot name="body-head"></slot>
</div>
<slot name="body" />
<slot name="body"></slot>
</div>
<div class="modal-footer" v-if="!hideFooter">
<button
class="btn btn-cancel"
@click="$emit('close')"
@click="emits('close')"
>
{{ $t("action.close") }}
{{ trans(MODAL_ACTION_CLOSE) }}
</button>
<slot name="footer" />
<slot name="footer"></slot>
</div>
</div>
</div>
@@ -39,8 +39,7 @@
</transition>
</template>
<script lang="ts">
import { defineComponent } from "vue";
<script lang="ts" setup>
/*
* This Modal component is a mix between Vue3 modal implementation
* [+] with 'v-if:showModal' directive:parameter, html scope is added/removed not just shown/hidden
@@ -50,22 +49,23 @@ import { defineComponent } from "vue";
* [+] using bootstrap css classes, the modal have a responsive behaviour,
* [+] modal design can be configured using css classes (size, scroll)
*/
export default defineComponent({
name: "Modal",
props: {
modalDialogClass: {
type: Object,
required: false,
default: {},
},
hideFooter: {
type: Boolean,
required: false,
default: false,
},
},
emits: ["close"],
import { trans, MODAL_ACTION_CLOSE } from "translator";
import { defineProps } from "vue";
export interface ModalProps {
modalDialogClass: object | null;
hideFooter: boolean;
}
// Define the props
const props = withDefaults(defineProps<ModalProps>(), {
hideFooter: false,
modalDialogClass: null,
});
const emits = defineEmits<{
close: [];
}>();
</script>
<style lang="scss">

View File

@@ -9,12 +9,12 @@
class="btn"
:class="overrideClass"
type="button"
:title="$t('markAsUnread')"
:title="trans(NOTIFICATION_MARK_AS_UNREAD)"
@click="markAsUnread"
>
<i class="fa fa-sm fa-envelope-o" />
<span v-if="!buttonNoText" class="ps-2">
{{ $t("markAsUnread") }}
<i class="fa fa-sm fa-envelope-o"></i>
<span v-if="!props.buttonNoText" class="ps-2">
{{ trans(NOTIFICATION_MARK_AS_UNREAD) }}
</span>
</button>
@@ -23,12 +23,12 @@
class="btn"
:class="overrideClass"
type="button"
:title="$t('markAsRead')"
:title="trans(NOTIFICATION_MARK_AS_READ)"
@click="markAsRead"
>
<i class="fa fa-sm fa-envelope-open-o" />
<i class="fa fa-sm fa-envelope-open-o"></i>
<span v-if="!buttonNoText" class="ps-2">
{{ $t("markAsRead") }}
{{ trans(NOTIFICATION_MARK_AS_READ) }}
</span>
</button>
@@ -37,9 +37,9 @@
type="button"
class="btn btn-outline-primary"
:href="showUrl"
:title="$t('action.show')"
:title="trans(SEE)"
>
<i class="fa fa-sm fa-comment-o" />
<i class="fa fa-sm fa-comment-o"></i>
</a>
<!-- "Mark All Read" button -->
@@ -51,7 +51,7 @@
:title="$t('markAllRead')"
@click="markAllRead"
>
<i class="fa fa-sm fa-envelope-o" />
<i class="fa fa-sm fa-envelope-o"></i>
<span v-if="!buttonNoText" class="ps-2">
{{ $t("markAllRead") }}
</span>
@@ -59,89 +59,66 @@
</div>
</template>
<script>
<script setup>
import { computed } from "vue";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
import {
trans,
NOTIFICATION_MARK_AS_READ,
NOTIFICATION_MARK_AS_UNREAD,
SEE,
} from "translator";
export default {
name: "NotificationReadToggle",
props: {
isRead: {
required: true,
type: Boolean,
},
notificationId: {
required: true,
type: Number,
},
// Optional
buttonClass: {
required: false,
type: String,
},
buttonNoText: {
required: false,
type: Boolean,
},
showUrl: {
required: false,
type: String,
},
// Props
const props = defineProps({
isRead: {
type: Boolean,
required: true,
},
emits: ["markRead", "markUnread"],
computed: {
/// [Option] override default button appearance (btn-misc)
overrideClass() {
return this.buttonClass ? this.buttonClass : "btn-misc";
},
/// [Option] don't display text on button
buttonHideText() {
return this.buttonNoText;
},
/// [Option] showUrl is href for show page second button.
// When passed, the component return a button-group with 2 buttons.
isButtonGroup() {
return this.showUrl;
},
notificationId: {
type: Number,
required: true,
},
methods: {
markAsUnread() {
makeFetch(
"POST",
`/api/1.0/main/notification/${this.notificationId}/mark/unread`,
[],
).then(() => {
this.$emit("markRead", { notificationId: this.notificationId });
});
},
markAsRead() {
makeFetch(
"POST",
`/api/1.0/main/notification/${this.notificationId}/mark/read`,
[],
).then(() => {
this.$emit("markUnread", {
notificationId: this.notificationId,
});
});
},
markAllRead() {
makeFetch(
"POST",
`/api/1.0/main/notification/markallread`,
[],
).then(() => {
this.$emit("markAllRead");
});
},
buttonClass: {
type: String,
required: false,
},
i18n: {
messages: {
fr: {
markAsUnread: "Marquer comme non-lu",
markAsRead: "Marquer comme lu",
},
},
buttonNoText: {
type: Boolean,
required: false,
},
showUrl: {
type: String,
required: false,
},
});
// Emits
const emit = defineEmits(["markRead", "markUnread"]);
// Computed
const overrideClass = computed(() => props.buttonClass || "btn-misc");
const isButtonGroup = computed(() => props.showUrl);
// Methods
const markAsUnread = () => {
makeFetch(
"POST",
`/api/1.0/main/notification/${props.notificationId}/mark/unread`,
[],
).then(() => {
emit("markRead", { notificationId: props.notificationId });
});
};
const markAsRead = () => {
makeFetch(
"POST",
`/api/1.0/main/notification/${props.notificationId}/mark/read`,
[],
).then(() => {
emit("markUnread", { notificationId: props.notificationId });
});
};
</script>

View File

@@ -1,253 +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" />
<span v-if="!noText">
{{ $t("online_edit_document") }}
</span>
</a>
<teleport to="body">
<div class="wopi-frame" v-if="isOpenDocument">
<modal
v-if="modal.showModal"
:modal-dialog-class="modal.modalDialogClass"
:hide-footer="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>
<!--
<a class="btn btn-outline-light">
<i class="fa fa-save fa-fw"></i>
{{ $t('save_and_quit') }}
</a>
-->
</template>
<template #body>
<div v-if="loading" class="loading">
<i
class="fa fa-circle-o-notch fa-spin fa-3x"
:title="$t('loading')"
/>
</div>
<iframe :src="this.wopiUrl" @load="loaded" />
</template>
</modal>
</div>
<div v-else>
<modal
v-if="modal.showModal"
modal-dialog-class="modal-sm"
@close="modal.showModal = false"
>
<template #header>
<h3>{{ $t("invalid_title") }}</h3>
</template>
<template #body>
<div class="alert alert-warning">
{{ $t("invalid_message") }}
</div>
</template>
</modal>
</div>
</teleport>
</template>
<script>
import Modal from "ChillMainAssets/vuejs/_components/Modal";
import logo from "ChillMainAssets/chill/img/logo-chill-sans-slogan_white.png";
export default {
name: "OpenWopiLink",
components: {
Modal,
},
props: {
wopiUrl: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
options: {
type: Object,
required: false,
},
},
data() {
return {
modal: {
showModal: false,
modalDialogClass: "modal-fullscreen", //modal-dialog-scrollable
},
logo: logo,
loading: false,
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: {
isOpenDocument() {
if (this.mime.indexOf(this.type) !== -1) {
return true;
}
return false;
},
noText() {
if (typeof this.options.noText !== "undefined") {
return this.options.noText === true;
}
return false;
},
isChangeIcon() {
if (typeof this.options.changeIcon !== "undefined") {
return !(
this.options.changeIcon === null ||
this.options.changeIcon === ""
);
}
return false;
},
isChangeClass() {
if (typeof this.options.changeClass !== "undefined") {
return !(
this.options.changeClass === null ||
this.options.changeClass === ""
);
}
return false;
},
},
methods: {
openModal() {
this.loading = true;
this.modal.showModal = true;
},
loaded() {
this.loading = false;
},
},
i18n: {
messages: {
fr: {
online_edit_document: "Éditer en ligne",
save_and_quit: "Enregistrer et quitter",
loading: "Chargement de l'éditeur en ligne",
invalid_title: "Format incompatible",
invalid_message:
"Désolé, ce format de document n'est pas éditable en ligne.",
},
},
},
};
</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 %}

View File

@@ -9,7 +9,6 @@
{{ encore_entry_script_tags('mod_pickentity_type') }}
{{ encore_entry_script_tags('mod_entity_workflow_subscribe') }}
{{ encore_entry_script_tags('page_workflow_show') }}
{{ encore_entry_script_tags('mod_wopi_link') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
{{ encore_entry_script_tags('mod_workflow_attachment') }}
{% endblock %}
@@ -19,7 +18,6 @@
{{ encore_entry_link_tags('mod_pickentity_type') }}
{{ encore_entry_link_tags('mod_entity_workflow_subscribe') }}
{{ encore_entry_link_tags('page_workflow_show') }}
{{ encore_entry_link_tags('mod_wopi_link') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
{{ encore_entry_link_tags('mod_workflow_attachment') }}
{% endblock %}

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