Compare commits

..

2 Commits

628 changed files with 23692 additions and 48833 deletions

View File

@@ -0,0 +1,6 @@
kind: DX
body: Add missing fixtures for proper loading of AccompanyingPeriods
time: 2025-10-30T12:37:32.824593456+01:00
custom:
Issue: "280"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Feature
body: |
Upgrade import of address list to the last version of compiled addresses of belgian-best-address
time: 2024-05-30T16:00:03.440767606+02:00
custom:
Issue: ""

View File

@@ -1,6 +0,0 @@
kind: Feature
body: |
Upgrade CKEditor and refactor configuration with use of typescript
time: 2024-05-31T19:02:42.776662753+02:00
custom:
Issue: ""

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Create invitation list in user menu
time: 2025-08-08T12:08:02.446361367+02:00
custom:
Issue: "385"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Admin interface for Motive entity
time: 2025-10-07T15:59:45.597029709+02:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Add an admin interface for Motive entity
time: 2025-10-22T11:15:52.13937955+02:00
custom:
Issue: ""
SchemaChange: Add columns or tables

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Add columns for comments linked to an activity in the activity list export
time: 2025-10-29T15:25:10.493968528+01:00
custom:
Issue: "404"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: 'Fix: display also social actions linked to parents of the selected social issue'
time: 2025-10-29T12:43:55.008647232+01:00
custom:
Issue: "451"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: 'Fix: export actions and their results in csv even when action does not have any goals attached to it.'
time: 2025-10-29T14:38:36.195220844+01:00
custom:
Issue: "453"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: Fix the possibility to delete a workflow
time: 2025-11-04T13:51:08.113234488+01:00
custom:
Issue: ""
SchemaChange: Drop or rename table or columns, or enforce new constraint that must be manually fixed

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: Fix suggestion of referrer when creating notification for accompanyingPeriodWorkDocument
time: 2025-11-06T16:16:05.861813041+01:00
custom:
Issue: "428"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: UX
body: Change the terms 'cercle' and 'centre' to 'service', and 'territoire' respectively.
time: 2025-10-06T12:39:32.514056818+02:00
custom:
Issue: "425"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: UX
body: Improve the ux for selecting whether user wants to be notified of the final step of a workflow or all steps
time: 2025-10-29T11:08:04.077020411+01:00
custom:
Issue: "542"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: UX
body: Expand timeSpent choices for evaluation document and translate them to user locale or fallback 'fr'
time: 2025-10-30T18:09:19.373907522+01:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -19,11 +19,11 @@ max_line_length = 80
[COMMIT_EDITMSG] [COMMIT_EDITMSG]
max_line_length = 0 max_line_length = 0
[*.{js,vue,ts}] [*.{js, vue, ts}]
indent_size = 2 indent_size = 2
indent_style = space indent_style = space
[*.rst] [.rst]
indent_size = 3 ident_size = 3
indent_style = space ident_style = space

View File

@@ -234,17 +234,17 @@ This must be a decision made by a human, not by an AI. Every AI task must abort
#### Running Tests #### Running Tests
The tests are run from the project's root (not from the bundle's root: so, do not change the directory to any bundle directory before running tests). The tests are run from the project's root (not from the bundle's root).
Tests must be run using the `symfony` command:
```bash ```bash
# Run all tests
vendor/bin/phpunit
# Run a specific test file # Run a specific test file
symfony composer exec phpunit -- path/to/TestFile.php vendor/bin/phpunit path/to/TestFile.php
# Run a specific test method # Run a specific test method
symfony composer exec phpunit -- --filter methodName path/to/TestFile.php vendor/bin/phpunit --filter methodName path/to/TestFile.php
``` ```
When writing tests, only test specific files. Do not run all tests or the full When writing tests, only test specific files. Do not run all tests or the full

View File

@@ -1,4 +0,0 @@
{
"tabWidth": 2,
"useTabs": false
}

30
.vscode/launch.json vendored
View File

@@ -1,30 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Chill Debug",
"type": "php",
"request": "launch",
"port": 9000,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
},
"preLaunchTask": "symfony"
},
{
"name": "Yarn Encore Dev (Watch)",
"type": "node-terminal",
"request": "launch",
"command": "yarn encore dev --watch",
"cwd": "${workspaceFolder}"
}
],
"compounds": [
{
"name": "Chill Debug + Yarn Encore Dev (Watch)",
"configurations": ["Chill Debug", "Yarn Encore Dev (Watch)"]
}
]
}

23
.vscode/tasks.json vendored
View File

@@ -1,23 +0,0 @@
{
"tasks": [
{
"type": "shell",
"command": "symfony",
"args": [
"server:start",
"--allow-http",
"--no-tls",
"--port=8000",
"--allow-all-ip",
"-d"
],
"label": "symfony"
},
{
"type": "shell",
"command": "yarn",
"args": ["encore", "dev", "--watch"],
"label": "webpack"
}
]
}

View File

@@ -780,7 +780,7 @@ Fix color of Chill footer
- ajout d'un filtre et regroupement par usager participant sur les échanges - ajout d'un filtre et regroupement par usager participant sur les échanges
- ajout d'un regroupement: par type d'activité associé au parcours; - ajout d'un regroupement: par type d'activité associé au parcours;
- trie les filtre et regroupements par ordre alphabétique dans els exports - trie les filtre et regroupements par ordre alphabétique dans els exports
- ajout d'un paramètre qui permet de désactiver le filtre par territoire dans les exports - ajout d'un paramètre qui permet de désactiver le filtre par centre dans les exports
- correction de l'interface de date dans les filtres et regroupements "par statut du parcours à la date" - correction de l'interface de date dans les filtres et regroupements "par statut du parcours à la date"
## v2.9.2 - 2023-10-17 ## v2.9.2 - 2023-10-17
@@ -960,7 +960,7 @@ error when trying to reedit a saved export
- ajout d'un regroupement par métier des intervenants sur un parcours; - ajout d'un regroupement par métier des intervenants sur un parcours;
- ajout d'un regroupement par service des intervenants sur un parcours; - ajout d'un regroupement par service des intervenants sur un parcours;
- ajout d'un regroupement par utilisateur intervenant sur un parcours - ajout d'un regroupement par utilisateur intervenant sur un parcours
- ajout d'un regroupement "par territoire de l'usager"; - ajout d'un regroupement "par centre de l'usager";
- ajout d'un filtre "par métier intervenant sur un parcours"; - ajout d'un filtre "par métier intervenant sur un parcours";
- ajout d'un filtre "par service intervenant sur un parcours"; - ajout d'un filtre "par service intervenant sur un parcours";
- création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot); - création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot);

View File

@@ -54,7 +54,7 @@ Arborescence:
- person - person
- personvendee - personvendee
- household_edit_metadata - household_edit_metadata
- index.ts - index.js
``` ```
## Organisation des feuilles de styles ## Organisation des feuilles de styles

View File

@@ -1,12 +1,7 @@
import { import { trans, setLocale, setLocaleFallbacks } from "./ux-translator";
trans,
setLocale,
getLocale,
setLocaleFallbacks,
} from "./ux-translator";
setLocaleFallbacks({ en: "fr", nl: "fr", fr: "en" }); setLocaleFallbacks({"en": "fr", "nl": "fr", "fr": "en"});
setLocale("fr"); setLocale('fr');
export { trans, getLocale }; export { trans };
export * from "../var/translations"; export * from '../var/translations';

View File

@@ -14,7 +14,7 @@
"ext-openssl": "*", "ext-openssl": "*",
"ext-redis": "*", "ext-redis": "*",
"ext-zlib": "*", "ext-zlib": "*",
"champs-libres/wopi-bundle": "dev-symfony-v5@dev", "champs-libres/wopi-bundle": "dev-master@dev",
"champs-libres/wopi-lib": "dev-master@dev", "champs-libres/wopi-lib": "dev-master@dev",
"doctrine/data-fixtures": "^1.8", "doctrine/data-fixtures": "^1.8",
"doctrine/doctrine-bundle": "^2.1", "doctrine/doctrine-bundle": "^2.1",
@@ -133,7 +133,6 @@
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle", "Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle", "Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
"Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src", "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src",
"Chill\\TicketBundle\\": "src/Bundle/ChillTicketBundle/src",
"Chill\\Utils\\Rector\\": "utils/rector/src" "Chill\\Utils\\Rector\\": "utils/rector/src"
} }
}, },

View File

@@ -2,6 +2,7 @@
return [ return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true],
ChampsLibres\WopiBundle\WopiBundle::class => ['all' => true], ChampsLibres\WopiBundle\WopiBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
@@ -34,8 +35,6 @@ return [
Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true], Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true],
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true], Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true], Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
Chill\TicketBundle\ChillTicketBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true], Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true],
]; ];

View File

@@ -1,2 +0,0 @@
chill_aside_activity:
show_concerned_persons_count: hidden

View File

@@ -1,5 +1,5 @@
chill_doc_store: chill_doc_store:
use_driver: local_storage use_driver: openstack
local_storage: local_storage:
storage_path: '%kernel.project_dir%/var/storage' storage_path: '%kernel.project_dir%/var/storage'
openstack: openstack:

View File

@@ -1,5 +0,0 @@
chill_ticket:
ticket:
person_per_ticket: one # One of "one"; "many"
response_time_exceeded_delay: PT12H

View File

@@ -14,7 +14,6 @@ doctrine_migrations:
'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations' 'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations'
'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations' 'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations'
'Chill\Migrations\Report': '@ChillReportBundle/migrations' 'Chill\Migrations\Report': '@ChillReportBundle/migrations'
'Chill\Migrations\Ticket': '@ChillTicketBundle/migrations'
all_or_nothing: all_or_nothing:
true true

View File

@@ -66,7 +66,6 @@ framework:
'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority 'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async 'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async
'Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage': async 'Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage': async
'Chill\TicketBundle\Messenger\PostTicketUpdateMessage': async
# end of routes added by chill-bundles recipes # end of routes added by chill-bundles recipes
# Route your messages to the transports # Route your messages to the transports
# 'App\Message\YourMessage': async # 'App\Message\YourMessage': async

View File

@@ -1,2 +0,0 @@
chill_ticket_bundle:
resource: '@ChillTicketBundle/config/routes.yaml'

View File

@@ -23,8 +23,8 @@ class "Document" {
- text description - text description
- ArrayCollection_DocumentCategory categories - ArrayCollection_DocumentCategory categories
- varchar_150 content #link to openstack - varchar_150 content #link to openstack
- Territoire territoire - Center center
- Service service - Cercle cercle
- User user - User user
- DateTime date # Creation date - DateTime date # Creation date
} }

View File

@@ -11,94 +11,24 @@
Create a new bundle Create a new bundle
******************* *******************
Create your own bundle is not a trivial task.
The easiest way to achieve this is seems to be :
1. Prepare a fresh installation of the chill project, in a new directory
2. Create a new bundle in this project, in the src directory
3. Initialize a git repository **at the root bundle**, and create your initial commit.
4. Register the bundle with composer/packagist. If you do not plan to distribute your bundle with packagist, you may use a custom repository for achieve this [#f1]_
5. Move to a development installation, made as described in the :ref:`installation-for-development` section, and add your new repository to the composer.json file
6. Work as :ref:`usual <editing-code-and-commiting>`
.. warning:: .. warning::
This part of the doc is not yet tested This part of the doc is not yet tested
Create a new directory with Bundle class TODO
----------------------------------------
.. code-block:: bash
mkdir -p src/Bundle/ChillSomeBundle/src/config
mkdir -p src/Bundle/ChillSomeBundle/src/Controller
Add a bundle file
.. code-block:: php
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\SomeBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ChillSomeBundle extends Bundle {}
And a route file:
.. code-block:: yaml
chill_ticket_controller:
resource: '@ChillTicketBundle/Controller/'
type: annotation
Register the new psr-4 namespace
--------------------------------
In composer.json, add the new psr4 namespace
.. code-block:: diff
{
"autoload": {
"psr-4": {
+ "Chill\\SomeBundle\\": "src/Bundle/ChillSomeBundle/src",
}
}
}
Register the bundle .. rubric:: Footnotes
-------------------
Register in the file :code:`config/bundles.php`:
.. code-block:: php
Vendor\Bundle\YourBundle\YourBundle::class => ['all' => true],
And import routes in :code:`config/routes/chill_some_bundle.yaml`:
.. code-block:: yaml
chill_ticket_bundle:
resource: '@ChillSomeBundle/config/routes.yaml'
Add the doctrine_migration namespace
------------------------------------
Add the namespace to :code:`config/packages/doctrine_migrations_chill.yaml`
.. code-block:: diff
doctrine_migrations:
migrations_paths:
+ 'Chill\Some\Ticket': '@ChillSomeBundle/migrations'
Dump autoloading
----------------
.. code-block:: bash
symfony composer dump-autoload
.. [#f1] Be aware that we use the Affero GPL Licence, which ensure that all users must have access to derivative works done with this software.

View File

@@ -38,7 +38,7 @@ Certaines données sont historisées:
- les référents d'un parcours; - les référents d'un parcours;
- les statuts d'un parcours; - les statuts d'un parcours;
- la liaison entre les territoires et les usagers; - la liaison entre les centres et les usagers;
- etc. - etc.
Dans ces cas-là, Chill crée généralement deux colonnes, qui sont habituellement nommées :code:`startDate` et :code:`endDate`. Lorsque la colonne :code:`endDate` est à :code:`NULL`, cela signifie que la période n'est pas "fermée". La colonne :code:`startDate` n'est pas nullable. Dans ces cas-là, Chill crée généralement deux colonnes, qui sont habituellement nommées :code:`startDate` et :code:`endDate`. Lorsque la colonne :code:`endDate` est à :code:`NULL`, cela signifie que la période n'est pas "fermée". La colonne :code:`startDate` n'est pas nullable.

View File

@@ -1,6 +1,6 @@
order,table_schema,table_name,commentaire order,table_schema,table_name,commentaire
1,chill_3party,party_category,Catégorie de tiers 1,chill_3party,party_category,Catégorie de tiers
2,chill_3party,party_center,Association entre les tiers et les territoires (déprécié) 2,chill_3party,party_center,Association entre les tiers et les centres (déprécié)
3,chill_3party,party_profession,Profession du tiers (déprécié) 3,chill_3party,party_profession,Profession du tiers (déprécié)
4,chill_3party,third_party,Tiers 4,chill_3party,third_party,Tiers
5,chill_3party,thirdparty_category,association tiers - catégories 5,chill_3party,thirdparty_category,association tiers - catégories
@@ -54,7 +54,7 @@ order,table_schema,table_name,commentaire
53,public,activitytpresence,Présence aux échanges 53,public,activitytpresence,Présence aux échanges
54,public,activitytype,Types d'échanges 54,public,activitytype,Types d'échanges
55,public,activitytypecategory,Catégories de types d'échanges 55,public,activitytypecategory,Catégories de types d'échanges
56,public,centers,"Territoires (territoires, agences, etc.)" 56,public,centers,"Centres (territoires, agences, etc.)"
57,public,chill_activity_activity_chill_person_socialaction, 57,public,chill_activity_activity_chill_person_socialaction,
58,public,chill_activity_activity_chill_person_socialissue 58,public,chill_activity_activity_chill_person_socialissue
59,public,chill_docgen_template,Gabarits de documents 59,public,chill_docgen_template,Gabarits de documents
@@ -111,7 +111,7 @@ order,table_schema,table_name,commentaire
110,public,chill_person_marital_status,Etats civils 110,public,chill_person_marital_status,Etats civils
111,public,chill_person_not_duplicate, 111,public,chill_person_not_duplicate,
112,public,chill_person_person,Usagers 112,public,chill_person_person,Usagers
113,public,chill_person_person_center_history,Historique des territoires d'un usagers 113,public,chill_person_person_center_history,Historique des centres d'un usagers
114,public,chill_person_persons_to_addresses,Déprécié 114,public,chill_person_persons_to_addresses,Déprécié
115,public,chill_person_phone,Numéros d etéléphone supplémentaires d'un usager 115,public,chill_person_phone,Numéros d etéléphone supplémentaires d'un usager
116,public,chill_person_relations,Types de relations de filiation 116,public,chill_person_relations,Types de relations de filiation
@@ -142,7 +142,7 @@ order,table_schema,table_name,commentaire
141,public,permission_groups 141,public,permission_groups
142,public,permissionsgroup_rolescope 142,public,permissionsgroup_rolescope
143,public,persons_spoken_languages 143,public,persons_spoken_languages
144,public,regroupment,Regroupement de territoires 144,public,regroupment,Regroupement de centres
145,public,regroupment_center, 145,public,regroupment_center,
146,public,role_scopes, 146,public,role_scopes,
147,public,scopes,Services 147,public,scopes,Services
Can't render this file because it has a wrong number of fields in line 28.

View File

@@ -41,7 +41,6 @@
"typescript": "^5.6.3", "typescript": "^5.6.3",
"typescript-eslint": "^8.13.0", "typescript-eslint": "^8.13.0",
"vue-loader": "^17.0.0", "vue-loader": "^17.0.0",
"vue-tsc": "^3.1.3",
"webpack": "^5.75.0", "webpack": "^5.75.0",
"webpack-cli": "^5.0.1" "webpack-cli": "^5.0.1"
}, },
@@ -81,12 +80,12 @@
"dev": "encore dev", "dev": "encore dev",
"watch": "encore dev --watch", "watch": "encore dev --watch",
"build": "encore production --progress", "build": "encore production --progress",
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml src/Bundle/ChillTicketBundle/chill.api.specs.yaml> templates/api/specs.yaml", "specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> templates/api/specs.yaml",
"specs-validate": "swagger-cli validate templates/api/specs.yaml", "specs-validate": "swagger-cli validate templates/api/specs.yaml",
"specs-create-dir": "mkdir -p templates/api", "specs-create-dir": "mkdir -p templates/api",
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate", "specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
"version": "node --version", "version": "node --version",
"eslint": "eslint-baseline --fix \"src/**/*.{js,ts,vue}\"" "eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
}, },
"private": true "private": true
} }

View File

@@ -58,10 +58,6 @@
<!-- temporarily removed, the time to find a fix --> <!-- temporarily removed, the time to find a fix -->
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude> <exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
</testsuite> </testsuite>
<testsuite name="TicketBundle">
<directory suffix="Test.php">src/Bundle/ChillTicketBundle/tests/</directory>
</testsuite>
<!-- <!--
<testsuite name="ReportBundle"> <testsuite name="ReportBundle">
<directory suffix="Test.php">src/Bundle/ChillReportBundle/Tests/</directory> <directory suffix="Test.php">src/Bundle/ChillReportBundle/Tests/</directory>

View File

@@ -1,8 +0,0 @@
In this directory, you find an example of file for the command `chill:main:ticket_motives_import`.
This file contains a list of ticket motives to import into the system. Each entry is a dictionary with two keys: `code` and `label`. The `code` key contains the unique code for the ticket motive, and the `label` key contains the human-readable label for the ticket motive.
The `stored_objects` key contains the documents that will be associated with the tickets. They must be found in the same directory.
The command `chill:main:ticket_motives_import` uses this file to import the specified ticket motives into the system.

View File

@@ -1,136 +0,0 @@
- label:
fr: Appel famille pour annonce de décès
urgent: false
supplementary_informations:
- label:
fr: Date du décès
- label:
fr: lieu du décès (domicile ou hôpital)
- label:
fr: nom de lhôpital
- label:
fr: service concerné
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 2_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 3_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 4_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 'Appel famille pour annonce absence : hospitalisation ou consultation'
urgent: false
supplementary_informations:
- label:
fr: Quel hôpital
- label:
fr: quel service
- label:
fr: pour quelles raisons
- label:
fr: 'consultation : date et heure'
- label:
fr: hospitalisation complète ou HDJ
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 5_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 6_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 7_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 'Appel famille pour annonce absence : interruption de prise en charge'
urgent: false
supplementary_informations:
- label:
fr: Pour quelles raisons ? Date
- label:
fr: durée
- label:
fr: accord médical ?
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 8_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 9_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 10_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 'Appel famille pour annonce absence : changement dadresse'
urgent: false
supplementary_informations:
- label:
fr:
- label:
fr: Pourquoi ? Pour combien de temps ? Besoin dun relais des soins ? Nouvelle adresse ?
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 11_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 12_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 13_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: Appel famille pour altération de létat général du patient
urgent: true
supplementary_informations:
- label:
fr: Recherche des symptômes
- label:
fr: Attentes par rapport à la demande
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 14_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 15_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 16_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: Appel famille pour prise en charge de la douleur
urgent: true
supplementary_informations:
- label:
fr: Localisation douleur
- label:
fr: Horaire dernier passage
- label:
fr: Traitements en cours
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 17_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 18_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 19_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: Appel famille pour information sur la date de prise en charge
urgent: false
supplementary_informations: []
stored_objects:
- label:
fr: ☀️ De 07h à 21h
filename: 20_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🌙 De 21h à 07h du matin
filename: 21_doc_20250402_Pelotons flux externes consolidés.pdf
- label:
fr: 🗓️ Dimanches et jours fériés
filename: 22_doc_20250402_Pelotons flux externes consolidés.pdf

View File

@@ -1,6 +0,0 @@
In this directory, you find an example of file for the command `chill:main:override_translation`.
This file contains a list of translations to override in the translation catalogue. Each entry is a dictionary with two keys: `from` and `to`. The `from` key contains the original translation string, and the `to` key contains the replacement string.
The command `chill:main:override_translation` uses this file to generate a new translation catalogue with the specified overrides applied.

View File

@@ -1,8 +0,0 @@
- {from: "de l'usager", to: "du patient"}
- {from: "l'usager", to: "le patient"}
- {from: "L'usager", to: "Le patient"}
- {from: "d'usagers", to: "de patients"}
- {from: "usagers", to: "patients"}
- {from: "Usagers", to: "Patients"}
- {from: "usager", to: "patient"}
- {from: "Usager", to: "Patient"}

View File

@@ -66,9 +66,6 @@ class ListActivityHelper
->leftJoin('activity.location', 'location') ->leftJoin('activity.location', 'location')
->addSelect('location.name AS locationName') ->addSelect('location.name AS locationName')
->addSelect('activity.sentReceived') ->addSelect('activity.sentReceived')
->addSelect('activity.comment.comment AS commentText')
->addSelect('activity.comment.date AS commentDate')
->addSelect('JSON_BUILD_OBJECT(\'uid\', activity.comment.userId, \'d\', activity.comment.date) AS commentUser')
->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.createdBy), \'d\', activity.createdAt) AS createdBy') ->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.createdBy), \'d\', activity.createdAt) AS createdBy')
->addSelect('activity.createdAt') ->addSelect('activity.createdAt')
->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.updatedBy), \'d\', activity.updatedAt) AS updatedBy') ->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.updatedBy), \'d\', activity.updatedAt) AS updatedBy')
@@ -90,8 +87,6 @@ class ListActivityHelper
'createdAt', 'updatedAt' => $this->dateTimeHelper->getLabel($key), 'createdAt', 'updatedAt' => $this->dateTimeHelper->getLabel($key),
'createdBy', 'updatedBy' => $this->userHelper->getLabel($key, $values, $key), 'createdBy', 'updatedBy' => $this->userHelper->getLabel($key, $values, $key),
'date' => $this->dateTimeHelper->getLabel(self::MSG_KEY.$key), 'date' => $this->dateTimeHelper->getLabel(self::MSG_KEY.$key),
'commentDate' => $this->dateTimeHelper->getLabel(self::MSG_KEY.'comment_date'),
'commentUser' => $this->userHelper->getLabel($key, $values, self::MSG_KEY.'comment_user'),
'attendeeName' => function ($value) { 'attendeeName' => function ($value) {
if ('_header' === $value) { if ('_header' === $value) {
return 'Attendee'; return 'Attendee';
@@ -181,9 +176,6 @@ class ListActivityHelper
'usersNames', 'usersNames',
'thirdPartiesIds', 'thirdPartiesIds',
'thirdPartiesNames', 'thirdPartiesNames',
'commentText',
'commentDate',
'commentUser',
'createdBy', 'createdBy',
'createdAt', 'createdAt',
'updatedBy', 'updatedBy',

View File

@@ -1,7 +1,7 @@
<template> <template>
<concerned-groups v-if="hasPerson" /> <concerned-groups v-if="hasPerson" />
<social-issues-acc v-if="hasSocialIssues" /> <social-issues-acc v-if="hasSocialIssues" />
<location v-if="hasLocation" /> <location v-if="hasLocation" />
</template> </template>
<script> <script>
@@ -10,12 +10,12 @@ import SocialIssuesAcc from "./components/SocialIssuesAcc.vue";
import Location from "./components/Location.vue"; import Location from "./components/Location.vue";
export default { export default {
name: "App", name: "App",
props: ["hasSocialIssues", "hasLocation", "hasPerson"], props: ["hasSocialIssues", "hasLocation", "hasPerson"],
components: { components: {
ConcernedGroups, ConcernedGroups,
SocialIssuesAcc, SocialIssuesAcc,
Location, Location,
}, },
}; };
</script> </script>

View File

@@ -1,43 +1,46 @@
<template> <template>
<teleport to="#add-persons" v-if="isComponentVisible"> <teleport to="#add-persons" v-if="isComponentVisible">
<div class="flex-bloc concerned-groups" :class="getContext"> <div class="flex-bloc concerned-groups" :class="getContext">
<persons-bloc <persons-bloc
v-for="bloc in contextPersonsBlocs" v-for="bloc in contextPersonsBlocs"
:key="bloc.key" :key="bloc.key"
:bloc="bloc" :bloc="bloc"
:bloc-width="getBlocWidth" :bloc-width="getBlocWidth"
:set-persons-in-bloc="setPersonsInBloc" :set-persons-in-bloc="setPersonsInBloc"
/> />
</div> </div>
<div <div
v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0" v-if="
> getContext === 'accompanyingCourse' &&
<ul class="list-suggest add-items inline"> suggestedEntities.length > 0
<li "
v-for="(p, i) in suggestedEntities"
@click="addSuggestedEntity(p)"
:key="`suggestedEntities-${i}`"
> >
<person-text v-if="p.type === 'person'" :person="p" /> <ul class="list-suggest add-items inline">
<span v-else>{{ p.text }}</span> <li
</li> v-for="(p, i) in suggestedEntities"
</ul> @click="addSuggestedEntity(p)"
</div> :key="`suggestedEntities-${i}`"
>
<person-text v-if="p.type === 'person'" :person="p" />
<span v-else>{{ p.text }}</span>
</li>
</ul>
</div>
<ul class="record_actions"> <ul class="record_actions">
<li class="add-persons"> <li class="add-persons">
<add-persons <add-persons
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)" :buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
:modalTitle="trans(ACTIVITY_ADD_PERSONS)" :modalTitle="trans(ACTIVITY_ADD_PERSONS)"
v-bind:key="addPersons.key" v-bind:key="addPersons.key"
v-bind:options="addPersonsOptions" v-bind:options="addPersonsOptions"
@addNewPersons="addNewPersons" @addNewPersons="addNewPersons"
ref="addPersons" ref="addPersons"
> >
</add-persons> </add-persons>
</li> </li>
</ul> </ul>
</teleport> </teleport>
</template> </template>
<script> <script>
@@ -46,208 +49,208 @@ import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue"; import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue"; import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
import { import {
ACTIVITY_BLOC_PERSONS, ACTIVITY_BLOC_PERSONS,
ACTIVITY_BLOC_PERSONS_ASSOCIATED, ACTIVITY_BLOC_PERSONS_ASSOCIATED,
ACTIVITY_BLOC_THIRDPARTY, ACTIVITY_BLOC_THIRDPARTY,
ACTIVITY_BLOC_USERS, ACTIVITY_BLOC_USERS,
ACTIVITY_ADD_PERSONS, ACTIVITY_ADD_PERSONS,
trans, trans,
} from "translator"; } from "translator";
export default { export default {
name: "ConcernedGroups", name: "ConcernedGroups",
components: { components: {
AddPersons, AddPersons,
PersonsBloc, PersonsBloc,
PersonText, PersonText,
}, },
setup() { setup() {
return { return {
trans, trans,
ACTIVITY_ADD_PERSONS, ACTIVITY_ADD_PERSONS,
}; };
}, },
data() { data() {
return { return {
personsBlocs: [ personsBlocs: [
{ {
key: "persons", key: "persons",
title: trans(ACTIVITY_BLOC_PERSONS), title: trans(ACTIVITY_BLOC_PERSONS),
persons: [], persons: [],
included: false, included: false,
},
{
key: "personsAssociated",
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
persons: [],
included: window.activity
? window.activity.activityType.personsVisible !== 0
: true,
},
{
key: "personsNotAssociated",
title: "activity.bloc_persons_not_associated",
persons: [],
included: window.activity
? window.activity.activityType.personsVisible !== 0
: true,
},
{
key: "thirdparty",
title: trans(ACTIVITY_BLOC_THIRDPARTY),
persons: [],
included: window.activity
? window.activity.activityType.thirdPartiesVisible !== 0
: true,
},
{
key: "users",
title: trans(ACTIVITY_BLOC_USERS),
persons: [],
included: window.activity
? window.activity.activityType.usersVisible !== 0
: true,
},
],
addPersons: {
key: "activity",
},
};
},
computed: {
isComponentVisible() {
return window.activity
? window.activity.activityType.personsVisible !== 0 ||
window.activity.activityType.thirdPartiesVisible !== 0 ||
window.activity.activityType.usersVisible !== 0
: true;
}, },
{ ...mapState({
key: "personsAssociated", persons: (state) => state.activity.persons,
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED), thirdParties: (state) => state.activity.thirdParties,
persons: [], users: (state) => state.activity.users,
included: window.activity accompanyingCourse: (state) => state.activity.accompanyingPeriod,
? window.activity.activityType.personsVisible !== 0 }),
: true, ...mapGetters(["suggestedEntities"]),
getContext() {
return this.accompanyingCourse ? "accompanyingCourse" : "person";
}, },
{ contextPersonsBlocs() {
key: "personsNotAssociated", return this.personsBlocs.filter((bloc) => bloc.included !== false);
title: "activity.bloc_persons_not_associated",
persons: [],
included: window.activity
? window.activity.activityType.personsVisible !== 0
: true,
}, },
{ addPersonsOptions() {
key: "thirdparty", let optionsType = [];
title: trans(ACTIVITY_BLOC_THIRDPARTY), if (window.activity) {
persons: [], if (window.activity.activityType.personsVisible !== 0) {
included: window.activity optionsType.push("person");
? window.activity.activityType.thirdPartiesVisible !== 0 }
: true, if (window.activity.activityType.thirdPartiesVisible !== 0) {
optionsType.push("thirdparty");
}
if (window.activity.activityType.usersVisible !== 0) {
optionsType.push("user");
}
} else {
optionsType = ["person", "thirdparty", "user"];
}
return {
type: optionsType,
priority: null,
uniq: false,
button: {
size: "btn-sm",
},
};
}, },
{ getBlocWidth() {
key: "users", return Math.round(100 / this.contextPersonsBlocs.length) + "%";
title: trans(ACTIVITY_BLOC_USERS),
persons: [],
included: window.activity
? window.activity.activityType.usersVisible !== 0
: true,
}, },
],
addPersons: {
key: "activity",
},
};
},
computed: {
isComponentVisible() {
return window.activity
? window.activity.activityType.personsVisible !== 0 ||
window.activity.activityType.thirdPartiesVisible !== 0 ||
window.activity.activityType.usersVisible !== 0
: true;
}, },
...mapState({ mounted() {
persons: (state) => state.activity.persons, this.setPersonsInBloc();
thirdParties: (state) => state.activity.thirdParties,
users: (state) => state.activity.users,
accompanyingCourse: (state) => state.activity.accompanyingPeriod,
}),
...mapGetters(["suggestedEntities"]),
getContext() {
return this.accompanyingCourse ? "accompanyingCourse" : "person";
}, },
contextPersonsBlocs() { methods: {
return this.personsBlocs.filter((bloc) => bloc.included !== false); setPersonsInBloc() {
}, let groups;
addPersonsOptions() { if (this.accompanyingCourse) {
let optionsType = []; groups = this.splitPersonsInGroups();
if (window.activity) { }
if (window.activity.activityType.personsVisible !== 0) { this.personsBlocs.forEach((bloc) => {
optionsType.push("person"); if (this.accompanyingCourse) {
} switch (bloc.key) {
if (window.activity.activityType.thirdPartiesVisible !== 0) { case "personsAssociated":
optionsType.push("thirdparty"); bloc.persons = groups.personsAssociated;
} bloc.included = true;
if (window.activity.activityType.usersVisible !== 0) { break;
optionsType.push("user"); case "personsNotAssociated":
} bloc.persons = groups.personsNotAssociated;
} else { bloc.included = true;
optionsType = ["person", "thirdparty", "user"]; break;
} }
return { } else {
type: optionsType, switch (bloc.key) {
priority: null, case "persons":
uniq: false, bloc.persons = this.persons;
button: { bloc.included = true;
size: "btn-sm", break;
}
}
switch (bloc.key) {
case "thirdparty":
bloc.persons = this.thirdParties;
break;
case "users":
bloc.persons = this.users;
break;
}
}, groups);
},
splitPersonsInGroups() {
let personsAssociated = [];
let personsNotAssociated = this.persons;
let participations = this.getCourseParticipations();
this.persons.forEach((person) => {
participations.forEach((participation) => {
if (person.id === participation.id) {
//console.log(person.id);
personsAssociated.push(person);
personsNotAssociated = personsNotAssociated.filter(
(p) => p !== person,
);
}
});
});
return {
personsAssociated: personsAssociated,
personsNotAssociated: personsNotAssociated,
};
},
getCourseParticipations() {
let participations = [];
this.accompanyingCourse.participations.forEach((participation) => {
if (!participation.endDate) {
participations.push(participation.person);
}
});
return participations;
},
addNewPersons({ selected, modal }) {
console.log("@@@ CLICK button addNewPersons", selected);
selected.forEach((item) => {
this.$store.dispatch("addPersonsInvolved", item);
}, this);
this.$refs.addPersons.resetSearch(); // to cast child method
modal.showModal = false;
this.setPersonsInBloc();
},
addSuggestedEntity(person) {
this.$store.dispatch("addPersonsInvolved", {
result: person,
type: "person",
});
this.setPersonsInBloc();
}, },
};
}, },
getBlocWidth() {
return Math.round(100 / this.contextPersonsBlocs.length) + "%";
},
},
mounted() {
this.setPersonsInBloc();
},
methods: {
setPersonsInBloc() {
let groups;
if (this.accompanyingCourse) {
groups = this.splitPersonsInGroups();
}
this.personsBlocs.forEach((bloc) => {
if (this.accompanyingCourse) {
switch (bloc.key) {
case "personsAssociated":
bloc.persons = groups.personsAssociated;
bloc.included = true;
break;
case "personsNotAssociated":
bloc.persons = groups.personsNotAssociated;
bloc.included = true;
break;
}
} else {
switch (bloc.key) {
case "persons":
bloc.persons = this.persons;
bloc.included = true;
break;
}
}
switch (bloc.key) {
case "thirdparty":
bloc.persons = this.thirdParties;
break;
case "users":
bloc.persons = this.users;
break;
}
}, groups);
},
splitPersonsInGroups() {
let personsAssociated = [];
let personsNotAssociated = this.persons;
let participations = this.getCourseParticipations();
this.persons.forEach((person) => {
participations.forEach((participation) => {
if (person.id === participation.id) {
//console.log(person.id);
personsAssociated.push(person);
personsNotAssociated = personsNotAssociated.filter(
(p) => p !== person,
);
}
});
});
return {
personsAssociated: personsAssociated,
personsNotAssociated: personsNotAssociated,
};
},
getCourseParticipations() {
let participations = [];
this.accompanyingCourse.participations.forEach((participation) => {
if (!participation.endDate) {
participations.push(participation.person);
}
});
return participations;
},
addNewPersons({ selected, modal }) {
console.log("@@@ CLICK button addNewPersons", selected);
selected.forEach((item) => {
this.$store.dispatch("addPersonsInvolved", item);
}, this);
this.$refs.addPersons.resetSearch(); // to cast child method
modal.showModal = false;
this.setPersonsInBloc();
},
addSuggestedEntity(person) {
this.$store.dispatch("addPersonsInvolved", {
result: person,
type: "person",
});
this.setPersonsInBloc();
},
},
}; };
</script> </script>

View File

@@ -1,29 +1,29 @@
<template> <template>
<li> <li>
<span :title="person.text" @click.prevent="$emit('remove', person)"> <span :title="person.text" @click.prevent="$emit('remove', person)">
<span class="chill_denomination"> <span class="chill_denomination">
<person-text :person="person" :is-cut="true" /> <person-text :person="person" :is-cut="true" />
</span> </span>
</span> </span>
</li> </li>
</template> </template>
<script> <script>
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue"; import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
export default { export default {
name: "PersonBadge", name: "PersonBadge",
props: ["person"], props: ["person"],
components: { components: {
PersonText, PersonText,
}, },
// computed: { // computed: {
// textCutted() { // textCutted() {
// let more = (this.person.text.length > 15) ?'…' : ''; // let more = (this.person.text.length > 15) ?'…' : '';
// return this.person.text.slice(0,15) + more; // return this.person.text.slice(0,15) + more;
// } // }
// }, // },
emits: ["remove"], emits: ["remove"],
}; };
</script> </script>

View File

@@ -1,38 +1,38 @@
<template> <template>
<div class="item-bloc" :style="{ 'flex-basis': blocWidth }"> <div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
<div class="item-row"> <div class="item-row">
<div class="item-col"> <div class="item-col">
<h4>{{ $t(bloc.title) }}</h4> <h4>{{ $t(bloc.title) }}</h4>
</div> </div>
<div class="item-col"> <div class="item-col">
<ul class="list-suggest remove-items"> <ul class="list-suggest remove-items">
<person-badge <person-badge
v-for="person in bloc.persons" v-for="person in bloc.persons"
:key="person.id" :key="person.id"
:person="person" :person="person"
@remove="removePerson" @remove="removePerson"
/> />
</ul> </ul>
</div> </div>
</div>
</div> </div>
</div>
</template> </template>
<script> <script>
import PersonBadge from "./PersonBadge.vue"; import PersonBadge from "./PersonBadge.vue";
export default { export default {
name: "PersonsBloc", name: "PersonsBloc",
components: { components: {
PersonBadge, PersonBadge,
}, },
props: ["bloc", "setPersonsInBloc", "blocWidth"], props: ["bloc", "setPersonsInBloc", "blocWidth"],
methods: { methods: {
removePerson(item) { removePerson(item) {
console.log("@@ CLICK remove person: item", item); console.log("@@ CLICK remove person: item", item);
this.$store.dispatch("removePersonInvolved", item); this.$store.dispatch("removePersonInvolved", item);
this.setPersonsInBloc(); this.setPersonsInBloc();
},
}, },
},
}; };
</script> </script>

View File

@@ -1,32 +1,32 @@
<template> <template>
<teleport to="#location"> <teleport to="#location">
<div class="mb-3 row"> <div class="mb-3 row">
<label :class="locationClassList"> <label :class="locationClassList">
{{ trans(ACTIVITY_LOCATION) }} {{ trans(ACTIVITY_LOCATION) }}
</label> </label>
<div class="col-sm-8"> <div class="col-sm-8">
<VueMultiselect <VueMultiselect
name="selectLocation" name="selectLocation"
id="selectLocation" id="selectLocation"
label="name" label="name"
track-by="id" track-by="id"
open-direction="top" open-direction="top"
:multiple="false" :multiple="false"
:searchable="true" :searchable="true"
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)" :placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
:custom-label="customLabel" :custom-label="customLabel"
:select-label="trans(MULTISELECT_SELECT_LABEL)" :select-label="trans(MULTISELECT_SELECT_LABEL)"
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)" :deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
:selected-label="trans(MULTISELECT_SELECTED_LABEL)" :selected-label="trans(MULTISELECT_SELECTED_LABEL)"
:options="availableLocations" :options="availableLocations"
group-values="locations" group-values="locations"
group-label="locationGroup" group-label="locationGroup"
v-model="location" v-model="location"
/> />
<new-location v-bind:available-locations="availableLocations" /> <new-location v-bind:available-locations="availableLocations" />
</div> </div>
</div> </div>
</teleport> </teleport>
</template> </template>
<script> <script>
@@ -35,60 +35,60 @@ import VueMultiselect from "vue-multiselect";
import NewLocation from "./Location/NewLocation.vue"; import NewLocation from "./Location/NewLocation.vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper"; import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import { import {
trans, trans,
ACTIVITY_LOCATION, ACTIVITY_LOCATION,
ACTIVITY_CHOOSE_LOCATION, ACTIVITY_CHOOSE_LOCATION,
MULTISELECT_SELECT_LABEL, MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL, MULTISELECT_DESELECT_LABEL,
MULTISELECT_SELECTED_LABEL, MULTISELECT_SELECTED_LABEL,
} from "translator"; } from "translator";
export default { export default {
name: "Location", name: "Location",
components: { components: {
NewLocation, NewLocation,
VueMultiselect, VueMultiselect,
},
setup() {
return {
trans,
ACTIVITY_LOCATION,
ACTIVITY_CHOOSE_LOCATION,
MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL,
MULTISELECT_SELECTED_LABEL,
};
},
data() {
return {
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
};
},
computed: {
...mapState(["activity", "availableLocations"]),
...mapGetters(["suggestedEntities"]),
location: {
get() {
return this.activity.location;
},
set(value) {
this.$store.dispatch("updateLocation", value);
},
}, },
}, setup() {
methods: { return {
labelAccompanyingCourseLocation(value) { trans,
return `${value.address.text} (${localizeString(value.locationType.title)})`; ACTIVITY_LOCATION,
ACTIVITY_CHOOSE_LOCATION,
MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL,
MULTISELECT_SELECTED_LABEL,
};
}, },
customLabel(value) { data() {
return value.locationType return {
? value.name locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
? value.name === "__AccompanyingCourseLocation__" };
? this.labelAccompanyingCourseLocation(value) },
: `${value.name} (${localizeString(value.locationType.title)})` computed: {
: localizeString(value.locationType.title) ...mapState(["activity", "availableLocations"]),
: ""; ...mapGetters(["suggestedEntities"]),
location: {
get() {
return this.activity.location;
},
set(value) {
this.$store.dispatch("updateLocation", value);
},
},
},
methods: {
labelAccompanyingCourseLocation(value) {
return `${value.address.text} (${localizeString(value.locationType.title)})`;
},
customLabel(value) {
return value.locationType
? value.name
? value.name === "__AccompanyingCourseLocation__"
? this.labelAccompanyingCourseLocation(value)
: `${value.name} (${localizeString(value.locationType.title)})`
: localizeString(value.locationType.title)
: "";
},
}, },
},
}; };
</script> </script>

View File

@@ -1,114 +1,123 @@
<template> <template>
<div> <div>
<ul class="record_actions"> <ul class="record_actions">
<li> <li>
<a class="btn btn-sm btn-create" @click="openModal"> <a class="btn btn-sm btn-create" @click="openModal">
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }} {{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
</a> </a>
</li> </li>
</ul> </ul>
<teleport to="body"> <teleport to="body">
<modal <modal
v-if="modal.showModal" v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass" :modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false" @close="modal.showModal = false"
> >
<template #header> <template #header>
<h3 class="modal-title"> <h3 class="modal-title">
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }} {{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
</h3> </h3>
</template> </template>
<template #body> <template #body>
<form> <form>
<div class="alert alert-warning" v-if="errors.length"> <div class="alert alert-warning" v-if="errors.length">
<ul> <ul>
<li v-for="(e, i) in errors" :key="i"> <li v-for="(e, i) in errors" :key="i">
{{ e }} {{ e }}
</li> </li>
</ul> </ul>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<select <select
class="form-select form-select-lg" class="form-select form-select-lg"
id="type" id="type"
required required
v-model="selectType" v-model="selectType"
> >
<option selected disabled value=""> <option selected disabled value="">
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }} {{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
</option> </option>
<option v-for="t in locationTypes" :value="t" :key="t.id"> <option
{{ localizeString(t.title) }} v-for="t in locationTypes"
</option> :value="t"
</select> :key="t.id"
<label>{{ trans(ACTIVITY_LOCATION_FIELDS_TYPE) }}</label> >
</div> {{ localizeString(t.title) }}
</option>
</select>
<label>{{
trans(ACTIVITY_LOCATION_FIELDS_TYPE)
}}</label>
</div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input <input
class="form-control form-control-lg" class="form-control form-control-lg"
id="name" id="name"
v-model="inputName" v-model="inputName"
placeholder placeholder
/> />
<label for="name">{{ <label for="name">{{
trans(ACTIVITY_LOCATION_FIELDS_NAME) trans(ACTIVITY_LOCATION_FIELDS_NAME)
}}</label> }}</label>
</div> </div>
<add-address <add-address
:context="addAddress.context" :context="addAddress.context"
:options="addAddress.options" :options="addAddress.options"
:addressChangedCallback="submitNewAddress" :addressChangedCallback="submitNewAddress"
v-if="showAddAddress" v-if="showAddAddress"
ref="addAddress" ref="addAddress"
/> />
<div class="form-floating mb-3" v-if="showContactData"> <div class="form-floating mb-3" v-if="showContactData">
<input <input
class="form-control form-control-lg" class="form-control form-control-lg"
id="phonenumber1" id="phonenumber1"
v-model="inputPhonenumber1" v-model="inputPhonenumber1"
placeholder placeholder
/> />
<label for="phonenumber1">{{ <label for="phonenumber1">{{
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1) trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
}}</label> }}</label>
</div> </div>
<div class="form-floating mb-3" v-if="hasPhonenumber1"> <div class="form-floating mb-3" v-if="hasPhonenumber1">
<input <input
class="form-control form-control-lg" class="form-control form-control-lg"
id="phonenumber2" id="phonenumber2"
v-model="inputPhonenumber2" v-model="inputPhonenumber2"
placeholder placeholder
/> />
<label for="phonenumber2">{{ <label for="phonenumber2">{{
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2) trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
}}</label> }}</label>
</div> </div>
<div class="form-floating mb-3" v-if="showContactData"> <div class="form-floating mb-3" v-if="showContactData">
<input <input
class="form-control form-control-lg" class="form-control form-control-lg"
id="email" id="email"
v-model="inputEmail" v-model="inputEmail"
placeholder placeholder
/> />
<label for="email">{{ <label for="email">{{
trans(ACTIVITY_LOCATION_FIELDS_EMAIL) trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
}}</label> }}</label>
</div> </div>
</form> </form>
</template> </template>
<template #footer> <template #footer>
<button class="btn btn-save" @click.prevent="saveNewLocation"> <button
{{ trans(SAVE) }} class="btn btn-save"
</button> @click.prevent="saveNewLocation"
</template> >
</modal> {{ trans(SAVE) }}
</teleport> </button>
</div> </template>
</modal>
</teleport>
</div>
</template> </template>
<script> <script>
@@ -119,236 +128,237 @@ import { getLocationTypes } from "../../api";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper"; import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import { import {
SAVE, SAVE,
ACTIVITY_LOCATION_FIELDS_EMAIL, ACTIVITY_LOCATION_FIELDS_EMAIL,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1, ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2, ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
ACTIVITY_LOCATION_FIELDS_NAME, ACTIVITY_LOCATION_FIELDS_NAME,
ACTIVITY_LOCATION_FIELDS_TYPE, ACTIVITY_LOCATION_FIELDS_TYPE,
ACTIVITY_CHOOSE_LOCATION_TYPE, ACTIVITY_CHOOSE_LOCATION_TYPE,
ACTIVITY_CREATE_NEW_LOCATION, ACTIVITY_CREATE_NEW_LOCATION,
trans, trans,
} from "translator"; } from "translator";
export default { export default {
name: "NewLocation", name: "NewLocation",
components: { components: {
Modal, Modal,
AddAddress, AddAddress,
},
setup() {
return {
trans,
SAVE,
ACTIVITY_LOCATION_FIELDS_EMAIL,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
ACTIVITY_LOCATION_FIELDS_NAME,
ACTIVITY_LOCATION_FIELDS_TYPE,
ACTIVITY_CHOOSE_LOCATION_TYPE,
ACTIVITY_CREATE_NEW_LOCATION,
};
},
props: ["availableLocations"],
data() {
return {
errors: [],
selected: {
type: null,
name: null,
addressId: null,
phonenumber1: null,
phonenumber2: null,
email: null,
},
locationTypes: [],
modal: {
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl",
},
addAddress: {
options: {
button: {
text: {
create: "activity.create_address",
edit: "activity.edit_address",
},
size: "btn-sm",
},
title: {
create: "activity.create_address",
edit: "activity.edit_address",
},
},
context: {
target: {
//name, id
},
edit: false,
addressId: null,
defaults: window.addaddress,
},
},
};
},
computed: {
...mapState(["activity"]),
selectType: {
get() {
return this.selected.type;
},
set(value) {
this.selected.type = value;
},
}, },
inputName: { setup() {
get() { return {
return this.selected.name; trans,
}, SAVE,
set(value) { ACTIVITY_LOCATION_FIELDS_EMAIL,
this.selected.name = value; ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
}, ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
}, ACTIVITY_LOCATION_FIELDS_NAME,
inputEmail: { ACTIVITY_LOCATION_FIELDS_TYPE,
get() { ACTIVITY_CHOOSE_LOCATION_TYPE,
return this.selected.email; ACTIVITY_CREATE_NEW_LOCATION,
},
set(value) {
this.selected.email = value;
},
},
inputPhonenumber1: {
get() {
return this.selected.phonenumber1;
},
set(value) {
this.selected.phonenumber1 = value;
},
},
inputPhonenumber2: {
get() {
return this.selected.phonenumber2;
},
set(value) {
this.selected.phonenumber2 = value;
},
},
hasPhonenumber1() {
return (
this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== ""
);
},
showAddAddress() {
let cond = false;
if (this.selected.type) {
if (this.selected.type.addressRequired !== "never") {
cond = true;
}
}
return cond;
},
showContactData() {
let cond = false;
if (this.selected.type) {
if (this.selected.type.contactData !== "never") {
cond = true;
}
}
return cond;
},
},
mounted() {
this.getLocationTypesList();
},
methods: {
localizeString,
checkForm() {
let cond = true;
this.errors = [];
if (!this.selected.type) {
this.errors.push("Type de localisation requis");
cond = false;
} else {
if (
this.selected.type.addressRequired === "required" &&
!this.selected.addressId
) {
this.errors.push("Adresse requise");
cond = false;
}
if (
this.selected.type.contactData === "required" &&
!this.selected.phonenumber1
) {
this.errors.push("Numéro de téléphone requis");
cond = false;
}
if (
this.selected.type.contactData === "required" &&
!this.selected.email
) {
this.errors.push("Adresse email requise");
cond = false;
}
}
return cond;
},
getLocationTypesList() {
getLocationTypes().then((results) => {
this.locationTypes = results.filter(
(t) => t.availableForUsers === true,
);
});
},
openModal() {
this.modal.showModal = true;
},
saveNewLocation() {
if (this.checkForm()) {
let body = {
type: "location",
name: this.selected.name,
locationType: {
id: this.selected.type.id,
type: "location-type",
},
phonenumber1: this.selected.phonenumber1,
phonenumber2: this.selected.phonenumber2,
email: this.selected.email,
}; };
if (this.selected.addressId) { },
body = Object.assign(body, { props: ["availableLocations"],
address: { data() {
id: this.selected.addressId, return {
errors: [],
selected: {
type: null,
name: null,
addressId: null,
phonenumber1: null,
phonenumber2: null,
email: null,
}, },
}); locationTypes: [],
} modal: {
showModal: false,
makeFetch("POST", "/api/1.0/main/location.json", body) modalDialogClass: "modal-dialog-scrollable modal-xl",
.then((response) => { },
this.$store.dispatch("addAvailableLocationGroup", { addAddress: {
locationGroup: "Localisations nouvellement créées", options: {
locations: [response], button: {
}); text: {
this.$store.dispatch("updateLocation", response); create: "activity.create_address",
this.modal.showModal = false; edit: "activity.edit_address",
}) },
.catch((error) => { size: "btn-sm",
if (error.name === "ValidationException") { },
for (let v of error.violations) { title: {
this.errors.push(v); create: "activity.create_address",
} edit: "activity.edit_address",
} else { },
this.errors.push("An error occurred"); },
context: {
target: {
//name, id
},
edit: false,
addressId: null,
defaults: window.addaddress,
},
},
};
},
computed: {
...mapState(["activity"]),
selectType: {
get() {
return this.selected.type;
},
set(value) {
this.selected.type = value;
},
},
inputName: {
get() {
return this.selected.name;
},
set(value) {
this.selected.name = value;
},
},
inputEmail: {
get() {
return this.selected.email;
},
set(value) {
this.selected.email = value;
},
},
inputPhonenumber1: {
get() {
return this.selected.phonenumber1;
},
set(value) {
this.selected.phonenumber1 = value;
},
},
inputPhonenumber2: {
get() {
return this.selected.phonenumber2;
},
set(value) {
this.selected.phonenumber2 = value;
},
},
hasPhonenumber1() {
return (
this.selected.phonenumber1 !== null &&
this.selected.phonenumber1 !== ""
);
},
showAddAddress() {
let cond = false;
if (this.selected.type) {
if (this.selected.type.addressRequired !== "never") {
cond = true;
}
} }
}); return cond;
} },
showContactData() {
let cond = false;
if (this.selected.type) {
if (this.selected.type.contactData !== "never") {
cond = true;
}
}
return cond;
},
}, },
submitNewAddress(payload) { mounted() {
this.selected.addressId = payload.addressId; this.getLocationTypesList();
this.addAddress.context.addressId = payload.addressId; },
this.addAddress.context.edit = true; methods: {
localizeString,
checkForm() {
let cond = true;
this.errors = [];
if (!this.selected.type) {
this.errors.push("Type de localisation requis");
cond = false;
} else {
if (
this.selected.type.addressRequired === "required" &&
!this.selected.addressId
) {
this.errors.push("Adresse requise");
cond = false;
}
if (
this.selected.type.contactData === "required" &&
!this.selected.phonenumber1
) {
this.errors.push("Numéro de téléphone requis");
cond = false;
}
if (
this.selected.type.contactData === "required" &&
!this.selected.email
) {
this.errors.push("Adresse email requise");
cond = false;
}
}
return cond;
},
getLocationTypesList() {
getLocationTypes().then((results) => {
this.locationTypes = results.filter(
(t) => t.availableForUsers === true,
);
});
},
openModal() {
this.modal.showModal = true;
},
saveNewLocation() {
if (this.checkForm()) {
let body = {
type: "location",
name: this.selected.name,
locationType: {
id: this.selected.type.id,
type: "location-type",
},
phonenumber1: this.selected.phonenumber1,
phonenumber2: this.selected.phonenumber2,
email: this.selected.email,
};
if (this.selected.addressId) {
body = Object.assign(body, {
address: {
id: this.selected.addressId,
},
});
}
makeFetch("POST", "/api/1.0/main/location.json", body)
.then((response) => {
this.$store.dispatch("addAvailableLocationGroup", {
locationGroup: "Localisations nouvellement créées",
locations: [response],
});
this.$store.dispatch("updateLocation", response);
this.modal.showModal = false;
})
.catch((error) => {
if (error.name === "ValidationException") {
for (let v of error.violations) {
this.errors.push(v);
}
} else {
this.errors.push("An error occurred");
}
});
}
},
submitNewAddress(payload) {
this.selected.addressId = payload.addressId;
this.addAddress.context.addressId = payload.addressId;
this.addAddress.context.edit = true;
},
}, },
},
}; };
</script> </script>

View File

@@ -1,38 +1,38 @@
<template> <template>
<span class="inline-choice"> <span class="inline-choice">
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input" class="form-check-input"
type="checkbox" type="checkbox"
v-model="selected" v-model="selected"
name="action" name="action"
:id="action.id" :id="action.id"
:value="action" :value="action"
/> />
<label class="form-check-label" :for="action.id"> <label class="form-check-label" :for="action.id">
<span class="badge bg-light text-dark" :title="action.text">{{ <span class="badge bg-light text-dark" :title="action.text">{{
action.text action.text
}}</span> }}</span>
</label> </label>
</div> </div>
</span> </span>
</template> </template>
<script> <script>
export default { export default {
name: "CheckSocialAction", name: "CheckSocialAction",
props: ["action", "selection"], props: ["action", "selection"],
emits: ["updateSelected"], emits: ["updateSelected"],
computed: { computed: {
selected: { selected: {
set(value) { set(value) {
this.$emit("updateSelected", value); this.$emit("updateSelected", value);
}, },
get() { get() {
return this.selection; return this.selection;
}, },
},
}, },
},
}; };
</script> </script>
@@ -41,13 +41,13 @@ export default {
@import "ChillPersonAssets/chill/scss/mixins"; @import "ChillPersonAssets/chill/scss/mixins";
@import "ChillMainAssets/chill/scss/chill_variables"; @import "ChillMainAssets/chill/scss/chill_variables";
span.badge { span.badge {
@include badge_social($social-action-color); @include badge_social($social-action-color);
font-size: 95%; font-size: 95%;
margin-bottom: 5px; margin-bottom: 5px;
margin-right: 1em; margin-right: 1em;
max-width: 100%; /* Adjust as needed */ max-width: 100%; /* Adjust as needed */
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
</style> </style>

View File

@@ -1,36 +1,38 @@
<template> <template>
<span class="inline-choice"> <span class="inline-choice">
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input" class="form-check-input"
type="checkbox" type="checkbox"
v-model="selected" v-model="selected"
name="issue" name="issue"
:id="issue.id" :id="issue.id"
:value="issue" :value="issue"
/> />
<label class="form-check-label" :for="issue.id"> <label class="form-check-label" :for="issue.id">
<span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span> <span class="badge bg-chill-l-gray text-dark">{{
</label> issue.text
</div> }}</span>
</span> </label>
</div>
</span>
</template> </template>
<script> <script>
export default { export default {
name: "CheckSocialIssue", name: "CheckSocialIssue",
props: ["issue", "selection"], props: ["issue", "selection"],
emits: ["updateSelected"], emits: ["updateSelected"],
computed: { computed: {
selected: { selected: {
set(value) { set(value) {
this.$emit("updateSelected", value); this.$emit("updateSelected", value);
}, },
get() { get() {
return this.selection; return this.selection;
}, },
},
}, },
},
}; };
</script> </script>
@@ -39,9 +41,9 @@ export default {
@import "ChillPersonAssets/chill/scss/mixins"; @import "ChillPersonAssets/chill/scss/mixins";
@import "ChillMainAssets/chill/scss/chill_variables"; @import "ChillMainAssets/chill/scss/chill_variables";
span.badge { span.badge {
@include badge_social($social-issue-color); @include badge_social($social-issue-color);
font-size: 95%; font-size: 95%;
margin-bottom: 5px; margin-bottom: 5px;
margin-right: 1em; margin-right: 1em;
} }
</style> </style>

View File

@@ -10,7 +10,7 @@ Attendee: Présence de l'usager
attendee: présence de l'usager attendee: présence de l'usager
list_reasons: liste des sujets list_reasons: liste des sujets
user_username: nom de l'utilisateur user_username: nom de l'utilisateur
circle_name: nom du service circle_name: nom du cercle
Remark: Commentaire Remark: Commentaire
No comments: Aucun commentaire No comments: Aucun commentaire
Add a new activity: Ajouter une nouvel échange Add a new activity: Ajouter une nouvel échange
@@ -20,7 +20,7 @@ not present: absent
Delete: Supprimer Delete: Supprimer
Update: Mettre à jour Update: Mettre à jour
Update activity: Modifier l'échange Update activity: Modifier l'échange
Scope: Service Scope: Cercle
Activity data: Données de l'échange Activity data: Données de l'échange
Activity location: Localisation de l'échange Activity location: Localisation de l'échange
No reason associated: Aucun sujet No reason associated: Aucun sujet
@@ -398,15 +398,13 @@ export:
sent received: Envoyé ou reçu sent received: Envoyé ou reçu
emergency: Urgence emergency: Urgence
accompanying course id: Identifiant du parcours accompanying course id: Identifiant du parcours
course circles: Services du parcours course circles: Cercles du parcours
travelTime: Durée de déplacement travelTime: Durée de déplacement
durationTime: Durée durationTime: Durée
id: Identifiant id: Identifiant
List activities linked to an accompanying course: Liste les échanges liés à un parcours en fonction de différents filtres. List activities linked to an accompanying course: Liste les échanges liés à un parcours en fonction de différents filtres.
List activity linked to a course: Liste des échanges liés à un parcours List activity linked to a course: Liste des échanges liés à un parcours
commentText: Commentaire
comment_date: Date de la dernière édition du commentaire
comment_user: Dernière édition par
filter: filter:
activity: activity:

View File

@@ -25,7 +25,6 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
$config = $this->processConfiguration($configuration, $configs); $config = $this->processConfiguration($configuration, $configs);
$container->setParameter('chill_aside_activity.form.time_duration', $config['form']['time_duration']); $container->setParameter('chill_aside_activity.form.time_duration', $config['form']['time_duration']);
$container->setParameter('chill_aside_activity.show_concerned_persons_count', 'visible' === $config['show_concerned_persons_count']);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
$loader->load('services.yaml'); $loader->load('services.yaml');
@@ -39,24 +38,6 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
{ {
$this->prependRoute($container); $this->prependRoute($container);
$this->prependCruds($container); $this->prependCruds($container);
$this->prependTwigConfig($container);
}
protected function prependTwigConfig(ContainerBuilder $container)
{
// Get the configuration for this bundle
$chillAsideActivityConfig = $container->getExtensionConfig($this->getAlias());
$config = $this->processConfiguration($this->getConfiguration($chillAsideActivityConfig, $container), $chillAsideActivityConfig);
// Add configuration to twig globals
$twigConfig = [
'globals' => [
'chill_aside_activity_config' => [
'show_concerned_persons_count' => 'visible' === $config['show_concerned_persons_count'],
],
],
];
$container->prependExtensionConfig('twig', $twigConfig);
} }
protected function prependCruds(ContainerBuilder $container) protected function prependCruds(ContainerBuilder $container)

View File

@@ -141,12 +141,6 @@ class Configuration implements ConfigurationInterface
->end() ->end()
->end() ->end()
->end() ->end()
->end()
->enumNode('show_concerned_persons_count')
->values(['hidden', 'visible'])
->defaultValue('hidden')
->info('Show the concerned persons count field in aside activity forms and views')
->end()
->end(); ->end();
return $treeBuilder; return $treeBuilder;

View File

@@ -62,10 +62,6 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
#[ORM\ManyToOne(targetEntity: User::class)] #[ORM\ManyToOne(targetEntity: User::class)]
private User $updatedBy; private User $updatedBy;
#[Assert\GreaterThanOrEqual(0)]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true)]
private ?int $concernedPersonsCount = 0;
public function getAgent(): ?User public function getAgent(): ?User
{ {
return $this->agent; return $this->agent;
@@ -190,16 +186,4 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
return $this; return $this;
} }
public function getConcernedPersonsCount(): ?int
{
return $this->concernedPersonsCount;
}
public function setConcernedPersonsCount(?int $concernedPersonsCount): self
{
$this->concernedPersonsCount = $concernedPersonsCount;
return $this;
}
} }

View File

@@ -1,86 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\AsideActivityBundle\Export\Aggregator;
use Chill\AsideActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class ByConcernedPersonsCountAggregator implements AggregatorInterface
{
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void
{
$qb->addSelect('aside.concernedPersonsCount AS by_concerned_persons_count_aggregator')
->addGroupBy('by_concerned_persons_count_aggregator');
}
public function applyOn(): string
{
return Declarations::ASIDE_ACTIVITY_TYPE;
}
public function buildForm(FormBuilderInterface $builder): void
{
// No form needed
}
public function getNormalizationVersion(): int
{
return 1;
}
public function normalizeFormData(array $formData): array
{
return [];
}
public function denormalizeFormData(array $formData, int $fromVersion): array
{
return [];
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data): callable
{
return function ($value): string {
if ('_header' === $value) {
return 'export.aggregator.Concerned persons count';
}
if (null === $value) {
return 'export.aggregator.No concerned persons count specified';
}
return (string) $value;
};
}
public function getQueryKeys($data): array
{
return ['by_concerned_persons_count_aggregator'];
}
public function getTitle(): string
{
return 'export.aggregator.Group by concerned persons count';
}
}

View File

@@ -1,116 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\AsideActivityBundle\Export\Export;
use Chill\AsideActivityBundle\Export\Declarations;
use Chill\AsideActivityBundle\Repository\AsideActivityRepository;
use Chill\AsideActivityBundle\Security\AsideActivityVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Doctrine\ORM\Query;
use Symfony\Component\Form\FormBuilderInterface;
class SumConcernedPersonsCountAsideActivity implements ExportInterface, GroupedExportInterface
{
public function __construct(private readonly AsideActivityRepository $repository) {}
public function buildForm(FormBuilderInterface $builder) {}
public function getNormalizationVersion(): int
{
return 1;
}
public function normalizeFormData(array $formData): array
{
return [];
}
public function denormalizeFormData(array $formData, int $fromVersion): array
{
return [];
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'export.Sum concerned persons count for aside activities';
}
public function getGroup(): string
{
return 'export.Exports of aside activities';
}
public function getLabels($key, array $values, $data)
{
if ('export_sum_concerned_persons_count' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static fn ($value) => $labels[$value];
}
public function getQueryKeys($data): array
{
return ['export_sum_concerned_persons_count'];
}
public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array
{
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'export.Sum concerned persons count for aside activities';
}
public function getType(): string
{
return Declarations::ASIDE_ACTIVITY_TYPE;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder
{
$qb = $this->repository->createQueryBuilder('aside');
$qb->select('SUM(COALESCE(aside.concernedPersonsCount, 0)) as export_sum_concerned_persons_count');
return $qb;
}
public function requiredRole(): string
{
return AsideActivityVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::ASIDE_ACTIVITY_TYPE,
];
}
}

View File

@@ -21,7 +21,6 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvents;
@@ -30,13 +29,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
final class AsideActivityFormType extends AbstractType final class AsideActivityFormType extends AbstractType
{ {
private readonly array $timeChoices; private readonly array $timeChoices;
private readonly bool $showConcernedPersonsCount;
public function __construct( public function __construct(
ParameterBagInterface $parameterBag, ParameterBagInterface $parameterBag,
) { ) {
$this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration'); $this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration');
$this->showConcernedPersonsCount = $parameterBag->get('chill_aside_activity.show_concerned_persons_count');
} }
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
@@ -79,16 +76,6 @@ final class AsideActivityFormType extends AbstractType
->add('location', PickUserLocationType::class) ->add('location', PickUserLocationType::class)
; ;
if ($this->showConcernedPersonsCount) {
$builder->add('concernedPersonsCount', IntegerType::class, [
'label' => 'Concerned persons count',
'required' => false,
'attr' => [
'min' => 0,
],
]);
}
foreach (['duration'] as $fieldName) { foreach (['duration'] as $fieldName) {
$builder->get($fieldName) $builder->get($fieldName)
->addModelTransformer($durationTimeTransformer); ->addModelTransformer($durationTimeTransformer);

View File

@@ -42,11 +42,6 @@
{%- if entity.location.name is defined -%} {%- if entity.location.name is defined -%}
<div><i class="fa fa-fw fa-map-marker"></i>{{ entity.location.name }}</div> <div><i class="fa fa-fw fa-map-marker"></i>{{ entity.location.name }}</div>
{%- endif -%} {%- endif -%}
{%- if entity.concernedPersonsCount > 0 -%}
<div><i class="fa fa-fw fa-user"></i>{{ entity.concernedPersonsCount }}</div>
{%- endif -%}
</div> </div>
<div class="item-col" style="justify-content: flex-end;"> <div class="item-col" style="justify-content: flex-end;">
<div class="box"> <div class="box">

View File

@@ -38,11 +38,6 @@
<dt class="inline">{{ 'Duration'|trans }}</dt> <dt class="inline">{{ 'Duration'|trans }}</dt>
<dd>{{ entity.duration|date('H:i') }}</dd> <dd>{{ entity.duration|date('H:i') }}</dd>
{% if chill_aside_activity_config.show_concerned_persons_count == 'visible' %}
<dt class="inline">{{ 'Concerned persons count'|trans }}</dt>
<dd>{{ entity.concernedPersonsCount }}</dd>
{% endif %}
<dt class="inline">{{ 'Remark'|trans }}</dt> <dt class="inline">{{ 'Remark'|trans }}</dt>
{%- if entity.note is empty -%} {%- if entity.note is empty -%}
<dd> <dd>

View File

@@ -1,49 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\AsideActivityBundle\Tests\Export\Aggregator;
use Chill\AsideActivityBundle\Entity\AsideActivity;
use Chill\AsideActivityBundle\Export\Aggregator\ByConcernedPersonsCountAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class ByConcernedPersonsCountAggregatorTest extends AbstractAggregatorTest
{
public function getAggregator()
{
return new ByConcernedPersonsCountAggregator();
}
public static function getFormData(): array
{
return [
[],
];
}
public static function getQueryBuilders(): iterable
{
self::bootKernel();
$em = self::getContainer()->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('count(aside.id)')
->from(AsideActivity::class, 'aside'),
];
}
}

View File

@@ -1,50 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\AsideActivityBundle\Tests\Export\Export;
use Chill\AsideActivityBundle\Export\Export\SumConcernedPersonsCountAsideActivity;
use Chill\AsideActivityBundle\Repository\AsideActivityRepository;
use Chill\MainBundle\Test\Export\AbstractExportTest;
/**
* @internal
*
* @coversNothing
*/
final class SumConcernedPersonsCountAsideActivityTest extends AbstractExportTest
{
protected function setUp(): void
{
self::bootKernel();
}
public function getExport()
{
$repository = self::getContainer()->get(AsideActivityRepository::class);
yield new SumConcernedPersonsCountAsideActivity($repository);
}
public static function getFormData(): array
{
return [
[],
];
}
public static function getModifiersCombination(): array
{
return [
['aside_activity'],
];
}
}

View File

@@ -20,10 +20,6 @@ services:
tags: tags:
- { name: chill.export, alias: 'avg_aside_activity_duration' } - { name: chill.export, alias: 'avg_aside_activity_duration' }
Chill\AsideActivityBundle\Export\Export\SumConcernedPersonsCountAsideActivity:
tags:
- { name: chill.export, alias: 'sum_aside_activity_concerned_persons_count' }
## Filters ## Filters
chill.aside_activity.export.date_filter: chill.aside_activity.export.date_filter:
class: Chill\AsideActivityBundle\Export\Filter\ByDateFilter class: Chill\AsideActivityBundle\Export\Filter\ByDateFilter
@@ -74,7 +70,3 @@ services:
Chill\AsideActivityBundle\Export\Aggregator\ByLocationAggregator: Chill\AsideActivityBundle\Export\Aggregator\ByLocationAggregator:
tags: tags:
- { name: chill.export_aggregator, alias: 'aside_activity_location_aggregator' } - { name: chill.export_aggregator, alias: 'aside_activity_location_aggregator' }
Chill\AsideActivityBundle\Export\Aggregator\ByConcernedPersonsCountAggregator:
tags:
- { name: chill.export_aggregator, alias: 'aside_activity_concerned_persons_count_aggregator' }

View File

@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\AsideActivity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20251006113048 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add concernedPersonsCount property to AsideActivity entity';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD concernedPersonsCount INT DEFAULT 0');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP concernedPersonsCount');
}
}

View File

@@ -27,7 +27,6 @@ Emergency: Urgent
by: "Par " by: "Par "
location: Lieu location: Lieu
Asideactivity location: Localisation de l'activité Asideactivity location: Localisation de l'activité
Concerned persons count: Nombre d'usager concernés
# Crud # Crud
crud: crud:
@@ -178,7 +177,7 @@ export:
agent_id: Utilisateur agent_id: Utilisateur
creator_id: Créateur creator_id: Créateur
main_scope: Service principal de l'utilisateur main_scope: Service principal de l'utilisateur
main_center: Territoire principal de l'utilisateur main_center: Centre principal de l'utilisateur
aside_activity_type: Catégorie d'activité annexe aside_activity_type: Catégorie d'activité annexe
date: Date date: Date
duration: Durée duration: Durée
@@ -191,7 +190,6 @@ export:
Count aside activities by various parameters.: Compte le nombre d'activités annexes selon divers critères Count aside activities by various parameters.: Compte le nombre d'activités annexes selon divers critères
Average aside activities duration: Durée moyenne des activités annexes Average aside activities duration: Durée moyenne des activités annexes
Sum aside activities duration: Durée des activités annexes Sum aside activities duration: Durée des activités annexes
Sum concerned persons count for aside activities: Nombre d'usager concernés par les activités annexes
filter: filter:
Filter by aside activity date: Filtrer les activités annexes par date Filter by aside activity date: Filtrer les activités annexes par date
Filter by aside activity type: Filtrer les activités annexes par type d'activité Filter by aside activity type: Filtrer les activités annexes par type d'activité
@@ -212,8 +210,6 @@ export:
'Filtered by aside activity location: only %location%': "Filtré par localisation: uniquement %location%" 'Filtered by aside activity location: only %location%': "Filtré par localisation: uniquement %location%"
aggregator: aggregator:
Group by aside activity type: Grouper les activités annexes par type d'activité Group by aside activity type: Grouper les activités annexes par type d'activité
Group by concerned persons count: Grouper les activités annexes par nombre d'usagers conernés
Concerned persons count: Nombre d'usagers concernés
Aside activity type: Type d'activité annexe Aside activity type: Type d'activité annexe
by_user_job: by_user_job:
Aggregate by user job: Grouper les activités annexes par métier des utilisateurs Aggregate by user job: Grouper les activités annexes par métier des utilisateurs

View File

@@ -13,7 +13,6 @@ namespace Chill\CalendarBundle\Controller;
use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Form\CalendarType; use Chill\CalendarBundle\Form\CalendarType;
use Chill\CalendarBundle\Form\CancelType;
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface; use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface; use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface;
use Chill\CalendarBundle\Security\Voter\CalendarVoter; use Chill\CalendarBundle\Security\Voter\CalendarVoter;
@@ -31,7 +30,6 @@ use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\ORM\EntityManagerInterface;
use http\Exception\UnexpectedValueException; use http\Exception\UnexpectedValueException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -62,7 +60,6 @@ class CalendarController extends AbstractController
private readonly UserRepositoryInterface $userRepository, private readonly UserRepositoryInterface $userRepository,
private readonly TranslatorInterface $translator, private readonly TranslatorInterface $translator,
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry, private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
private readonly EntityManagerInterface $em,
) {} ) {}
/** /**
@@ -114,55 +111,6 @@ class CalendarController extends AbstractController
]); ]);
} }
#[Route(path: '/{_locale}/calendar/calendar/{id}/cancel', name: 'chill_calendar_calendar_cancel')]
public function cancelAction(Calendar $calendar, Request $request): Response
{
// Deal with sms being sent or not
// Communicate cancellation with the remote calendar.
$this->denyAccessUnlessGranted(CalendarVoter::EDIT, $calendar);
[$person, $accompanyingPeriod] = [$calendar->getPerson(), $calendar->getAccompanyingPeriod()];
$form = $this->createForm(CancelType::class, $calendar);
$form->add('submit', SubmitType::class);
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = '@ChillCalendar/Calendar/cancelCalendarByAccompanyingCourse.html.twig';
$redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_period', ['id' => $accompanyingPeriod->getId()]);
} elseif ($person instanceof Person) {
$view = '@ChillCalendar/Calendar/cancelCalendarByPerson.html.twig';
$redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_person', ['id' => $person->getId()]);
} else {
throw new \RuntimeException('nor person or accompanying period');
}
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->logger->notice('A calendar event has been cancelled', [
'by_user' => $this->getUser()->getUsername(),
'calendar_id' => $calendar->getId(),
]);
$calendar->setStatus($calendar::STATUS_CANCELED);
$calendar->setSmsStatus($calendar::SMS_CANCEL_PENDING);
$this->em->flush();
$this->addFlash('success', $this->translator->trans('chill_calendar.calendar_canceled'));
return new RedirectResponse($redirectRoute);
}
return $this->render($view, [
'calendar' => $calendar,
'form' => $form->createView(),
'accompanyingCourse' => $accompanyingPeriod,
'person' => $person,
]);
}
/** /**
* Edit a calendar item. * Edit a calendar item.
*/ */
@@ -318,7 +266,7 @@ class CalendarController extends AbstractController
} }
if (!$this->getUser() instanceof User) { if (!$this->getUser() instanceof User) {
throw new UnauthorizedHttpException('you are not a user'); throw new UnauthorizedHttpException('you are not an user');
} }
$view = '@ChillCalendar/Calendar/listByUser.html.twig'; $view = '@ChillCalendar/Calendar/listByUser.html.twig';

View File

@@ -1,58 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\CalendarBundle\Controller;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Repository\InviteRepository;
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepositoryInterface;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\Routing\Annotation\Route;
class MyInvitationsController extends AbstractController
{
public function __construct(private readonly InviteRepository $inviteRepository, private readonly PaginatorFactory $paginator, private readonly DocGeneratorTemplateRepositoryInterface $docGeneratorTemplateRepository) {}
#[Route(path: '/{_locale}/calendar/invitations/my', name: 'chill_calendar_invitations_list_my')]
public function myInvitations(Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_USER');
$user = $this->getUser();
if (!$user instanceof User) {
throw new UnauthorizedHttpException('you are not a user');
}
$total = count($this->inviteRepository->findBy(['user' => $user]));
$paginator = $this->paginator->create($total);
$invitations = $this->inviteRepository->findBy(
['user' => $user],
['createdAt' => 'DESC'],
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
);
$view = '@ChillCalendar/Invitations/listByUser.html.twig';
return $this->render($view, [
'invitations' => $invitations,
'paginator' => $paginator,
'templates' => $this->docGeneratorTemplateRepository->findByEntity(Calendar::class),
]);
}
}

View File

@@ -35,7 +35,7 @@ class LoadCancelReason extends Fixture implements FixtureGroupInterface
$arr = [ $arr = [
['name' => CancelReason::CANCELEDBY_USER], ['name' => CancelReason::CANCELEDBY_USER],
['name' => CancelReason::CANCELEDBY_PERSON], ['name' => CancelReason::CANCELEDBY_PERSON],
['name' => CancelReason::CANCELEDBY_OTHER], ['name' => CancelReason::CANCELEDBY_DONOTCOUNT],
]; ];
foreach ($arr as $a) { foreach ($arr as $a) {

View File

@@ -269,11 +269,6 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente
return $this->cancelReason; return $this->cancelReason;
} }
public function isCanceled(): bool
{
return null !== $this->cancelReason;
}
public function getCenters(): ?iterable public function getCenters(): ?iterable
{ {
return match ($this->getContext()) { return match ($this->getContext()) {

View File

@@ -18,14 +18,14 @@ use Doctrine\ORM\Mapping as ORM;
#[ORM\Table(name: 'chill_calendar.cancel_reason')] #[ORM\Table(name: 'chill_calendar.cancel_reason')]
class CancelReason class CancelReason
{ {
final public const CANCELEDBY_OTHER = 'CANCELEDBY_OTHER'; final public const CANCELEDBY_DONOTCOUNT = 'CANCELEDBY_DONOTCOUNT';
final public const CANCELEDBY_PERSON = 'CANCELEDBY_PERSON'; final public const CANCELEDBY_PERSON = 'CANCELEDBY_PERSON';
final public const CANCELEDBY_USER = 'CANCELEDBY_USER'; final public const CANCELEDBY_USER = 'CANCELEDBY_USER';
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => true])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN)]
private bool $active = true; private ?bool $active = null;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
private ?string $canceledBy = null; private ?string $canceledBy = null;

View File

@@ -15,7 +15,7 @@ use Chill\CalendarBundle\Entity\CancelReason;
use Chill\MainBundle\Form\Type\TranslatableStringFormType; use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -28,14 +28,7 @@ class CancelReasonType extends AbstractType
->add('active', CheckboxType::class, [ ->add('active', CheckboxType::class, [
'required' => false, 'required' => false,
]) ])
->add('canceledBy', ChoiceType::class, [ ->add('canceledBy', TextType::class);
'choices' => [
'chill_calendar.canceled_by.user' => CancelReason::CANCELEDBY_USER,
'chill_calendar.canceled_by.person' => CancelReason::CANCELEDBY_PERSON,
'chill_calendar.canceled_by.other' => CancelReason::CANCELEDBY_OTHER,
],
'required' => true,
]);
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)

View File

@@ -1,42 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\CalendarBundle\Form;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\CancelReason;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CancelType extends AbstractType
{
public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper) {}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('cancelReason', EntityType::class, [
'class' => CancelReason::class,
'required' => true,
'choice_label' => fn (CancelReason $cancelReason) => $this->translatableStringHelper->localize($cancelReason->getName()),
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Calendar::class,
]);
}
}

View File

@@ -25,13 +25,6 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
if ($this->security->isGranted('ROLE_USER')) { if ($this->security->isGranted('ROLE_USER')) {
$menu->addChild('My calendar list', [ $menu->addChild('My calendar list', [
'route' => 'chill_calendar_calendar_list_my', 'route' => 'chill_calendar_calendar_list_my',
])
->setExtras([
'order' => 8,
'icon' => 'tasks',
]);
$menu->addChild('invite.list.title', [
'route' => 'chill_calendar_invitations_list_my',
]) ])
->setExtras([ ->setExtras([
'order' => 9, 'order' => 9,

View File

@@ -21,7 +21,6 @@ namespace Chill\CalendarBundle\Messenger\Doctrine;
use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Messenger\Message\CalendarMessage; use Chill\CalendarBundle\Messenger\Message\CalendarMessage;
use Chill\CalendarBundle\Messenger\Message\CalendarRemovedMessage; use Chill\CalendarBundle\Messenger\Message\CalendarRemovedMessage;
use Chill\MainBundle\Entity\User;
use Doctrine\ORM\Event\PostPersistEventArgs; use Doctrine\ORM\Event\PostPersistEventArgs;
use Doctrine\ORM\Event\PostRemoveEventArgs; use Doctrine\ORM\Event\PostRemoveEventArgs;
use Doctrine\ORM\Event\PostUpdateEventArgs; use Doctrine\ORM\Event\PostUpdateEventArgs;
@@ -32,17 +31,6 @@ class CalendarEntityListener
{ {
public function __construct(private readonly MessageBusInterface $messageBus, private readonly Security $security) {} public function __construct(private readonly MessageBusInterface $messageBus, private readonly Security $security) {}
private function getAuthenticatedUser(): User
{
$user = $this->security->getUser();
if (!$user instanceof User) {
throw new \LogicException('Expected an instance of User.');
}
return $user;
}
public function postPersist(Calendar $calendar, PostPersistEventArgs $args): void public function postPersist(Calendar $calendar, PostPersistEventArgs $args): void
{ {
if (!$calendar->preventEnqueueChanges) { if (!$calendar->preventEnqueueChanges) {
@@ -50,7 +38,7 @@ class CalendarEntityListener
new CalendarMessage( new CalendarMessage(
$calendar, $calendar,
CalendarMessage::CALENDAR_PERSIST, CalendarMessage::CALENDAR_PERSIST,
$this->getAuthenticatedUser() $this->security->getUser()
) )
); );
} }
@@ -62,7 +50,7 @@ class CalendarEntityListener
$this->messageBus->dispatch( $this->messageBus->dispatch(
new CalendarRemovedMessage( new CalendarRemovedMessage(
$calendar, $calendar,
$this->getAuthenticatedUser() $this->security->getUser()
) )
); );
} }
@@ -70,19 +58,12 @@ class CalendarEntityListener
public function postUpdate(Calendar $calendar, PostUpdateEventArgs $args): void public function postUpdate(Calendar $calendar, PostUpdateEventArgs $args): void
{ {
if ($calendar->getStatus() === $calendar::STATUS_CANCELED) { if (!$calendar->preventEnqueueChanges) {
$this->messageBus->dispatch(
new CalendarRemovedMessage(
$calendar,
$this->getAuthenticatedUser()
)
);
} elseif (!$calendar->preventEnqueueChanges) {
$this->messageBus->dispatch( $this->messageBus->dispatch(
new CalendarMessage( new CalendarMessage(
$calendar, $calendar,
CalendarMessage::CALENDAR_UPDATE, CalendarMessage::CALENDAR_UPDATE,
$this->getAuthenticatedUser() $this->security->getUser()
) )
); );
} }

View File

@@ -70,8 +70,6 @@ class CalendarRemovedMessage
public function getRemoteId(): string public function getRemoteId(): string
{ {
dump($this->remoteId);
return $this->remoteId; return $this->remoteId;
} }
} }

View File

@@ -191,7 +191,6 @@ class CalendarRepository implements ObjectRepository
$qb->expr()->eq('c.mainUser', ':user'), $qb->expr()->eq('c.mainUser', ':user'),
$qb->expr()->gte('c.startDate', ':startDate'), $qb->expr()->gte('c.startDate', ':startDate'),
$qb->expr()->lte('c.endDate', ':endDate'), $qb->expr()->lte('c.endDate', ':endDate'),
$qb->expr()->isNull('c.cancelReason'),
) )
) )
->setParameters([ ->setParameters([

View File

@@ -41,7 +41,7 @@ class InviteRepository implements ObjectRepository
/** /**
* @return array|Invite[] * @return array|Invite[]
*/ */
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null)
{ {
return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset); return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset);
} }

View File

@@ -1,6 +1,5 @@
services: services:
Chill\CalendarBundle\Controller\: Chill\CalendarBundle\Controller\:
autowire: true autowire: true
autoconfigure: true
resource: '../../../Controller' resource: '../../../Controller'
tags: ['controller.service_arguments'] tags: ['controller.service_arguments']

View File

@@ -1,74 +1,76 @@
import { EventInput } from "@fullcalendar/core"; import { EventInput } from "@fullcalendar/core";
import { import {
DateTime, DateTime,
Location, Location,
User, User,
UserAssociatedInterface, UserAssociatedInterface,
} from "../../../ChillMainBundle/Resources/public/types"; } from "../../../ChillMainBundle/Resources/public/types";
import { Person } from "../../../ChillPersonBundle/Resources/public/types"; import { Person } from "../../../ChillPersonBundle/Resources/public/types";
export interface CalendarRange { export interface CalendarRange {
id: number; id: number;
endDate: DateTime; endDate: DateTime;
startDate: DateTime; startDate: DateTime;
user: User; user: User;
location: Location; location: Location;
createdAt: DateTime; createdAt: DateTime;
createdBy: User; createdBy: User;
updatedAt: DateTime; updatedAt: DateTime;
updatedBy: User; updatedBy: User;
} }
export interface CalendarRangeCreate { export interface CalendarRangeCreate {
user: UserAssociatedInterface; user: UserAssociatedInterface;
startDate: DateTime; startDate: DateTime;
endDate: DateTime; endDate: DateTime;
location: Location; location: Location;
} }
export interface CalendarRangeEdit { export interface CalendarRangeEdit {
startDate?: DateTime; startDate?: DateTime;
endDate?: DateTime; endDate?: DateTime;
location?: Location; location?: Location;
} }
export interface Calendar { export interface Calendar {
id: number; id: number;
} }
export interface CalendarLight { export interface CalendarLight {
id: number; id: number;
endDate: DateTime; endDate: DateTime;
startDate: DateTime; startDate: DateTime;
mainUser: User; mainUser: User;
persons: Person[]; persons: Person[];
status: "valid" | "moved" | "canceled"; status: "valid" | "moved" | "canceled";
} }
export interface CalendarRemote { export interface CalendarRemote {
id: number; id: number;
endDate: DateTime; endDate: DateTime;
startDate: DateTime; startDate: DateTime;
title: string; title: string;
isAllDay: boolean; isAllDay: boolean;
} }
export type EventInputCalendarRange = EventInput & { export type EventInputCalendarRange = EventInput & {
id: string; id: string;
userId: number; userId: number;
userLabel: string; userLabel: string;
calendarRangeId: number; calendarRangeId: number;
locationId: number; locationId: number;
locationName: string; locationName: string;
start: string; start: string;
end: string; end: string;
is: "range"; is: "range";
}; };
export function isEventInputCalendarRange( export function isEventInputCalendarRange(
toBeDetermined: EventInputCalendarRange | EventInput, toBeDetermined: EventInputCalendarRange | EventInput,
): toBeDetermined is EventInputCalendarRange { ): toBeDetermined is EventInputCalendarRange {
return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"; return (
typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
);
} }
export {}; export {};

View File

@@ -1,105 +1,119 @@
<template> <template>
<div :style="style" class="calendar-active"> <div :style="style" class="calendar-active">
<span class="badge-user"> <span class="badge-user">
{{ user.text }} {{ user.text }}
<template v-if="invite !== null"> <template v-if="invite !== null">
<i v-if="invite.status === 'accepted'" class="fa fa-check" /> <i v-if="invite.status === 'accepted'" class="fa fa-check" />
<i v-else-if="invite.status === 'declined'" class="fa fa-times" /> <i
<i v-else-if="invite.status === 'pending'" class="fa fa-question-o" /> v-else-if="invite.status === 'declined'"
<i v-else-if="invite.status === 'tentative'" class="fa fa-question" /> class="fa fa-times"
<span v-else="">{{ invite.status }}</span> />
</template> <i
</span> v-else-if="invite.status === 'pending'"
<span class="form-check-inline form-switch"> class="fa fa-question-o"
<input />
class="form-check-input" <i
type="checkbox" v-else-if="invite.status === 'tentative'"
id="flexSwitchCheckDefault" class="fa fa-question"
v-model="rangeShow" />
/> <span v-else="">{{ invite.status }}</span>
&nbsp;<label </template>
class="form-check-label" </span>
for="flexSwitchCheckDefault" <span class="form-check-inline form-switch">
title="Disponibilités" <input
><i class="fa fa-calendar-check-o" class="form-check-input"
/></label> type="checkbox"
</span> id="flexSwitchCheckDefault"
<span class="form-check-inline form-switch"> v-model="rangeShow"
<input />
class="form-check-input" &nbsp;<label
type="checkbox" class="form-check-label"
id="flexSwitchCheckDefault" for="flexSwitchCheckDefault"
v-model="remoteShow" title="Disponibilités"
/> ><i class="fa fa-calendar-check-o"
&nbsp;<label /></label>
class="form-check-label" </span>
for="flexSwitchCheckDefault" <span class="form-check-inline form-switch">
title="Agenda" <input
><i class="fa fa-calendar" class="form-check-input"
/></label> type="checkbox"
</span> id="flexSwitchCheckDefault"
</div> v-model="remoteShow"
/>
&nbsp;<label
class="form-check-label"
for="flexSwitchCheckDefault"
title="Agenda"
><i class="fa fa-calendar"
/></label>
</span>
</div>
</template> </template>
<script> <script>
import { mapGetters } from "vuex"; import { mapGetters } from "vuex";
export default { export default {
name: "CalendarActive", name: "CalendarActive",
props: { props: {
user: { user: {
type: Object, type: Object,
required: true, required: true,
},
invite: {
type: Object,
required: false,
default: null,
},
}, },
invite: { computed: {
type: Object, style() {
required: false, return {
default: null, backgroundColor: this.$store.getters.getUserData(this.user)
.mainColor,
};
},
rangeShow: {
set(value) {
this.$store.commit("showUserOnCalendar", {
user: this.user,
ranges: value,
});
},
get() {
return this.$store.getters.isRangeShownOnCalendarForUser(
this.user,
);
},
},
remoteShow: {
set(value) {
this.$store.commit("showUserOnCalendar", {
user: this.user,
remotes: value,
});
},
get() {
return this.$store.getters.isRemoteShownOnCalendarForUser(
this.user,
);
},
},
}, },
},
computed: {
style() {
return {
backgroundColor: this.$store.getters.getUserData(this.user).mainColor,
};
},
rangeShow: {
set(value) {
this.$store.commit("showUserOnCalendar", {
user: this.user,
ranges: value,
});
},
get() {
return this.$store.getters.isRangeShownOnCalendarForUser(this.user);
},
},
remoteShow: {
set(value) {
this.$store.commit("showUserOnCalendar", {
user: this.user,
remotes: value,
});
},
get() {
return this.$store.getters.isRemoteShownOnCalendarForUser(this.user);
},
},
},
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.calendar-active { .calendar-active {
margin: 0 0.25rem 0.25rem 0; margin: 0 0.25rem 0.25rem 0;
padding: 0.5rem; padding: 0.5rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--bs-blue); color: var(--bs-blue);
& > .badge-user { & > .badge-user {
margin-right: 0.5rem; margin-right: 0.5rem;
} }
} }
</style> </style>

View File

@@ -14,37 +14,37 @@ export { whoami } from "../../../../../ChillMainBundle/Resources/public/lib/api/
* @return Promise * @return Promise
*/ */
export const fetchCalendarRangeForUser = ( export const fetchCalendarRangeForUser = (
user: User, user: User,
start: Date, start: Date,
end: Date, end: Date,
): Promise<CalendarRange[]> => { ): Promise<CalendarRange[]> => {
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`; const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
const dateFrom = datetimeToISO(start); const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end); const dateTo = datetimeToISO(end);
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo }); return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
}; };
export const fetchCalendarRemoteForUser = ( export const fetchCalendarRemoteForUser = (
user: User, user: User,
start: Date, start: Date,
end: Date, end: Date,
): Promise<CalendarRemote[]> => { ): Promise<CalendarRemote[]> => {
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`; const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
const dateFrom = datetimeToISO(start); const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end); const dateTo = datetimeToISO(end);
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo }); return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
}; };
export const fetchCalendarLocalForUser = ( export const fetchCalendarLocalForUser = (
user: User, user: User,
start: Date, start: Date,
end: Date, end: Date,
): Promise<CalendarLight[]> => { ): Promise<CalendarLight[]> => {
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`; const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
const dateFrom = datetimeToISO(start); const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end); const dateTo = datetimeToISO(end);
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo }); return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
}; };

View File

@@ -1,17 +1,17 @@
const COLORS = [ const COLORS = [
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */ /* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
"#8dd3c7", "#8dd3c7",
"#ffffb3", "#ffffb3",
"#bebada", "#bebada",
"#fb8072", "#fb8072",
"#80b1d3", "#80b1d3",
"#fdb462", "#fdb462",
"#b3de69", "#b3de69",
"#fccde5", "#fccde5",
"#d9d9d9", "#d9d9d9",
"#bc80bd", "#bc80bd",
"#ccebc5", "#ccebc5",
"#ffed6f", "#ffed6f",
]; ];
export { COLORS }; export { COLORS };

View File

@@ -1,117 +1,117 @@
import { COLORS } from "../const"; import { COLORS } from "../const";
import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date"; import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date";
import { import {
DateTime, DateTime,
User, User,
} from "../../../../../../ChillMainBundle/Resources/public/types"; } from "../../../../../../ChillMainBundle/Resources/public/types";
import { CalendarLight, CalendarRange, CalendarRemote } from "../../../types"; import { CalendarLight, CalendarRange, CalendarRemote } from "../../../types";
import type { EventInputCalendarRange } from "../../../types"; import type { EventInputCalendarRange } from "../../../types";
import { EventInput } from "@fullcalendar/core"; import { EventInput } from "@fullcalendar/core";
export interface UserData { export interface UserData {
user: User; user: User;
calendarRanges: CalendarRange[]; calendarRanges: CalendarRange[];
calendarRangesLoaded: {}[]; calendarRangesLoaded: {}[];
remotes: CalendarRemote[]; remotes: CalendarRemote[];
remotesLoaded: {}[]; remotesLoaded: {}[];
locals: CalendarRemote[]; locals: CalendarRemote[];
localsLoaded: {}[]; localsLoaded: {}[];
mainColor: string; mainColor: string;
} }
export const addIdToValue = (string: string, id: number): string => { export const addIdToValue = (string: string, id: number): string => {
const array = string ? string.split(",") : []; const array = string ? string.split(",") : [];
array.push(id.toString()); array.push(id.toString());
const str = array.join(); const str = array.join();
return str; return str;
}; };
export const removeIdFromValue = (string: string, id: number) => { export const removeIdFromValue = (string: string, id: number) => {
let array = string.split(","); let array = string.split(",");
array = array.filter((el) => el !== id.toString()); array = array.filter((el) => el !== id.toString());
const str = array.join(); const str = array.join();
return str; return str;
}; };
/* /*
* Assign missing keys for the ConcernedGroups component * Assign missing keys for the ConcernedGroups component
*/ */
export const mapEntity = (entity: EventInput): EventInput => { export const mapEntity = (entity: EventInput): EventInput => {
const calendar = { ...entity }; const calendar = { ...entity };
Object.assign(calendar, { thirdParties: entity.professionals }); Object.assign(calendar, { thirdParties: entity.professionals });
if (entity.startDate !== null) { if (entity.startDate !== null) {
calendar.startDate = ISOToDatetime(entity.startDate.datetime); calendar.startDate = ISOToDatetime(entity.startDate.datetime);
} }
if (entity.endDate !== null) { if (entity.endDate !== null) {
calendar.endDate = ISOToDatetime(entity.endDate.datetime); calendar.endDate = ISOToDatetime(entity.endDate.datetime);
} }
if (entity.calendarRange !== null) { if (entity.calendarRange !== null) {
calendar.calendarRange.calendarRangeId = entity.calendarRange.id; calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
calendar.calendarRange.id = `range_${entity.calendarRange.id}`; calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
} }
return calendar; return calendar;
}; };
export const createUserData = (user: User, colorIndex: number): UserData => { export const createUserData = (user: User, colorIndex: number): UserData => {
const colorId = colorIndex % COLORS.length; const colorId = colorIndex % COLORS.length;
return { return {
user: user, user: user,
calendarRanges: [], calendarRanges: [],
calendarRangesLoaded: [], calendarRangesLoaded: [],
remotes: [], remotes: [],
remotesLoaded: [], remotesLoaded: [],
locals: [], locals: [],
localsLoaded: [], localsLoaded: [],
mainColor: COLORS[colorId], mainColor: COLORS[colorId],
}; };
}; };
// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app // TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app
export const calendarRangeToFullCalendarEvent = ( export const calendarRangeToFullCalendarEvent = (
entity: CalendarRange, entity: CalendarRange,
): EventInputCalendarRange => { ): EventInputCalendarRange => {
return { return {
id: `range_${entity.id}`, id: `range_${entity.id}`,
title: "(" + entity.user.text + ")", title: "(" + entity.user.text + ")",
start: entity.startDate.datetime8601, start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601, end: entity.endDate.datetime8601,
allDay: false, allDay: false,
userId: entity.user.id, userId: entity.user.id,
userLabel: entity.user.label, userLabel: entity.user.label,
calendarRangeId: entity.id, calendarRangeId: entity.id,
locationId: entity.location.id, locationId: entity.location.id,
locationName: entity.location.name, locationName: entity.location.name,
is: "range", is: "range",
}; };
}; };
export const remoteToFullCalendarEvent = ( export const remoteToFullCalendarEvent = (
entity: CalendarRemote, entity: CalendarRemote,
): EventInput & { id: string } => { ): EventInput & { id: string } => {
return { return {
id: `range_${entity.id}`, id: `range_${entity.id}`,
title: entity.title, title: entity.title,
start: entity.startDate.datetime8601, start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601, end: entity.endDate.datetime8601,
allDay: entity.isAllDay, allDay: entity.isAllDay,
is: "remote", is: "remote",
}; };
}; };
export const localsToFullCalendarEvent = ( export const localsToFullCalendarEvent = (
entity: CalendarLight, entity: CalendarLight,
): EventInput & { id: string; originId: number } => { ): EventInput & { id: string; originId: number } => {
return { return {
id: `local_${entity.id}`, id: `local_${entity.id}`,
title: entity.persons.map((p) => p.text).join(", "), title: entity.persons.map((p) => p.text).join(", "),
originId: entity.id, originId: entity.id,
start: entity.startDate.datetime8601, start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601, end: entity.endDate.datetime8601,
allDay: false, allDay: false,
is: "local", is: "local",
}; };
}; };

View File

@@ -1,50 +1,58 @@
<template> <template>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button <button
id="btnGroupDrop1" id="btnGroupDrop1"
type="button" type="button"
class="btn btn-misc dropdown-toggle" class="btn btn-misc dropdown-toggle"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false" aria-expanded="false"
>
<template v-if="status === Statuses.PENDING">
<span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
</template>
<template v-else-if="status === Statuses.ACCEPTED">
<span class="fa fa-check"></span> {{ $t("Accepted") }}
</template>
<template v-else-if="status === Statuses.DECLINED">
<span class="fa fa-times"></span> {{ $t("Declined") }}
</template>
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
<span class="fa fa-question"></span> {{ $t("Tentative") }}
</template>
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
<li v-if="status !== Statuses.ACCEPTED">
<a class="dropdown-item" @click="changeStatus(Statuses.ACCEPTED)"
><i class="fa fa-check" aria-hidden="true"></i> {{ $t("Accept") }}</a
> >
</li> <template v-if="status === Statuses.PENDING">
<li v-if="status !== Statuses.DECLINED"> <span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
<a class="dropdown-item" @click="changeStatus(Statuses.DECLINED)" </template>
><i class="fa fa-times" aria-hidden="true"></i> {{ $t("Decline") }}</a <template v-else-if="status === Statuses.ACCEPTED">
> <span class="fa fa-check"></span> {{ $t("Accepted") }}
</li> </template>
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED"> <template v-else-if="status === Statuses.DECLINED">
<a <span class="fa fa-times"></span> {{ $t("Declined") }}
class="dropdown-item" </template>
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)" <template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
><i class="fa fa-question"></i> {{ $t("Tentatively_accept") }}</a <span class="fa fa-question"></span> {{ $t("Tentative") }}
> </template>
</li> </button>
<li v-if="status !== Statuses.PENDING"> <ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)" <li v-if="status !== Statuses.ACCEPTED">
><i class="fa fa-hourglass-o"></i> {{ $t("Set_pending") }}</a <a
> class="dropdown-item"
</li> @click="changeStatus(Statuses.ACCEPTED)"
</ul> ><i class="fa fa-check" aria-hidden="true"></i>
</div> {{ $t("Accept") }}</a
>
</li>
<li v-if="status !== Statuses.DECLINED">
<a
class="dropdown-item"
@click="changeStatus(Statuses.DECLINED)"
><i class="fa fa-times" aria-hidden="true"></i>
{{ $t("Decline") }}</a
>
</li>
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
<a
class="dropdown-item"
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
><i class="fa fa-question"></i>
{{ $t("Tentatively_accept") }}</a
>
</li>
<li v-if="status !== Statuses.PENDING">
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
><i class="fa fa-hourglass-o"></i>
{{ $t("Set_pending") }}</a
>
</li>
</ul>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -56,67 +64,69 @@ const PENDING = "pending";
const TENTATIVELY_ACCEPTED = "tentative"; const TENTATIVELY_ACCEPTED = "tentative";
const i18n = { const i18n = {
messages: { messages: {
fr: { fr: {
Give_an_answer: "Répondre", Give_an_answer: "Répondre",
Accepted: "Accepté", Accepted: "Accepté",
Declined: "Refusé", Declined: "Refusé",
Tentative: "Accepté provisoirement", Tentative: "Accepté provisoirement",
Accept: "Accepter", Accept: "Accepter",
Decline: "Refuser", Decline: "Refuser",
Tentatively_accept: "Accepter provisoirement", Tentatively_accept: "Accepter provisoirement",
Set_pending: "Ne pas répondre", Set_pending: "Ne pas répondre",
},
}, },
},
}; };
export default defineComponent({ export default defineComponent({
name: "Answer", name: "Answer",
i18n, i18n,
props: { props: {
calendarId: { type: Number, required: true }, calendarId: { type: Number, required: true },
status: { status: {
type: String as PropType< type: String as PropType<
"accepted" | "declined" | "pending" | "tentative" "accepted" | "declined" | "pending" | "tentative"
>, >,
required: true, required: true,
},
}, },
}, emits: {
emits: { statusChanged(
statusChanged(payload: "accepted" | "declined" | "pending" | "tentative") { payload: "accepted" | "declined" | "pending" | "tentative",
return true; ) {
return true;
},
}, },
}, data() {
data() { return {
return { Statuses: {
Statuses: { ACCEPTED,
ACCEPTED, DECLINED,
DECLINED, PENDING,
PENDING, TENTATIVELY_ACCEPTED,
TENTATIVELY_ACCEPTED, },
}, };
}; },
}, methods: {
methods: { changeStatus: function (
changeStatus: function ( newStatus: "accepted" | "declined" | "pending" | "tentative",
newStatus: "accepted" | "declined" | "pending" | "tentative", ) {
) { console.log("changeStatus", newStatus);
console.log("changeStatus", newStatus); const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`; window
window .fetch(url, {
.fetch(url, { method: "POST",
method: "POST", })
}) .then((r: Response) => {
.then((r: Response) => { if (!r.ok) {
if (!r.ok) { console.error("could not confirm answer", newStatus);
console.error("could not confirm answer", newStatus); return;
return; }
} console.log("answer sent", newStatus);
console.log("answer sent", newStatus); this.$emit("statusChanged", newStatus);
this.$emit("statusChanged", newStatus); });
}); },
}, },
},
}); });
</script> </script>

View File

@@ -1,28 +1,28 @@
<template> <template>
<component :is="Teleport" to="body"> <component :is="Teleport" to="body">
<modal v-if="showModal" @close="closeModal"> <modal v-if="showModal" @close="closeModal">
<template v-slot:header> <template v-slot:header>
<h3>{{ "Modifier le lieu" }}</h3> <h3>{{ "Modifier le lieu" }}</h3>
</template> </template>
<template v-slot:body> <template v-slot:body>
<div></div> <div></div>
<label>Localisation</label> <label>Localisation</label>
<vue-multiselect <vue-multiselect
v-model="location" v-model="location"
:options="locations" :options="locations"
:label="'name'" :label="'name'"
:track-by="'id'" :track-by="'id'"
></vue-multiselect> ></vue-multiselect>
</template> </template>
<template v-slot:footer> <template v-slot:footer>
<button class="btn btn-save" @click="saveAndClose"> <button class="btn btn-save" @click="saveAndClose">
{{ "Enregistrer" }} {{ "Enregistrer" }}
</button> </button>
</template> </template>
</modal> </modal>
</component> </component>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -39,7 +39,7 @@ import VueMultiselect from "vue-multiselect";
import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue"; import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
const Teleport = teleport_ as new () => { const Teleport = teleport_ as new () => {
$props: VNodeProps & TeleportProps; $props: VNodeProps & TeleportProps;
}; };
const store = useStore(key); const store = useStore(key);
@@ -50,37 +50,37 @@ const showModal = ref(false);
//const tele = ref<InstanceType<typeof Teleport> | null>(null); //const tele = ref<InstanceType<typeof Teleport> | null>(null);
const locations = computed<Location[]>(() => { const locations = computed<Location[]>(() => {
return store.state.locations.locations; return store.state.locations.locations;
}); });
const startEdit = function (event: EventApi): void { const startEdit = function (event: EventApi): void {
console.log("startEditing", event); console.log("startEditing", event);
calendarRangeId.value = event.extendedProps.calendarRangeId; calendarRangeId.value = event.extendedProps.calendarRangeId;
location.value = location.value =
store.getters["locations/getLocationById"]( store.getters["locations/getLocationById"](
event.extendedProps.locationId, event.extendedProps.locationId,
) || null; ) || null;
console.log("new location value", location.value); console.log("new location value", location.value);
console.log("calendar range id", calendarRangeId.value); console.log("calendar range id", calendarRangeId.value);
showModal.value = true; showModal.value = true;
}; };
const saveAndClose = function (e: Event): void { const saveAndClose = function (e: Event): void {
console.log("saveEditAndClose", e); console.log("saveEditAndClose", e);
store store
.dispatch("calendarRanges/patchRangeLocation", { .dispatch("calendarRanges/patchRangeLocation", {
location: location.value, location: location.value,
calendarRangeId: calendarRangeId.value, calendarRangeId: calendarRangeId.value,
}) })
.then((_) => { .then((_) => {
showModal.value = false; showModal.value = false;
}); });
}; };
const closeModal = function (_: any): void { const closeModal = function (_: any): void {
showModal.value = false; showModal.value = false;
}; };
defineExpose({ startEdit }); defineExpose({ startEdit });

View File

@@ -1,27 +1,27 @@
const appMessages = { const appMessages = {
fr: { fr: {
created_availabilities: "Lieu des plages de disponibilités créées", created_availabilities: "Lieu des plages de disponibilités créées",
edit_your_calendar_range: "Planifiez vos plages de disponibilités", edit_your_calendar_range: "Planifiez vos plages de disponibilités",
show_my_calendar: "Afficher mon calendrier", show_my_calendar: "Afficher mon calendrier",
show_weekends: "Afficher les week-ends", show_weekends: "Afficher les week-ends",
copy_range: "Copier", copy_range: "Copier",
copy_range_from_to: "Copier les plages", copy_range_from_to: "Copier les plages",
from_day_to_day: "d'un jour à l'autre", from_day_to_day: "d'un jour à l'autre",
from_week_to_week: "d'une semaine à l'autre", from_week_to_week: "d'une semaine à l'autre",
copy_range_how_to: copy_range_how_to:
"Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.", "Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
new_range_to_save: "Nouvelles plages à enregistrer", new_range_to_save: "Nouvelles plages à enregistrer",
update_range_to_save: "Plages à modifier", update_range_to_save: "Plages à modifier",
delete_range_to_save: "Plages à supprimer", delete_range_to_save: "Plages à supprimer",
by: "Par", by: "Par",
main_user_concerned: "Utilisateur concerné", main_user_concerned: "Utilisateur concerné",
dateFrom: "De", dateFrom: "De",
dateTo: "à", dateTo: "à",
day: "Jour", day: "Jour",
week: "Semaine", week: "Semaine",
month: "Mois", month: "Mois",
today: "Aujourd'hui", today: "Aujourd'hui",
}, },
}; };
export { appMessages }; export { appMessages };

View File

@@ -7,13 +7,13 @@ import App2 from "./App2.vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
futureStore().then((store) => { futureStore().then((store) => {
const i18n = _createI18n(appMessages, false); const i18n = _createI18n(appMessages, false);
const app = createApp({ const app = createApp({
template: `<app></app>`, template: `<app></app>`,
}) })
.use(store, key) .use(store, key)
.use(i18n) .use(i18n)
.component("app", App2) .component("app", App2)
.mount("#myCalendar"); .mount("#myCalendar");
}); });

View File

@@ -5,7 +5,7 @@ import me, { MeState } from "./modules/me";
import fullCalendar, { FullCalendarState } from "./modules/fullcalendar"; import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges"; import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
import calendarRemotes, { import calendarRemotes, {
CalendarRemotesState, CalendarRemotesState,
} from "./modules/calendarRemotes"; } from "./modules/calendarRemotes";
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user"; import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
import { User } from "../../../../../../ChillMainBundle/Resources/public/types"; import { User } from "../../../../../../ChillMainBundle/Resources/public/types";
@@ -15,40 +15,42 @@ import calendarLocals, { CalendarLocalsState } from "./modules/calendarLocals";
const debug = process.env.NODE_ENV !== "production"; const debug = process.env.NODE_ENV !== "production";
export interface State { export interface State {
calendarRanges: CalendarRangesState; calendarRanges: CalendarRangesState;
calendarRemotes: CalendarRemotesState; calendarRemotes: CalendarRemotesState;
calendarLocals: CalendarLocalsState; calendarLocals: CalendarLocalsState;
fullCalendar: FullCalendarState; fullCalendar: FullCalendarState;
me: MeState; me: MeState;
locations: LocationState; locations: LocationState;
} }
export const key: InjectionKey<Store<State>> = Symbol(); export const key: InjectionKey<Store<State>> = Symbol();
const futureStore = function (): Promise<Store<State>> { const futureStore = function (): Promise<Store<State>> {
return whoami().then((user: User) => { return whoami().then((user: User) => {
const store = createStore<State>({ const store = createStore<State>({
strict: debug, strict: debug,
modules: { modules: {
me, me,
fullCalendar, fullCalendar,
calendarRanges, calendarRanges,
calendarRemotes, calendarRemotes,
calendarLocals, calendarLocals,
locations, locations,
}, },
mutations: {}, mutations: {},
}); });
store.commit("me/setWhoAmi", user, { root: true }); store.commit("me/setWhoAmi", user, { root: true });
store.dispatch("locations/getLocations", null, { root: true }).then((_) => { store
return store.dispatch("locations/getCurrentLocation", null, { .dispatch("locations/getLocations", null, { root: true })
root: true, .then((_) => {
}); return store.dispatch("locations/getCurrentLocation", null, {
}); root: true,
});
});
return Promise.resolve(store); return Promise.resolve(store);
}); });
}; };
export default futureStore; export default futureStore;

View File

@@ -8,99 +8,109 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
import { COLORS } from "../../../Calendar/const"; import { COLORS } from "../../../Calendar/const";
export interface CalendarLocalsState { export interface CalendarLocalsState {
locals: EventInput[]; locals: EventInput[];
localsLoaded: { start: number; end: number }[]; localsLoaded: { start: number; end: number }[];
localsIndex: Set<string>; localsIndex: Set<string>;
key: number; key: number;
} }
type Context = ActionContext<CalendarLocalsState, State>; type Context = ActionContext<CalendarLocalsState, State>;
export default { export default {
namespaced: true, namespaced: true,
state: (): CalendarLocalsState => ({ state: (): CalendarLocalsState => ({
locals: [], locals: [],
localsLoaded: [], localsLoaded: [],
localsIndex: new Set<string>(), localsIndex: new Set<string>(),
key: 0, key: 0,
}), }),
getters: { getters: {
isLocalsLoaded: isLocalsLoaded:
(state: CalendarLocalsState) => (state: CalendarLocalsState) =>
({ start, end }: { start: Date; end: Date }): boolean => { ({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.localsLoaded) { for (const range of state.localsLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) { if (
return true; start.getTime() === range.start &&
} end.getTime() === range.end
} ) {
return true;
}
}
return false; return false;
}, },
},
mutations: {
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
console.log("addLocals", ranges);
const toAdd = ranges
.map((cr) => localsToFullCalendarEvent(cr))
.filter((r) => !state.localsIndex.has(r.id));
toAdd.forEach((r) => {
state.localsIndex.add(r.id);
state.locals.push(r);
});
state.key = state.key + toAdd.length;
}, },
addLoaded(state: CalendarLocalsState, payload: { start: Date; end: Date }) { mutations: {
state.localsLoaded.push({ addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
start: payload.start.getTime(), console.log("addLocals", ranges);
end: payload.end.getTime(),
}); const toAdd = ranges
.map((cr) => localsToFullCalendarEvent(cr))
.filter((r) => !state.localsIndex.has(r.id));
toAdd.forEach((r) => {
state.localsIndex.add(r.id);
state.locals.push(r);
});
state.key = state.key + toAdd.length;
},
addLoaded(
state: CalendarLocalsState,
payload: { start: Date; end: Date },
) {
state.localsLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
}, },
}, actions: {
actions: { fetchLocals(
fetchLocals( ctx: Context,
ctx: Context, payload: { start: Date; end: Date },
payload: { start: Date; end: Date }, ): Promise<null> {
): Promise<null> { const start = payload.start;
const start = payload.start; const end = payload.end;
const end = payload.end;
if (ctx.rootGetters["me/getMe"] === null) { if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(null); return Promise.resolve(null);
} }
if (ctx.getters.isLocalsLoaded({ start, end })) { if (ctx.getters.isLocalsLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource); return Promise.resolve(ctx.getters.getRangeSource);
} }
ctx.commit("addLoaded", { ctx.commit("addLoaded", {
start: start, start: start,
end: end, end: end,
}); });
return fetchCalendarLocalForUser(ctx.rootGetters["me/getMe"], start, end) return fetchCalendarLocalForUser(
.then((remotes: CalendarLight[]) => { ctx.rootGetters["me/getMe"],
// to be add when reactivity problem will be solve ? start,
//ctx.commit('addRemotes', remotes); end,
const inputs = remotes )
.map((cr) => localsToFullCalendarEvent(cr)) .then((remotes: CalendarLight[]) => {
.map((cr) => ({ // to be add when reactivity problem will be solve ?
...cr, //ctx.commit('addRemotes', remotes);
backgroundColor: COLORS[0], const inputs = remotes
textColor: "black", .map((cr) => localsToFullCalendarEvent(cr))
editable: false, .map((cr) => ({
})); ...cr,
ctx.commit("calendarRanges/addExternals", inputs, { backgroundColor: COLORS[0],
root: true, textColor: "black",
}); editable: false,
return Promise.resolve(null); }));
}) ctx.commit("calendarRanges/addExternals", inputs, {
.catch((e: TransportExceptionInterface) => { root: true,
console.error(e); });
return Promise.resolve(null);
})
.catch((e: TransportExceptionInterface) => {
console.error(e);
return Promise.resolve(null); return Promise.resolve(null);
}); });
},
}, },
},
} as Module<CalendarLocalsState, State>; } as Module<CalendarLocalsState, State>;

View File

@@ -1,10 +1,10 @@
import { State } from "./../index"; import { State } from "./../index";
import { ActionContext, Module } from "vuex"; import { ActionContext, Module } from "vuex";
import { import {
CalendarRange, CalendarRange,
CalendarRangeCreate, CalendarRangeCreate,
CalendarRangeEdit, CalendarRangeEdit,
isEventInputCalendarRange, isEventInputCalendarRange,
} from "../../../../types"; } from "../../../../types";
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types"; import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
import { fetchCalendarRangeForUser } from "../../../Calendar/api"; import { fetchCalendarRangeForUser } from "../../../Calendar/api";
@@ -12,332 +12,369 @@ import { calendarRangeToFullCalendarEvent } from "../../../Calendar/store/utils"
import { EventInput } from "@fullcalendar/core"; import { EventInput } from "@fullcalendar/core";
import { makeFetch } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; import { makeFetch } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import { import {
datetimeToISO, datetimeToISO,
dateToISO, dateToISO,
ISOToDatetime, ISOToDatetime,
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date"; } from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
import type { EventInputCalendarRange } from "../../../../types"; import type { EventInputCalendarRange } from "../../../../types";
export interface CalendarRangesState { export interface CalendarRangesState {
ranges: (EventInput | EventInputCalendarRange)[]; ranges: (EventInput | EventInputCalendarRange)[];
rangesLoaded: { start: number; end: number }[]; rangesLoaded: { start: number; end: number }[];
rangesIndex: Set<string>; rangesIndex: Set<string>;
key: number; key: number;
} }
type Context = ActionContext<CalendarRangesState, State>; type Context = ActionContext<CalendarRangesState, State>;
export default { export default {
namespaced: true, namespaced: true,
state: (): CalendarRangesState => ({ state: (): CalendarRangesState => ({
ranges: [], ranges: [],
rangesLoaded: [], rangesLoaded: [],
rangesIndex: new Set<string>(), rangesIndex: new Set<string>(),
key: 0, key: 0,
}), }),
getters: { getters: {
isRangeLoaded: isRangeLoaded:
(state: CalendarRangesState) => (state: CalendarRangesState) =>
({ start, end }: { start: Date; end: Date }): boolean => { ({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.rangesLoaded) { for (const range of state.rangesLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) { if (
return true; start.getTime() === range.start &&
} end.getTime() === range.end
} ) {
return true;
}
}
return false; return false;
}, },
getRangesOnDate: getRangesOnDate:
(state: CalendarRangesState) => (state: CalendarRangesState) =>
(date: Date): EventInputCalendarRange[] => { (date: Date): EventInputCalendarRange[] => {
const founds = []; const founds = [];
const dateStr = dateToISO(date) as string; const dateStr = dateToISO(date) as string;
for (const range of state.ranges) { for (const range of state.ranges) {
if ( if (
isEventInputCalendarRange(range) && isEventInputCalendarRange(range) &&
range.start.startsWith(dateStr) range.start.startsWith(dateStr)
) { ) {
founds.push(range); founds.push(range);
} }
} }
return founds; return founds;
}, },
getRangesOnWeek: getRangesOnWeek:
(state: CalendarRangesState) => (state: CalendarRangesState) =>
(mondayDate: Date): EventInputCalendarRange[] => { (mondayDate: Date): EventInputCalendarRange[] => {
const founds = []; const founds = [];
for (const d of Array.from(Array(7).keys())) { for (const d of Array.from(Array(7).keys())) {
const dateOfWeek = new Date(mondayDate); const dateOfWeek = new Date(mondayDate);
dateOfWeek.setDate(mondayDate.getDate() + d); dateOfWeek.setDate(mondayDate.getDate() + d);
const dateStr = dateToISO(dateOfWeek) as string; const dateStr = dateToISO(dateOfWeek) as string;
for (const range of state.ranges) { for (const range of state.ranges) {
if ( if (
isEventInputCalendarRange(range) && isEventInputCalendarRange(range) &&
range.start.startsWith(dateStr) range.start.startsWith(dateStr)
) { ) {
founds.push(range); founds.push(range);
}
}
}
return founds;
},
},
mutations: {
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
const toAdd = ranges
.map((cr) => calendarRangeToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: "white",
borderColor: "#3788d8",
textColor: "black",
}))
.filter((r) => !state.rangesIndex.has(r.id));
toAdd.forEach((r) => {
state.rangesIndex.add(r.id);
state.ranges.push(r);
});
state.key = state.key + toAdd.length;
},
addExternals(
state: CalendarRangesState,
externalEvents: (EventInput & { id: string })[],
) {
const toAdd = externalEvents.filter(
(r) => !state.rangesIndex.has(r.id),
);
toAdd.forEach((r) => {
state.rangesIndex.add(r.id);
state.ranges.push(r);
});
state.key = state.key + toAdd.length;
},
addLoaded(
state: CalendarRangesState,
payload: { start: Date; end: Date },
) {
state.rangesLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
addRange(state: CalendarRangesState, payload: CalendarRange) {
const asEvent = calendarRangeToFullCalendarEvent(payload);
state.ranges.push({
...asEvent,
backgroundColor: "white",
borderColor: "#3788d8",
textColor: "black",
});
state.rangesIndex.add(asEvent.id);
state.key = state.key + 1;
},
removeRange(state: CalendarRangesState, calendarRangeId: number) {
const found = state.ranges.find(
(r) =>
r.calendarRangeId === calendarRangeId && r.is === "range",
);
if (found !== undefined) {
state.ranges = state.ranges.filter(
(r) =>
!(
r.calendarRangeId === calendarRangeId &&
r.is === "range"
),
);
if (typeof found.id === "string") {
// should always be true
state.rangesIndex.delete(found.id);
}
state.key = state.key + 1;
} }
}
}
return founds;
},
},
mutations: {
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
const toAdd = ranges
.map((cr) => calendarRangeToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: "white",
borderColor: "#3788d8",
textColor: "black",
}))
.filter((r) => !state.rangesIndex.has(r.id));
toAdd.forEach((r) => {
state.rangesIndex.add(r.id);
state.ranges.push(r);
});
state.key = state.key + toAdd.length;
},
addExternals(
state: CalendarRangesState,
externalEvents: (EventInput & { id: string })[],
) {
const toAdd = externalEvents.filter((r) => !state.rangesIndex.has(r.id));
toAdd.forEach((r) => {
state.rangesIndex.add(r.id);
state.ranges.push(r);
});
state.key = state.key + toAdd.length;
},
addLoaded(state: CalendarRangesState, payload: { start: Date; end: Date }) {
state.rangesLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
addRange(state: CalendarRangesState, payload: CalendarRange) {
const asEvent = calendarRangeToFullCalendarEvent(payload);
state.ranges.push({
...asEvent,
backgroundColor: "white",
borderColor: "#3788d8",
textColor: "black",
});
state.rangesIndex.add(asEvent.id);
state.key = state.key + 1;
},
removeRange(state: CalendarRangesState, calendarRangeId: number) {
const found = state.ranges.find(
(r) => r.calendarRangeId === calendarRangeId && r.is === "range",
);
if (found !== undefined) {
state.ranges = state.ranges.filter(
(r) => !(r.calendarRangeId === calendarRangeId && r.is === "range"),
);
if (typeof found.id === "string") {
// should always be true
state.rangesIndex.delete(found.id);
}
state.key = state.key + 1;
}
},
updateRange(state: CalendarRangesState, range: CalendarRange) {
const found = state.ranges.find(
(r) => r.calendarRangeId === range.id && r.is === "range",
);
const newEvent = calendarRangeToFullCalendarEvent(range);
if (found !== undefined) {
found.start = newEvent.start;
found.end = newEvent.end;
found.locationId = range.location.id;
found.locationName = range.location.name;
}
state.key = state.key + 1;
},
},
actions: {
fetchRanges(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(ctx.getters.getRangeSource);
}
if (ctx.getters.isRangeLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
ctx.commit("addLoaded", {
start: start,
end: end,
});
return fetchCalendarRangeForUser(
ctx.rootGetters["me/getMe"],
start,
end,
).then((ranges: CalendarRange[]) => {
ctx.commit("addRanges", ranges);
return Promise.resolve(null);
});
},
createRange(
ctx: Context,
{ start, end, location }: { start: Date; end: Date; location: Location },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range.json?`;
if (ctx.rootState.me.me === null) {
throw new Error("user is currently null");
}
const body = {
user: {
id: ctx.rootState.me.me.id,
type: "user",
}, },
startDate: { updateRange(state: CalendarRangesState, range: CalendarRange) {
datetime: datetimeToISO(start), const found = state.ranges.find(
(r) => r.calendarRangeId === range.id && r.is === "range",
);
const newEvent = calendarRangeToFullCalendarEvent(range);
if (found !== undefined) {
found.start = newEvent.start;
found.end = newEvent.end;
found.locationId = range.location.id;
found.locationName = range.location.name;
}
state.key = state.key + 1;
}, },
endDate: { },
datetime: datetimeToISO(end), actions: {
fetchRanges(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(ctx.getters.getRangeSource);
}
if (ctx.getters.isRangeLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
ctx.commit("addLoaded", {
start: start,
end: end,
});
return fetchCalendarRangeForUser(
ctx.rootGetters["me/getMe"],
start,
end,
).then((ranges: CalendarRange[]) => {
ctx.commit("addRanges", ranges);
return Promise.resolve(null);
});
}, },
location: { createRange(
id: location.id, ctx: Context,
type: "location", {
start,
end,
location,
}: { start: Date; end: Date; location: Location },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range.json?`;
if (ctx.rootState.me.me === null) {
throw new Error("user is currently null");
}
const body = {
user: {
id: ctx.rootState.me.me.id,
type: "user",
},
startDate: {
datetime: datetimeToISO(start),
},
endDate: {
datetime: datetimeToISO(end),
},
location: {
id: location.id,
type: "location",
},
} as CalendarRangeCreate;
return makeFetch<CalendarRangeCreate, CalendarRange>(
"POST",
url,
body,
)
.then((newRange) => {
ctx.commit("addRange", newRange);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
throw error;
});
}, },
} as CalendarRangeCreate; deleteRange(ctx: Context, calendarRangeId: number) {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
return makeFetch<CalendarRangeCreate, CalendarRange>("POST", url, body) makeFetch<undefined, never>("DELETE", url).then(() => {
.then((newRange) => { ctx.commit("removeRange", calendarRangeId);
ctx.commit("addRange", newRange); });
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
throw error;
});
},
deleteRange(ctx: Context, calendarRangeId: number) {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
makeFetch<undefined, never>("DELETE", url).then(() => {
ctx.commit("removeRange", calendarRangeId);
});
},
patchRangeTime(
ctx,
{
calendarRangeId,
start,
end,
}: { calendarRangeId: number; start: Date; end: Date },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
const body = {
startDate: {
datetime: datetimeToISO(start),
}, },
endDate: { patchRangeTime(
datetime: datetimeToISO(end), ctx,
{
calendarRangeId,
start,
end,
}: { calendarRangeId: number; start: Date; end: Date },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
const body = {
startDate: {
datetime: datetimeToISO(start),
},
endDate: {
datetime: datetimeToISO(end),
},
} as CalendarRangeEdit;
return makeFetch<CalendarRangeEdit, CalendarRange>(
"PATCH",
url,
body,
)
.then((range) => {
ctx.commit("updateRange", range);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
return Promise.resolve(null);
});
}, },
} as CalendarRangeEdit; patchRangeLocation(
ctx,
{
location,
calendarRangeId,
}: { location: Location; calendarRangeId: number },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
const body = {
location: {
id: location.id,
type: "location",
},
} as CalendarRangeEdit;
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body) return makeFetch<CalendarRangeEdit, CalendarRange>(
.then((range) => { "PATCH",
ctx.commit("updateRange", range); url,
return Promise.resolve(null); body,
}) )
.catch((error) => { .then((range) => {
console.error(error); ctx.commit("updateRange", range);
return Promise.resolve(null); return Promise.resolve(null);
}); })
}, .catch((error) => {
patchRangeLocation( console.error(error);
ctx, return Promise.resolve(null);
{ });
location,
calendarRangeId,
}: { location: Location; calendarRangeId: number },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
const body = {
location: {
id: location.id,
type: "location",
}, },
} as CalendarRangeEdit; copyFromDayToAnotherDay(
ctx,
{ from, to }: { from: Date; to: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
ctx.getters["getRangesOnDate"](from);
const promises = [];
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body) for (const r of rangesToCopy) {
.then((range) => { const start = new Date(ISOToDatetime(r.start) as Date);
ctx.commit("updateRange", range); start.setFullYear(
return Promise.resolve(null); to.getFullYear(),
}) to.getMonth(),
.catch((error) => { to.getDate(),
console.error(error); );
return Promise.resolve(null); const end = new Date(ISOToDatetime(r.end) as Date);
}); end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
const location = ctx.rootGetters["locations/getLocationById"](
r.locationId,
);
promises.push(
ctx.dispatch("createRange", { start, end, location }),
);
}
return Promise.all(promises).then(() => Promise.resolve(null));
},
copyFromWeekToAnotherWeek(
ctx: Context,
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
ctx.getters["getRangesOnWeek"](fromMonday);
const promises = [];
const diffTime = toMonday.getTime() - fromMonday.getTime();
for (const r of rangesToCopy) {
const start = new Date(ISOToDatetime(r.start) as Date);
const end = new Date(ISOToDatetime(r.end) as Date);
start.setTime(start.getTime() + diffTime);
end.setTime(end.getTime() + diffTime);
const location = ctx.rootGetters["locations/getLocationById"](
r.locationId,
);
promises.push(
ctx.dispatch("createRange", { start, end, location }),
);
}
return Promise.all(promises).then(() => Promise.resolve(null));
},
}, },
copyFromDayToAnotherDay(
ctx,
{ from, to }: { from: Date; to: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
ctx.getters["getRangesOnDate"](from);
const promises = [];
for (const r of rangesToCopy) {
const start = new Date(ISOToDatetime(r.start) as Date);
start.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
const end = new Date(ISOToDatetime(r.end) as Date);
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
const location = ctx.rootGetters["locations/getLocationById"](
r.locationId,
);
promises.push(ctx.dispatch("createRange", { start, end, location }));
}
return Promise.all(promises).then(() => Promise.resolve(null));
},
copyFromWeekToAnotherWeek(
ctx: Context,
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
ctx.getters["getRangesOnWeek"](fromMonday);
const promises = [];
const diffTime = toMonday.getTime() - fromMonday.getTime();
for (const r of rangesToCopy) {
const start = new Date(ISOToDatetime(r.start) as Date);
const end = new Date(ISOToDatetime(r.end) as Date);
start.setTime(start.getTime() + diffTime);
end.setTime(end.getTime() + diffTime);
const location = ctx.rootGetters["locations/getLocationById"](
r.locationId,
);
promises.push(ctx.dispatch("createRange", { start, end, location }));
}
return Promise.all(promises).then(() => Promise.resolve(null));
},
},
} as Module<CalendarRangesState, State>; } as Module<CalendarRangesState, State>;

View File

@@ -8,102 +8,109 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
import { COLORS } from "../../../Calendar/const"; import { COLORS } from "../../../Calendar/const";
export interface CalendarRemotesState { export interface CalendarRemotesState {
remotes: EventInput[]; remotes: EventInput[];
remotesLoaded: { start: number; end: number }[]; remotesLoaded: { start: number; end: number }[];
remotesIndex: Set<string>; remotesIndex: Set<string>;
key: number; key: number;
} }
type Context = ActionContext<CalendarRemotesState, State>; type Context = ActionContext<CalendarRemotesState, State>;
export default { export default {
namespaced: true, namespaced: true,
state: (): CalendarRemotesState => ({ state: (): CalendarRemotesState => ({
remotes: [], remotes: [],
remotesLoaded: [], remotesLoaded: [],
remotesIndex: new Set<string>(), remotesIndex: new Set<string>(),
key: 0, key: 0,
}), }),
getters: { getters: {
isRemotesLoaded: isRemotesLoaded:
(state: CalendarRemotesState) => (state: CalendarRemotesState) =>
({ start, end }: { start: Date; end: Date }): boolean => { ({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.remotesLoaded) { for (const range of state.remotesLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) { if (
return true; start.getTime() === range.start &&
} end.getTime() === range.end
} ) {
return true;
}
}
return false; return false;
}, },
},
mutations: {
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
console.log("addRemotes", ranges);
const toAdd = ranges
.map((cr) => remoteToFullCalendarEvent(cr))
.filter((r) => !state.remotesIndex.has(r.id));
toAdd.forEach((r) => {
state.remotesIndex.add(r.id);
state.remotes.push(r);
});
state.key = state.key + toAdd.length;
}, },
addLoaded( mutations: {
state: CalendarRemotesState, addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
payload: { start: Date; end: Date }, console.log("addRemotes", ranges);
) {
state.remotesLoaded.push({ const toAdd = ranges
start: payload.start.getTime(), .map((cr) => remoteToFullCalendarEvent(cr))
end: payload.end.getTime(), .filter((r) => !state.remotesIndex.has(r.id));
});
toAdd.forEach((r) => {
state.remotesIndex.add(r.id);
state.remotes.push(r);
});
state.key = state.key + toAdd.length;
},
addLoaded(
state: CalendarRemotesState,
payload: { start: Date; end: Date },
) {
state.remotesLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
}, },
}, actions: {
actions: { fetchRemotes(
fetchRemotes( ctx: Context,
ctx: Context, payload: { start: Date; end: Date },
payload: { start: Date; end: Date }, ): Promise<null> {
): Promise<null> { const start = payload.start;
const start = payload.start; const end = payload.end;
const end = payload.end;
if (ctx.rootGetters["me/getMe"] === null) { if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(null); return Promise.resolve(null);
} }
if (ctx.getters.isRemotesLoaded({ start, end })) { if (ctx.getters.isRemotesLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource); return Promise.resolve(ctx.getters.getRangeSource);
} }
ctx.commit("addLoaded", { ctx.commit("addLoaded", {
start: start, start: start,
end: end, end: end,
}); });
return fetchCalendarRemoteForUser(ctx.rootGetters["me/getMe"], start, end) return fetchCalendarRemoteForUser(
.then((remotes: CalendarRemote[]) => { ctx.rootGetters["me/getMe"],
// to be add when reactivity problem will be solve ? start,
//ctx.commit('addRemotes', remotes); end,
const inputs = remotes )
.map((cr) => remoteToFullCalendarEvent(cr)) .then((remotes: CalendarRemote[]) => {
.map((cr) => ({ // to be add when reactivity problem will be solve ?
...cr, //ctx.commit('addRemotes', remotes);
backgroundColor: COLORS[0], const inputs = remotes
textColor: "black", .map((cr) => remoteToFullCalendarEvent(cr))
editable: false, .map((cr) => ({
})); ...cr,
ctx.commit("calendarRanges/addExternals", inputs, { backgroundColor: COLORS[0],
root: true, textColor: "black",
}); editable: false,
return Promise.resolve(null); }));
}) ctx.commit("calendarRanges/addExternals", inputs, {
.catch((e: TransportExceptionInterface) => { root: true,
console.error(e); });
return Promise.resolve(null);
})
.catch((e: TransportExceptionInterface) => {
console.error(e);
return Promise.resolve(null); return Promise.resolve(null);
}); });
},
}, },
},
} as Module<CalendarRemotesState, State>; } as Module<CalendarRemotesState, State>;

View File

@@ -2,77 +2,77 @@ import { State } from "./../index";
import { ActionContext } from "vuex"; import { ActionContext } from "vuex";
export interface FullCalendarState { export interface FullCalendarState {
currentView: { currentView: {
start: Date | null; start: Date | null;
end: Date | null; end: Date | null;
}; };
key: number; key: number;
} }
type Context = ActionContext<FullCalendarState, State>; type Context = ActionContext<FullCalendarState, State>;
export default { export default {
namespaced: true, namespaced: true,
state: (): FullCalendarState => ({ state: (): FullCalendarState => ({
currentView: { currentView: {
start: null, start: null,
end: null, end: null,
},
key: 0,
}),
mutations: {
setCurrentDatesView: function (
state: FullCalendarState,
payload: { start: Date; end: Date },
): void {
state.currentView.start = payload.start;
state.currentView.end = payload.end;
},
increaseKey: function (state: FullCalendarState): void {
state.key = state.key + 1;
},
}, },
key: 0, actions: {
}), setCurrentDatesView(
mutations: { ctx: Context,
setCurrentDatesView: function ( { start, end }: { start: Date | null; end: Date | null },
state: FullCalendarState, ): Promise<null> {
payload: { start: Date; end: Date }, console.log("dispatch setCurrentDatesView", { start, end });
): void {
state.currentView.start = payload.start;
state.currentView.end = payload.end;
},
increaseKey: function (state: FullCalendarState): void {
state.key = state.key + 1;
},
},
actions: {
setCurrentDatesView(
ctx: Context,
{ start, end }: { start: Date | null; end: Date | null },
): Promise<null> {
console.log("dispatch setCurrentDatesView", { start, end });
if ( if (
ctx.state.currentView.start !== start || ctx.state.currentView.start !== start ||
ctx.state.currentView.end !== end ctx.state.currentView.end !== end
) { ) {
ctx.commit("setCurrentDatesView", { start, end }); ctx.commit("setCurrentDatesView", { start, end });
} }
if (start !== null && end !== null) { if (start !== null && end !== null) {
return Promise.all([ return Promise.all([
ctx ctx
.dispatch( .dispatch(
"calendarRanges/fetchRanges", "calendarRanges/fetchRanges",
{ start, end }, { start, end },
{ root: true }, { root: true },
) )
.then((_) => Promise.resolve(null)), .then((_) => Promise.resolve(null)),
ctx ctx
.dispatch( .dispatch(
"calendarRemotes/fetchRemotes", "calendarRemotes/fetchRemotes",
{ start, end }, { start, end },
{ root: true }, { root: true },
) )
.then((_) => Promise.resolve(null)), .then((_) => Promise.resolve(null)),
ctx ctx
.dispatch( .dispatch(
"calendarLocals/fetchLocals", "calendarLocals/fetchLocals",
{ start, end }, { start, end },
{ root: true }, { root: true },
) )
.then((_) => Promise.resolve(null)), .then((_) => Promise.resolve(null)),
]).then((_) => Promise.resolve(null)); ]).then((_) => Promise.resolve(null));
} else { } else {
return Promise.resolve(null); return Promise.resolve(null);
} }
},
}, },
},
}; };

View File

@@ -5,61 +5,61 @@ import { getLocations } from "../../../../../../../ChillMainBundle/Resources/pub
import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user"; import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user";
export interface LocationState { export interface LocationState {
locations: Location[]; locations: Location[];
locationPicked: Location | null; locationPicked: Location | null;
currentLocation: Location | null; currentLocation: Location | null;
} }
export default { export default {
namespaced: true, namespaced: true,
state: (): LocationState => { state: (): LocationState => {
return { return {
locations: [], locations: [],
locationPicked: null, locationPicked: null,
currentLocation: null, currentLocation: null,
}; };
},
getters: {
getLocationById:
(state) =>
(id: number): Location | undefined => {
return state.locations.find((l) => l.id === id);
},
},
mutations: {
setLocations(state, locations): void {
state.locations = locations;
}, },
setLocationPicked(state, location: Location | null): void { getters: {
if (null === location) { getLocationById:
state.locationPicked = null; (state) =>
return; (id: number): Location | undefined => {
} return state.locations.find((l) => l.id === id);
},
},
mutations: {
setLocations(state, locations): void {
state.locations = locations;
},
setLocationPicked(state, location: Location | null): void {
if (null === location) {
state.locationPicked = null;
return;
}
state.locationPicked = state.locationPicked =
state.locations.find((l) => l.id === location.id) || null; state.locations.find((l) => l.id === location.id) || null;
}, },
setCurrentLocation(state, location: Location | null): void { setCurrentLocation(state, location: Location | null): void {
if (null === location) { if (null === location) {
state.currentLocation = null; state.currentLocation = null;
return; return;
} }
state.currentLocation = state.currentLocation =
state.locations.find((l) => l.id === location.id) || null; state.locations.find((l) => l.id === location.id) || null;
},
}, },
}, actions: {
actions: { getLocations(ctx): Promise<void> {
getLocations(ctx): Promise<void> { return getLocations().then((locations) => {
return getLocations().then((locations) => { ctx.commit("setLocations", locations);
ctx.commit("setLocations", locations); return Promise.resolve();
return Promise.resolve(); });
}); },
getCurrentLocation(ctx): Promise<void> {
return whereami().then((location) => {
ctx.commit("setCurrentLocation", location);
});
},
}, },
getCurrentLocation(ctx): Promise<void> {
return whereami().then((location) => {
ctx.commit("setCurrentLocation", location);
});
},
},
} as Module<LocationState, State>; } as Module<LocationState, State>;

View File

@@ -3,24 +3,24 @@ import { User } from "../../../../../../../ChillMainBundle/Resources/public/type
import { ActionContext } from "vuex"; import { ActionContext } from "vuex";
export interface MeState { export interface MeState {
me: User | null; me: User | null;
} }
type Context = ActionContext<MeState, State>; type Context = ActionContext<MeState, State>;
export default { export default {
namespaced: true, namespaced: true,
state: (): MeState => ({ state: (): MeState => ({
me: null, me: null,
}), }),
getters: { getters: {
getMe: function (state: MeState): User | null { getMe: function (state: MeState): User | null {
return state.me; return state.me;
},
}, },
}, mutations: {
mutations: { setWhoAmi(state: MeState, me: User) {
setWhoAmi(state: MeState, me: User) { state.me = me;
state.me = me; },
}, },
},
}; };

View File

@@ -1,51 +1,51 @@
<template> <template>
<div> <div>
<h2 class="chill-red"> <h2 class="chill-red">
{{ $t("choose_your_calendar_user") }} {{ $t("choose_your_calendar_user") }}
</h2> </h2>
<VueMultiselect <VueMultiselect
name="field" name="field"
id="calendarUserSelector" id="calendarUserSelector"
v-model="value" v-model="value"
track-by="id" track-by="id"
label="value" label="value"
:custom-label="transName" :custom-label="transName"
:placeholder="$t('select_user')" :placeholder="$t('select_user')"
:multiple="true" :multiple="true"
:close-on-select="false" :close-on-select="false"
:allow-empty="true" :allow-empty="true"
:model-value="value" :model-value="value"
:select-label="$t('multiselect.select_label')" :select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')" :deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')" :selected-label="$t('multiselect.selected_label')"
@select="selectUsers" @select="selectUsers"
@remove="unSelectUsers" @remove="unSelectUsers"
@close="coloriseSelectedValues" @close="coloriseSelectedValues"
:options="options" :options="options"
/> />
</div> </div>
<div class="form-check"> <div class="form-check">
<input <input
type="checkbox" type="checkbox"
id="myCalendar" id="myCalendar"
class="form-check-input" class="form-check-input"
v-model="showMyCalendarWidget" v-model="showMyCalendarWidget"
/> />
<label class="form-check-label" for="myCalendar">{{ <label class="form-check-label" for="myCalendar">{{
$t("show_my_calendar") $t("show_my_calendar")
}}</label> }}</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input <input
type="checkbox" type="checkbox"
id="weekends" id="weekends"
class="form-check-input" class="form-check-input"
@click="toggleWeekends" @click="toggleWeekends"
/> />
<label class="form-check-label" for="weekends">{{ <label class="form-check-label" for="weekends">{{
$t("show_weekends") $t("show_weekends")
}}</label> }}</label>
</div> </div>
</template> </template>
<script> <script>
import { fetchCalendarRanges, fetchCalendar } from "../../_api/api"; import { fetchCalendarRanges, fetchCalendar } from "../../_api/api";
@@ -53,183 +53,206 @@ import VueMultiselect from "vue-multiselect";
import { whoami } from "ChillPersonAssets/vuejs/AccompanyingCourse/api"; import { whoami } from "ChillPersonAssets/vuejs/AccompanyingCourse/api";
const COLORS = [ const COLORS = [
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */ /* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
"#8dd3c7", "#8dd3c7",
"#ffffb3", "#ffffb3",
"#bebada", "#bebada",
"#fb8072", "#fb8072",
"#80b1d3", "#80b1d3",
"#fdb462", "#fdb462",
"#b3de69", "#b3de69",
"#fccde5", "#fccde5",
"#d9d9d9", "#d9d9d9",
"#bc80bd", "#bc80bd",
"#ccebc5", "#ccebc5",
"#ffed6f", "#ffed6f",
]; ];
export default { export default {
name: "CalendarUserSelector", name: "CalendarUserSelector",
components: { VueMultiselect }, components: { VueMultiselect },
props: [ props: [
"users", "users",
"updateEventsSource", "updateEventsSource",
"calendarEvents", "calendarEvents",
"showMyCalendar", "showMyCalendar",
"toggleMyCalendar", "toggleMyCalendar",
"toggleWeekends", "toggleWeekends",
], ],
data() { data() {
return { return {
errorMsg: [], errorMsg: [],
value: [], value: [],
options: [], options: [],
}; };
},
computed: {
showMyCalendarWidget: {
set(value) {
this.toggleMyCalendar(value);
this.updateEventsSource();
},
get() {
return this.showMyCalendar;
},
}, },
}, computed: {
methods: { showMyCalendarWidget: {
init() { set(value) {
this.fetchData(); this.toggleMyCalendar(value);
this.updateEventsSource();
},
get() {
return this.showMyCalendar;
},
},
}, },
fetchData() { methods: {
fetchCalendarRanges() init() {
.then( this.fetchData();
(calendarRanges) => },
new Promise((resolve, reject) => { fetchData() {
let results = calendarRanges.results; fetchCalendarRanges()
.then(
let users = []; (calendarRanges) =>
results.forEach((i) => {
if (!users.some((j) => i.user.id === j.id)) {
let ratio = Math.floor(users.length / COLORS.length);
let colorIndex = users.length - ratio * COLORS.length;
users.push({
id: i.user.id,
username: i.user.username,
color: COLORS[colorIndex],
});
}
});
let calendarEvents = [];
users.forEach((u) => {
let arr = results
.filter((i) => i.user.id === u.id)
.map((i) => ({
start: i.startDate.datetime,
end: i.endDate.datetime,
calendarRangeId: i.id,
sourceColor: u.color,
//display: 'background' // can be an option for the disponibility
}));
calendarEvents.push({
events: arr,
color: u.color,
textColor: "#444444",
editable: false,
id: u.id,
});
});
this.users.loaded = users;
this.options = users;
this.calendarEvents.loaded = calendarEvents;
whoami().then(
(me) =>
new Promise((resolve, reject) => {
this.users.logged = me;
let currentUser = users.find((u) => u.id === me.id);
this.value = currentUser;
fetchCalendar(currentUser.id).then(
(calendar) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
let results = calendar.results; let results = calendarRanges.results;
let events = results.map((i) => ({
start: i.startDate.datetime,
end: i.endDate.datetime,
}));
let calendarEventsCurrentUser = {
events: events,
color: "darkblue",
id: 1000,
editable: false,
};
this.calendarEvents.user = calendarEventsCurrentUser;
this.selectUsers(currentUser); let users = [];
resolve(); results.forEach((i) => {
if (!users.some((j) => i.user.id === j.id)) {
let ratio = Math.floor(
users.length / COLORS.length,
);
let colorIndex =
users.length - ratio * COLORS.length;
users.push({
id: i.user.id,
username: i.user.username,
color: COLORS[colorIndex],
});
}
});
let calendarEvents = [];
users.forEach((u) => {
let arr = results
.filter((i) => i.user.id === u.id)
.map((i) => ({
start: i.startDate.datetime,
end: i.endDate.datetime,
calendarRangeId: i.id,
sourceColor: u.color,
//display: 'background' // can be an option for the disponibility
}));
calendarEvents.push({
events: arr,
color: u.color,
textColor: "#444444",
editable: false,
id: u.id,
});
});
this.users.loaded = users;
this.options = users;
this.calendarEvents.loaded = calendarEvents;
whoami().then(
(me) =>
new Promise((resolve, reject) => {
this.users.logged = me;
let currentUser = users.find(
(u) => u.id === me.id,
);
this.value = currentUser;
fetchCalendar(currentUser.id).then(
(calendar) =>
new Promise(
(resolve, reject) => {
let results =
calendar.results;
let events =
results.map(
(i) => ({
start: i
.startDate
.datetime,
end: i
.endDate
.datetime,
}),
);
let calendarEventsCurrentUser =
{
events: events,
color: "darkblue",
id: 1000,
editable: false,
};
this.calendarEvents.user =
calendarEventsCurrentUser;
this.selectUsers(
currentUser,
);
resolve();
},
),
);
resolve();
}),
);
resolve();
}), }),
); )
.catch((error) => {
this.errorMsg.push(error.message);
});
},
transName(value) {
return `${value.username}`;
},
coloriseSelectedValues() {
let tags = document.querySelectorAll(
"div.multiselect__tags-wrap",
)[0];
resolve(); if (tags.hasChildNodes()) {
}), let children = tags.childNodes;
); for (let i = 0; i < children.length; i++) {
let child = children[i];
resolve(); if (child.nodeType === Node.ELEMENT_NODE) {
}), this.users.selected.forEach((u) => {
) if (child.hasChildNodes()) {
.catch((error) => { if (child.firstChild.innerText == u.username) {
this.errorMsg.push(error.message); child.style.background = u.color;
}); child.firstChild.style.color = "#444444";
}, }
transName(value) { }
return `${value.username}`; });
}, }
coloriseSelectedValues() {
let tags = document.querySelectorAll("div.multiselect__tags-wrap")[0];
if (tags.hasChildNodes()) {
let children = tags.childNodes;
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (child.nodeType === Node.ELEMENT_NODE) {
this.users.selected.forEach((u) => {
if (child.hasChildNodes()) {
if (child.firstChild.innerText == u.username) {
child.style.background = u.color;
child.firstChild.style.color = "#444444";
} }
} }
}); },
} selectEvents() {
} let selectedUsersId = this.users.selected.map((a) => a.id);
} this.calendarEvents.selected = this.calendarEvents.loaded.filter(
(a) => selectedUsersId.includes(a.id),
);
},
selectUsers(value) {
this.users.selected.push(value);
this.coloriseSelectedValues();
this.selectEvents();
this.updateEventsSource();
},
unSelectUsers(value) {
this.users.selected = this.users.selected.filter(
(a) => a.id != value.id,
);
this.selectEvents();
this.updateEventsSource();
},
}, },
selectEvents() { mounted() {
let selectedUsersId = this.users.selected.map((a) => a.id); this.init();
this.calendarEvents.selected = this.calendarEvents.loaded.filter((a) =>
selectedUsersId.includes(a.id),
);
}, },
selectUsers(value) {
this.users.selected.push(value);
this.coloriseSelectedValues();
this.selectEvents();
this.updateEventsSource();
},
unSelectUsers(value) {
this.users.selected = this.users.selected.filter((a) => a.id != value.id);
this.selectEvents();
this.updateEventsSource();
},
},
mounted() {
this.init();
},
}; };
</script> </script>

View File

@@ -1,23 +1,17 @@
{# list used in context of person, accompanyingPeriod or user #} {# list used in context of person or accompanyingPeriod #}
<div class="item-bloc"> {% if calendarItems|length > 0 %}
<div class="item-row main"> <div class="flex-table list-records context-accompanyingCourse">
<div class="item-col">
<div class="wrap-header"> {% for calendar in calendarItems %}
<div class="wl-row">
{% if calendar.status == 'canceled' %} <div class="item-bloc">
<div class="badge rounded-pill bg-danger"> <div class="item-row main">
<span>{{ 'chill_calendar.canceled'|trans }}: </span> <div class="item-col">
<span>{{ calendar.cancelReason.name|localize_translatable_string }}</span> <div class="wrap-header">
</div>
{% endif %}
</div>
<div class="wl-row"> <div class="wl-row">
<div class="wl-col title"> <div class="wl-col title">
<p class="date-label"> <p class="date-label">
{% if calendar.status == 'canceled' %}
<del>
{% endif %}
{% if context == 'person' and calendar.context == 'accompanying_period' %} {% if context == 'person' and calendar.context == 'accompanying_period' %}
<a href="{{ chill_path_add_return_path('chill_person_accompanying_course_index', {'accompanying_period_id': calendar.accompanyingPeriod.id}) }}" style="text-decoration: none;"> <a href="{{ chill_path_add_return_path('chill_person_accompanying_course_index', {'accompanying_period_id': calendar.accompanyingPeriod.id}) }}" style="text-decoration: none;">
<span class="badge bg-primary"> <span class="badge bg-primary">
@@ -25,9 +19,6 @@
</span> </span>
</a> </a>
{% endif %} {% endif %}
{% if calendar.status == 'canceled' %}
<del>
{% endif %}
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %} {% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
{{ calendar.startDate|format_datetime('short', 'short') }} {{ calendar.startDate|format_datetime('short', 'short') }}
- {{ calendar.endDate|format_datetime('short', 'short') }} - {{ calendar.endDate|format_datetime('short', 'short') }}
@@ -35,46 +26,44 @@
{{ calendar.startDate|format_datetime('short', 'short') }} {{ calendar.startDate|format_datetime('short', 'short') }}
- {{ calendar.endDate|format_datetime('none', 'short') }} - {{ calendar.endDate|format_datetime('none', 'short') }}
{% endif %} {% endif %}
{% if calendar.status == 'canceled' %} </p>
</del>
{% endif %}
<div class="duration short-message"> <div class="duration short-message">
<i class="fa fa-fw fa-hourglass-end"></i> <i class="fa fa-fw fa-hourglass-end"></i>
{{ calendar.duration|date('%H:%I') }} {{ calendar.duration|date('%H:%I') }}
{% if false == calendar.sendSMS or null == calendar.sendSMS %} {% if false == calendar.sendSMS or null == calendar.sendSMS %}
<!-- no sms will be sent --> <!-- no sms will be send -->
{% else %} {% else %}
{% if calendar.smsStatus == 'sms_sent' %} {% if calendar.smsStatus == 'sms_sent' %}
<span title="{{ 'SMS already sent'|trans }}" class="badge bg-info"> <span title="{{ 'SMS already sent'|trans }}" class="badge bg-info">
<i class="fa fa-check "></i> <i class="fa fa-check "></i>
<i class="fa fa-envelope "></i> <i class="fa fa-envelope "></i>
</span> </span>
{% else %} {% else %}
<span title="{{ 'Will send SMS'|trans }}" class="badge bg-info"> <span title="{{ 'Will send SMS'|trans }}" class="badge bg-info">
<i class="fa fa-envelope "></i> <i class="fa fa-envelope "></i>
<i class="fa fa-hourglass-end "></i> <i class="fa fa-hourglass-end "></i>
</span> </span>
{% endif %}
{% endif %}
</div>
</div>
</div>
</div>
<div class="item-col">
<ul class="list-content">
{% if calendar.mainUser is not empty %}
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box({'at_date': calendar.startDate}) }}</span>
{% endif %} {% endif %}
{% endif %} </ul>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="item-col"> {% if calendar.comment.comment is not empty
<ul class="list-content">
{% if calendar.mainUser is not empty %}
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box({'at_date': calendar.startDate}) }}</span>
{% endif %}
</ul>
</div>
</div>
</div>
{% if calendar.comment.comment is not empty
or calendar.users|length > 0 or calendar.users|length > 0
or calendar.thirdParties|length > 0 or calendar.thirdParties|length > 0
or calendar.users|length > 0 %} or calendar.users|length > 0 %}
@@ -87,133 +76,131 @@
} %} } %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if calendar.comment.comment is not empty %}
<div class="item-row details separator">
<div class="item-col comment">
{{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }}
</div>
</div>
{% endif %}
{% if calendar.location is not empty %}
<div class="item-row separator">
<div>
{% if calendar.location.address is not same as(null) and calendar.location.name is not empty %}
<i class="fa fa-map-marker"></i>{% endif %}
{% if calendar.location.name is not empty %}{{ calendar.location.name }}{% endif %}
{% if calendar.location.address is not same as(null) %}{{ calendar.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (calendar.location.name is empty)}) }}{% else %}
<i class="fa fa-map-marker"></i>{% endif %}
{% if calendar.location.phonenumber1 is not empty %}<i
class="fa fa-phone"></i> {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %}
{% if calendar.location.phonenumber2 is not empty %}<i
class="fa fa-phone"></i> {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %}
</div>
</div>
{% endif %}
{% if calendar.documents is not empty %}
<div class="item-row separator column">
<div>
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
</div>
</div>
{% endif %}
{% if calendar.activity is not null %}
<div class="item-row separator">
<div class="item-col">
<div class="wrap-list">
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Activity'|trans }}</h3></div>
<div class="wl-col list activity-linked">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
{{ calendar.activity.type.name | localize_translatable_string }}
{% if calendar.activity.emergency %}
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
{% endif %}
</span>
</h2>
<ul class="record_actions">
<li class="cancel">
<span class="createdBy">
{{ 'Created by'|trans }}
<b>{{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}</b>, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
</span>
</li>
{% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
<li>
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': calendar.activity.id}) }}" class="btn btn-sm btn-show" ></a>
</li>
{% endif %}
</ul>
{% if calendar.comment.comment is not empty %}
<div class="item-row details separator">
<div class="item-col comment">
{{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }}
</div> </div>
</div> </div>
</div>
</div>
</div>
{% endif %}
<div class="item-row separator">
<ul class="record_actions">
{% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) and calendar.status is not constant('STATUS_CANCELED', calendar) %}
{% if templates|length == 0 %}
<li>
<a class="btn btn-create"
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
{{ 'chill_calendar.Add a document'|trans }}
</a>
</li>
{% else %}
<li>
<div class="dropdown">
<button class="btn btn-create dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ 'chill_calendar.Add a document'|trans }}
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item"
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
{{ 'chill_calendar.Upload a document'|trans }}
</a>
</li>
{% for template in templates %}
<li>
<a class="dropdown-item"
href="{{ chill_path_add_return_path('chill_docgenerator_generate_from_template', {'template': template.id, 'entityClassName': 'Chill\\CalendarBundle\\Entity\\Calendar', 'entityId': calendar.id}) }}"
>
{{ template.name|localize_translatable_string }}
</a>
</li>
{% endfor %}
</ul>
</div>
</li>
{% endif %}
{% endif %} {% endif %}
{% if calendar.activity is null and (
(calendar.context == 'accompanying_period' and is_granted('CHILL_ACTIVITY_CREATE', calendar.accompanyingPeriod))
or
(calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person))
)
and calendar.status is not constant('STATUS_CANCELED', calendar)
%}
<li>
<a class="btn btn-create"
href="{{ chill_path_add_return_path('chill_calendar_calendar_to_activity', { 'id': calendar.id }) }}">
{{ 'Transform to activity'|trans }}
</a>
</li>
{% endif %}
{% if calendar.isInvited(app.user) and not calendar.isCanceled %} {% if calendar.location is not empty %}
<div class="item-row separator">
<div>
{% if calendar.location.address is not same as(null) and calendar.location.name is not empty %}
<i class="fa fa-map-marker"></i>{% endif %}
{% if calendar.location.name is not empty %}{{ calendar.location.name }}{% endif %}
{% if calendar.location.address is not same as(null) %}{{ calendar.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (calendar.location.name is empty)}) }}{% else %}
<i class="fa fa-map-marker"></i>{% endif %}
{% if calendar.location.phonenumber1 is not empty %}<i
class="fa fa-phone"></i> {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %}
{% if calendar.location.phonenumber2 is not empty %}<i
class="fa fa-phone"></i> {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %}
</div>
</div>
{% endif %}
<div class="item-row separator column">
<div>
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
</div>
</div>
{% if calendar.activity is not null %}
<div class="item-row separator">
<div class="item-col">
<div class="wrap-list">
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Activity'|trans }}</h3></div>
<div class="wl-col list activity-linked">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
{{ calendar.activity.type.name | localize_translatable_string }}
{% if calendar.activity.emergency %}
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
{% endif %}
</span>
</h2>
<ul class="record_actions">
<li class="cancel">
<span class="createdBy">
{{ 'Created by'|trans }}
<b>{{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}</b>, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
</span>
</li>
{% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
<li>
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': calendar.activity.id}) }}" class="btn btn-sm btn-show" ></a>
</li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<div class="item-row separator">
<ul class="record_actions">
{% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) %}
{% if templates|length == 0 %}
<li>
<a class="btn btn-create"
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
{{ 'chill_calendar.Add a document'|trans }}
</a>
</li>
{% else %}
<li>
<div class="dropdown">
<button class="btn btn-create dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ 'chill_calendar.Add a document'|trans }}
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item"
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
{{ 'chill_calendar.Upload a document'|trans }}
</a>
</li>
{% for template in templates %}
<li>
<a class="dropdown-item"
href="{{ chill_path_add_return_path('chill_docgenerator_generate_from_template', {'template': template.id, 'entityClassName': 'Chill\\CalendarBundle\\Entity\\Calendar', 'entityId': calendar.id}) }}"
>
{{ template.name|localize_translatable_string }}
</a>
</li>
{% endfor %}
</ul>
</div>
</li>
{% endif %}
{% endif %}
{% if calendar.activity is null and (
(calendar.context == 'accompanying_period' and is_granted('CHILL_ACTIVITY_CREATE', calendar.accompanyingPeriod))
or
(calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person))
)
%}
<li>
<a class="btn btn-create"
href="{{ chill_path_add_return_path('chill_calendar_calendar_to_activity', { 'id': calendar.id }) }}">
{{ 'Transform to activity'|trans }}
</a>
</li>
{% endif %}
{% if (calendar.isInvited(app.user)) %}
{% set invite = calendar.inviteForUser(app.user) %} {% set invite = calendar.inviteForUser(app.user) %}
<li> <li>
<div invite-answer data-status="{{ invite.status|e('html_attr') }}" <div invite-answer data-status="{{ invite.status|e('html_attr') }}"
@@ -226,18 +213,12 @@
class="btn btn-show "></a> class="btn btn-show "></a>
</li> </li>
{% endif %} {% endif %}
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', calendar) %}
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', calendar) and calendar.status is not constant('STATUS_CANCELED', calendar) %}
<li> <li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id }) }}" <a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id }) }}"
class="btn btn-update "></a> class="btn btn-update "></a>
</li> </li>
<li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_cancel', { 'id': calendar.id } ) }}"
class="btn btn-action"><i class="bi bi-x-circle"></i> {{ 'Cancel'|trans }}</a>
</li>
{% endif %} {% endif %}
{% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %} {% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %}
<li> <li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id } ) }}" <a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id } ) }}"
@@ -246,8 +227,14 @@
{% endif %} {% endif %}
</ul> </ul>
</div>
</div>
{% endfor %}
{% if calendarItems|length < paginator.getTotalItems %}
{{ chill_pagination(paginator) }}
{% endif %}
</div> </div>
{% endif %}
</div>

View File

@@ -1,29 +0,0 @@
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
{% set activeRouteKey = 'chill_calendar_calendar_list' %}
{% block title 'chill_calendar.cancel_calendar_item'|trans %}
{% block content %}
{{ form_start(form) }}
{{ form_row(form.cancelReason) }}
<ul class="record_actions sticky-form-buttons">
<li class="cancel">
<a
class="btn btn-cancel"
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'id': accompanyingCourse.id } )}}"
>
{{ 'Cancel'|trans|chill_return_path_label }}
</a>
</li>
<li>
{{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-save' }, 'label': 'Save' } ) }}
</li>
</ul>
{{ form_end(form) }}
{% endblock %}

View File

@@ -1,29 +0,0 @@
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% set activeRouteKey = 'chill_calendar_calendar_list' %}
{% block title 'chill_calendar.cancel_calendar_item'|trans %}
{% block content %}
{{ form_start(form) }}
{{ form_row(form.cancelReason) }}
<ul class="record_actions sticky-form-buttons">
<li class="cancel">
<a
class="btn btn-cancel"
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'id': person.id } )}}"
>
{{ 'Cancel'|trans|chill_return_path_label }}
</a>
</li>
<li>
{{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-save' }, 'label': 'Save' } ) }}
</li>
</ul>
{{ form_end(form) }}
{% endblock %}

View File

@@ -34,18 +34,7 @@
{% endif %} {% endif %}
</p> </p>
{% else %} {% else %}
{% if calendarItems|length > 0 %} {{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }}
<div class="flex-table list-records context-accompanyingCourse">
{% for calendar in calendarItems %}
{{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }}
{% endfor %}
</div>
{% if calendarItems|length < paginator.getTotalItems %}
{{ chill_pagination(paginator) }}
{% endif %}
{% endif %}
{% endif %} {% endif %}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">

View File

@@ -33,17 +33,7 @@
{% endif %} {% endif %}
</p> </p>
{% else %} {% else %}
{% if calendarItems|length > 0 %} {{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }}
<div class="flex-table list-records context-person">
{% for calendar in calendarItems %}
{{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }}
{% endfor %}
</div>
{% if calendarItems|length < paginator.getTotalItems %}
{{ chill_pagination(paginator) }}
{% endif %}
{% endif %}
{% endif %} {% endif %}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">

View File

@@ -5,7 +5,7 @@
{% block table_entities_thead_tr %} {% block table_entities_thead_tr %}
<th>{{ 'Id'|trans }}</th> <th>{{ 'Id'|trans }}</th>
<th>{{ 'Name'|trans }}</th> <th>{{ 'Name'|trans }}</th>
<th>{{ 'Canceled by'|trans }}</th> <th>{{ 'canceledBy'|trans }}</th>
<th>{{ 'active'|trans }}</th> <th>{{ 'active'|trans }}</th>
<th>&nbsp;</th> <th>&nbsp;</th>
{% endblock %} {% endblock %}

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