Compare commits

..

6 Commits

215 changed files with 10596 additions and 4400 deletions

View File

@@ -1,6 +0,0 @@
kind: DX
body: Remove dead code for wopi-link module
time: 2025-04-30T14:45:50.406111606+02:00
custom:
Issue: "352"
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Feature
body: Allow the merge of two accompanying period works
time: 2025-02-11T14:22:43.134106669+01:00
custom:
Issue: "359"
SchemaChange: No schema change

View File

@@ -1,7 +0,0 @@
kind: Feature
body: Add the document file name to the document title when a user upload a document,
unless there is already a document title.
time: 2025-04-24T14:22:11.800975422+02:00
custom:
Issue: "377"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Add desactivation date for social action and issue csv export
time: 2025-05-20T09:56:28.108941934+02:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -1,7 +0,0 @@
kind: Fixed
body: trying to prevent bug of typeerror in doc-history + improved display of document
history
time: 2025-04-24T13:39:43.878468232+02:00
custom:
Issue: "376"
SchemaChange: No schema change

View File

@@ -1,7 +0,0 @@
kind: Fixed
body: Display previous participation in acc course work even if the person has left
the acc course
time: 2025-04-24T16:37:46.970203594+02:00
custom:
Issue: "381"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: Fix display of text in calendar events
time: 2025-05-05T10:27:15.461493066+02:00
custom:
Issue: "372"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: Add missing translation for user_group.no_user_groups
time: 2025-05-14T14:53:39.53927329+02:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: UX
body: Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page
time: 2025-04-23T17:26:24.45777387+02:00
custom:
Issue: "374"
SchemaChange: No schema change

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,19 +0,0 @@
## v3.11.0 - 2025-04-17
### Feature
* ([#365](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/365)) Add counters of actions and activities, with 2 boxes to (1) show the number of active actions on total actions and (2) show the number of activities in a accompanying period, and pills in menus for showing the number of active actions and the number of activities.
* ([#364](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/364)) Added a second phone number "telephone2" to the thirdParty entity. Adapted twig templates and vuejs apps to handle this phone number
**Schema Change**: Add columns or tables
* Signature: add a button to go directly to the signature zone, even if there is only one
### Fixed
* Fixed wrong translations in the on-the-fly for creation of thirdParty
* Fixed update of phone number in on-the-fly edition of thirdParty
* Fixed closing of modal when editing thirdParty in accompanying course works
* Shorten the delay between two execution of AccompanyingPeriodStepChangeCronjob, to ensure at least one execution in a day
* ([#102](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/102)) Fix display of title in document list
* When cleaning the old stored object versions, do not throw an error if the stored object is not found on disk
* Add consistent log prefix and key to logs when stale workflows are automatically canceled
* ([#380](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/380)) Remove the "not null" validation constraint on recently added properties on HouseholdComposition
### DX
* Add new chill-col style for displaying title and aside in a flex table

View File

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

View File

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

View File

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

2
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -34,7 +34,6 @@ export default ts.config(
// override/add rules settings here, such as:
"vue/multi-word-component-names": "off",
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/ban-ts-comment": "off"
},
},
);

View File

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

View File

@@ -11,7 +11,6 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Menu;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
@@ -24,30 +23,22 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{
public function __construct(
protected Security $security,
protected TranslatorInterface $translator,
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
) {}
public function __construct(protected Security $security, protected TranslatorInterface $translator) {}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
$period = $parameters['accompanyingCourse'];
$activities = $this->managerRegistry->getManager()->getRepository(Activity::class)->findBy(
['accompanyingPeriod' => $period]
);
if (
AccompanyingPeriod::STEP_DRAFT !== $period->getStep()
&& $this->security->isGranted(ActivityVoter::SEE, $period)
) {
$menu->addChild($this->translator->trans('Activities'), [
$menu->addChild($this->translator->trans('Activity'), [
'route' => 'chill_activity_activity_list',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
], ])
->setExtras(['order' => 40, 'counter' => count($activities) > 0 ? count($activities) : null]);
->setExtras(['order' => 40]);
}
}

View File

@@ -11,7 +11,6 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Menu;
use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Entity\Person;
@@ -24,20 +23,13 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface
{
public function __construct(
private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
private AuthorizationCheckerInterface $authorizationChecker,
private TranslatorInterface $translator,
) {}
public function __construct(private AuthorizationCheckerInterface $authorizationChecker, private TranslatorInterface $translator) {}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
/** @var Person $person */
$person = $parameters['person'];
$count = $this->activityACLAwareRepository->countByPerson($person, ActivityVoter::SEE);
if ($this->authorizationChecker->isGranted(ActivityVoter::SEE, $person)) {
$menu->addChild(
$this->translator->trans('Activities'),
@@ -46,7 +38,7 @@ final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface
'routeParameters' => ['person_id' => $person->getId()],
]
)
->setExtras(['order' => 201, 'counter' => $count > 0 ? $count : null]);
->setExtra('order', 201);
}
}

View File

@@ -120,34 +120,3 @@ li.document-list-item {
vertical-align: baseline;
}
}
.badge-activity-type-simple {
@extend .badge;
display: inline-block;
margin: 0.2rem 0;
padding-left: 0;
padding-right: 0.5rem;
border-left: 20px groove #9acd32;
border-radius: $badge-border-radius;
color: black;
font-weight: normal;
font-size: unset;
max-width: 100%;
background-color: $gray-100;
overflow: hidden;
text-overflow: ellipsis;
text-indent: 5px hanging;
text-align: left;
&::before {
margin-right: 3px;
position: relative;
left: -0.5px;
font-family: ForkAwesome;
content: '\f04b';
color: #9acd32;
}
}

View File

@@ -11,7 +11,7 @@ import Location from "./components/Location.vue";
export default {
name: "App",
props: ["hasSocialIssues", "hasLocation", "hasPerson", "isSimpleEditor"],
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
components: {
ConcernedGroups,
SocialIssuesAcc,

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<div class="mb-3 row">
<div class="col-4">
<label :class="socialIssuesClassList">{{
trans(ACTIVITY_SOCIAL_ISSUES)
$t("activity.social_issues")
}}</label>
</div>
<div class="col-8">
@@ -12,9 +12,8 @@
:key="issue.id"
:issue="issue"
:selection="socialIssuesSelected"
@updateSelected="updateIssuesSelected"
>
</check-social-issue>
@update-selected="updateIssuesSelected"
/>
<div class="my-3">
<VueMultiselect
@@ -32,11 +31,10 @@
:allow-empty="true"
:show-labels="false"
:loading="issueIsLoading"
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
:placeholder="$t('activity.choose_other_social_issue')"
:options="socialIssuesOther"
@select="addIssueInList"
>
</VueMultiselect>
/>
</div>
</div>
</div>
@@ -44,46 +42,35 @@
<div class="mb-3 row">
<div class="col-4">
<label :class="socialActionsClassList">{{
trans(ACTIVITY_SOCIAL_ACTIONS)
$t("activity.social_actions")
}}</label>
</div>
<div class="col-8">
<div v-if="actionIsLoading === true">
<i
class="chill-green fa fa-circle-o-notch fa-spin fa-lg"
></i>
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg" />
</div>
<span
v-else-if="socialIssuesSelected.length === 0"
class="inline-choice chill-no-data-statement mt-3"
>
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
{{ $t("activity.select_first_a_social_issue") }}
</span>
<template
v-else-if="
socialActionsList.length > 0 &&
(socialIssuesSelected.length ||
socialActionsSelected.length)
<template v-else-if="socialActionsList.length > 0">
<div
v-if="
socialIssuesSelected.length ||
socialActionsSelected.length
"
>
<div
id="actionsList"
v-for="group in socialActionsList"
:key="group.issue"
>
<span class="badge bg-chill-l-gray text-dark">{{
group.issue
}}</span>
<check-social-action
v-for="action in group.actions"
v-for="action in socialActionsList"
:key="action.id"
:action="action"
:selection="socialActionsSelected"
@updateSelected="updateActionsSelected"
>
</check-social-action>
@update-selected="updateActionsSelected"
/>
</div>
</template>
@@ -93,7 +80,7 @@
"
class="inline-choice chill-no-data-statement mt-3"
>
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
{{ $t("activity.social_action_list_empty") }}
</span>
</div>
</div>
@@ -105,14 +92,6 @@ import VueMultiselect from "vue-multiselect";
import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
import {
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
ACTIVITY_SOCIAL_ACTIONS,
ACTIVITY_SOCIAL_ISSUES,
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
trans,
} from "translator";
export default {
name: "SocialIssuesAcc",
@@ -121,16 +100,6 @@ export default {
CheckSocialAction,
VueMultiselect,
},
setup() {
return {
trans,
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
ACTIVITY_SOCIAL_ACTIONS,
ACTIVITY_SOCIAL_ISSUES,
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
};
},
data() {
return {
issueIsLoading: false,
@@ -158,44 +127,53 @@ export default {
},
},
mounted() {
/* Load other issues in multiselect */
/* Load others issues in multiselect
*/
this.issueIsLoading = true;
this.actionAreLoaded = false;
getSocialIssues().then(
(response) =>
new Promise((resolve, reject) => {
this.$store.commit("updateIssuesOther", response.results);
getSocialIssues().then((response) => {
/* Add issues to the store */
this.$store.commit("updateIssuesOther", response);
/* Add in list the issues already associated (if not yet listed) */
/* Add in list the issues already associated (if not yet listed)
*/
this.socialIssuesSelected.forEach((issue) => {
if (
this.socialIssuesList.filter((i) => i.id === issue.id)
.length !== 1
this.socialIssuesList.filter(
(i) => i.id === issue.id,
).length !== 1
) {
this.$store.commit("addIssueInList", issue);
}
});
}, this);
/* Remove from multiselect the issues that are not yet in the checkbox list */
/* Remove from multiselect the issues that are not yet in checkbox list
*/
this.socialIssuesList.forEach((issue) => {
this.$store.commit("removeIssueInOther", issue);
});
}, this);
/* Filter issues */
/* Filter issues
*/
this.$store.commit("filterList", "issues");
/* Add in list the actions already associated (if not yet listed) */
/* Add in list the actions already associated (if not yet listed)
*/
this.socialActionsSelected.forEach((action) => {
this.$store.commit("addActionInList", action);
});
}, this);
/* Filter actions */
/* Filter issues
*/
this.$store.commit("filterList", "actions");
this.issueIsLoading = false;
this.actionAreLoaded = true;
this.updateActionsList();
});
resolve();
}),
);
},
methods: {
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
@@ -230,7 +208,7 @@ export default {
this.actionIsLoading = true;
getSocialActionByIssue(item.id).then(
(actions) =>
new Promise((resolve) => {
new Promise((resolve, reject) => {
actions.results.forEach((action) => {
this.$store.commit("addActionInList", action);
}, this);
@@ -257,24 +235,9 @@ export default {
};
</script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
<style lang="scss" scoped>
@import "ChillMainAssets/module/bootstrap/shared";
@import "ChillPersonAssets/chill/scss/mixins";
@import "ChillMainAssets/chill/scss/chill_variables";
span.multiselect__single {
display: none !important;
}
#actionsList {
border-radius: 0.5rem;
padding: 1rem;
margin: 0.5rem;
background-color: whitesmoke;
}
span.badge {
margin-bottom: 0.5rem;
@include badge_social($social-issue-color);
}
</style>

View File

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

View File

@@ -14,21 +14,18 @@ const i18n = _createI18n(activityMessages);
const hasSocialIssues = document.querySelector("#social-issues-acc") !== null;
const hasLocation = document.querySelector("#location") !== null;
const hasPerson = document.querySelector("#add-persons") !== null;
const isSimpleEditor = true;
const app = createApp({
template: `<app
:hasSocialIssues="hasSocialIssues"
:hasLocation="hasLocation"
:hasPerson="hasPerson"
:isSimpleEditor = "isSimpleEditor"
></app>`,
data() {
return {
hasSocialIssues,
hasLocation,
hasPerson,
isSimpleEditor
};
},
})

View File

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

View File

@@ -13,8 +13,7 @@
{% endif %}
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">
<div class="item-col" style="width: unset">
{% if document.isPending %}
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
{% elseif document.isFailure %}
@@ -22,13 +21,19 @@
{% endif %}
<div>
<div>
<div class="badge-activity-type-simple">
{% if activity.accompanyingPeriod is not null and context == 'person' %}
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
</span>&nbsp;
{% endif %}
<div class="badge-activity-type">
<span class="title_label"></span>
<span class="title_action">
{{ activity.type.name | localize_translatable_string }}
</div>
{% if activity.emergency %}
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
{% endif %}
</span>
</div>
</div>
<div class="denomination h2">
@@ -40,17 +45,12 @@
</div>
{% endif %}
</div>
<div class="aside">
<div class="item-col">
<div class="container">
<div class="dates row text-end">
<span>{{ document.createdAt|format_date('short') }}</span>
</div>
{% if activity.accompanyingPeriod is not null and context == 'person' %}
<div class="text-end">
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
</span>&nbsp;
</div>
{% endif %}
</div>
</div>
</div>

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
@import '~ChillMainAssets/module/bootstrap/shared';
@import '~ChillPersonAssets/chill/scss/mixins.scss';
@import 'bootstrap/scss/_badge.scss';
@import '~ChillMainAssets/module/bootstrap/shared';
.badge-calendar {
display: inline-block;
@@ -24,35 +23,3 @@
}
}
.badge-calendar-simple {
@extend .badge;
display: inline-block;
margin: 0.2rem 0;
padding-left: 0;
padding-right: 0.5rem;
border-left: 20px groove $chill-l-gray;
border-radius: $badge-border-radius;
max-width: 100%;
background-color: $gray-100;
color: black;
font-weight: normal;
overflow: hidden;
font-weight: normal;
font-size: unset;
text-overflow: ellipsis;
text-indent: 5px hanging;
text-align: left;
&::before {
margin-right: 3px;
position: relative;
left: -0.5px;
font-family: ForkAwesome;
content: '\f04b';
color: $chill-l-gray;
}
}

View File

@@ -16,7 +16,7 @@ div.calendar-list {
}
& > a.calendar-list__global {
display: inline-block;
display: inline-block;;
padding: 0.2rem;
min-width: 2rem;
border: 1px solid var(--bs-chill-blue);

View File

@@ -96,23 +96,23 @@
</div>
</div>
<FullCalendar :options="calendarOptions" ref="calendarRef">
<template v-slot:eventContent="{ event }">
<span :class="eventClasses(event)">
<b v-if="event.extendedProps.is === 'remote'">{{
event.title
<template v-slot:eventContent="arg: EventApi">
<span :class="eventClasses(arg.event)">
<b v-if="arg.event.extendedProps.is === 'remote'">{{
arg.event.title
}}</b>
<b v-else-if="event.extendedProps.is === 'range'"
>{{ formatDate(event.startStr) }} -
{{ event.extendedProps.locationName }}</b
<b v-else-if="arg.event.extendedProps.is === 'range'"
>{{ arg.timeText }} -
{{ arg.event.extendedProps.locationName }}</b
>
<b v-else-if="event.extendedProps.is === 'local'">{{
event.title
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
arg.event.title
}}</b>
<b v-else>no 'is'</b>
<a
v-if="event.extendedProps.is === 'range'"
v-if="arg.event.extendedProps.is === 'range'"
class="fa fa-fw fa-times delete"
@click.prevent="onClickDelete(event)"
@click.prevent="onClickDelete(arg.event)"
>
</a>
</span>
@@ -221,12 +221,13 @@ import type {
DatesSetArg,
EventInput,
} from "@fullcalendar/core";
import { computed, ref, onMounted } from "vue";
import { reactive, computed, ref, onMounted } from "vue";
import { useStore } from "vuex";
import { key } from "./store";
import FullCalendar from "@fullcalendar/vue3";
import frLocale from "@fullcalendar/core/locales/fr";
import interactionPlugin, {
DropArg,
EventResizeDoneArg,
} from "@fullcalendar/interaction";
import timeGridPlugin from "@fullcalendar/timegrid";
@@ -236,13 +237,19 @@ import {
EventDropArg,
EventClickArg,
} from "@fullcalendar/core";
import { dateToISO, ISOToDate } from "ChillMainAssets/chill/js/date";
import {
dateToISO,
ISOToDate,
} from "../../../../../ChillMainBundle/Resources/public/chill/js/date";
import VueMultiselect from "vue-multiselect";
import { Location } from "ChillMainAssets/types";
import { Location } from "../../../../../ChillMainBundle/Resources/public/types";
import EditLocation from "./Components/EditLocation.vue";
import { useI18n } from "vue-i18n";
const store = useStore(key);
const { t } = useI18n();
const showWeekends = ref(false);
const slotDuration = ref("00:15:00");
const slotMinTime = ref("09:00:00");
@@ -294,11 +301,6 @@ const nextWeeks = computed((): Weeks[] =>
}),
);
const formatDate = (datetime: string) => {
console.log(typeof datetime);
return ISOToDate(datetime);
};
const baseOptions = ref<CalendarOptions>({
locale: frLocale,
plugins: [interactionPlugin, timeGridPlugin],
@@ -351,7 +353,7 @@ const pickedLocation = computed<Location | null>({
* return the show classes for the event
* @param arg
*/
const eventClasses = function (): object {
const eventClasses = function (arg: EventApi): object {
return { calendarRangeItems: true };
};
@@ -429,6 +431,7 @@ function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) {
if (payload.event.extendedProps.is !== "range") {
return;
}
const changedEvent = payload.event;
store.dispatch("calendarRanges/patchRangeTime", {
calendarRangeId: payload.event.extendedProps.calendarRangeId,

View File

@@ -6,8 +6,7 @@
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">
<div class="item-col" style="width: unset">
{% if document.storedObject.isPending %}
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
{% elseif document.storedObject.isFailure %}
@@ -15,8 +14,15 @@
{% endif %}
<div>
{% if c.accompanyingPeriod is not null and context == 'person' %}
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }}
</span>&nbsp;
{% endif %}
<span class="badge-calendar-simple">
<span class="badge-calendar">
<span class="title_label"></span>
<span class="title_action">
{{ 'Calendar'|trans }}
{% if c.endDate.diff(c.startDate).days >= 1 %}
{{ c.startDate|format_datetime('short', 'short') }}
@@ -26,6 +32,7 @@
- {{ c.endDate|format_datetime('none', 'short') }}
{% endif %}
</span>
</span>
</div>
<div class="denomination h2">
@@ -37,17 +44,12 @@
</div>
{% endif %}
</div>
<div class="aside">
<div class="item-col">
<div class="container">
<div class="dates row text-end">
<span>{{ document.storedObject.createdAt|format_date('short') }}</span>
</div>
{% if c.accompanyingPeriod is not null and context == 'person' %}
<div class="text-end">
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }}
</span>&nbsp;
</div>
{% endif %}
</div>
</div>
</div>

View File

@@ -10,9 +10,6 @@ const startApp = (
collectionEntry: null | HTMLLIElement,
): void => {
console.log("app started", divElement);
const inputTitle = collectionEntry?.querySelector("input[type='text']");
const input_stored_object: HTMLInputElement | null =
divElement.querySelector("input[data-stored-object]");
if (null === input_stored_object) {
@@ -29,10 +26,9 @@ const startApp = (
const app = createApp({
template:
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
data() {
data(vm) {
return {
existingDoc: existingDoc,
inputTitle: inputTitle,
};
},
components: {
@@ -42,13 +38,10 @@ const startApp = (
addDocument: function ({
stored_object,
stored_object_version,
file_name,
}: {
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
file_name: string;
}): void {
stored_object.title = file_name;
console.log("object added", stored_object);
console.log("version added", stored_object_version);
this.$data.existingDoc = stored_object;
@@ -56,11 +49,6 @@ const startApp = (
input_stored_object.value = JSON.stringify(
this.$data.existingDoc,
);
if (this.$data.inputTitle) {
if (!this.$data.inputTitle?.value) {
this.$data.inputTitle.value = file_name;
}
}
},
removeDocument: function (object: StoredObject): void {
console.log("catch remove document", object);

View File

@@ -2,28 +2,26 @@
<teleport to="body">
<modal v-if="modalOpen" @close="modalOpen = false">
<template v-slot:header>
<h2>{{ trans(SIGNATURES_SIGNATURE_CONFIRMATION) }}</h2>
<h2>{{ $t("signature_confirmation") }}</h2>
</template>
<template v-slot:body>
<div class="signature-modal-body text-center" v-if="loading">
<p>
{{ trans(SIGNATURES_ELECTRONIC_SIGNATURE_IN_PROGRESS) }}
</p>
<p>{{ $t("electronic_signature_in_progress") }}</p>
<div class="loading">
<i
class="fa fa-circle-o-notch fa-spin fa-3x"
:title="trans(SIGNATURES_LOADING)"
:title="$t('loading')"
></i>
</div>
</div>
<div class="signature-modal-body text-center" v-else>
<p>{{ trans(SIGNATURES_YOU_ARE_GOING_TO_SIGN) }}</p>
<p>{{ trans(SIGNATURES_ARE_YOU_SURE) }}</p>
<p>{{ $t("you_are_going_to_sign") }}</p>
<p>{{ $t("are_you_sure") }}</p>
</div>
</template>
<template v-slot:footer>
<button class="btn btn-action" @click.prevent="confirmSign">
{{ trans(SIGNATURES_YES) }}
{{ $t("yes") }}
</button>
</template>
</modal>
@@ -84,39 +82,33 @@
@change="toggleMultiPage"
/>
<label class="form-check-label" for="checkboxMulti">
{{ trans(SIGNATURES_ALL_PAGES) }}
{{ $t("all_pages") }}
</label>
</template>
</div>
<div
v-if="signature.zones.length === 1 && signedState !== 'signed'"
v-if="signature.zones.length > 0"
class="col-5 p-0 text-center turnSignature"
>
<button
class="btn btn-light btn-sm"
@click="goToSignatureZoneUnique"
>
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
</button>
</div>
<div
v-if="signature.zones.length > 1"
class="col-5 p-0 text-center turnSignature"
>
<button
:disabled="isFirstSignatureZone()"
:disabled="
userSignatureZone === null ||
userSignatureZone?.index < 1
"
class="btn btn-light btn-sm"
@click="turnSignature(-1)"
>
{{ trans(SIGNATURES_LAST_ZONE) }}
{{ $t("last_zone") }}
</button>
<span>|</span>
<button
:disabled="isLastSignatureZone()"
:disabled="
userSignatureZone?.index >= signature.zones.length - 1
"
class="btn btn-light btn-sm"
@click="turnSignature(1)"
>
{{ trans(SIGNATURES_NEXT_ZONE) }}
{{ $t("next_zone") }}
</button>
</div>
<div class="col text-end" v-if="signedState !== 'signed'">
@@ -125,9 +117,9 @@
:hidden="!userSignatureZone"
@click="undoSign"
v-if="signature.zones.length > 1"
:title="trans(SIGNATURES_CHOOSE_ANOTHER_SIGNATURE)"
:title="$t('choose_another_signature')"
>
{{ trans(SIGNATURES_ANOTHER_ZONE) }}
{{ $t("another_zone") }}
</button>
<button
class="btn btn-misc btn-sm"
@@ -135,7 +127,7 @@
@click="undoSign"
v-else
>
{{ trans(SIGNATURES_CANCEL) }}
{{ $t("cancel") }}
</button>
<button
v-if="userSignatureZone === null"
@@ -147,7 +139,7 @@
active: canvasEvent === 'add',
}"
@click="toggleAddZone()"
:title="trans(SIGNATURES_ADD_SIGN_ZONE)"
:title="$t('add_sign_zone')"
>
<template v-if="canvasEvent === 'add'">
<div
@@ -199,70 +191,58 @@
@change="toggleMultiPage"
/>
<label class="form-check-label" for="checkboxMulti">
{{ trans(SIGNATURES_SEE_ALL_PAGES) }}
{{ $t("see_all_pages") }}
</label>
</template>
</div>
<div
v-if="signature.zones.length === 1 && signedState !== 'signed'"
v-if="signature.zones.length > 0 && signedState !== 'signed'"
class="col-4 d-xl-none text-center turnSignature p-0"
>
<button
class="btn btn-light btn-sm"
@click="goToSignatureZoneUnique"
>
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
</button>
</div>
<div
v-if="signature.zones.length > 1 && signedState !== 'signed'"
class="col-4 d-xl-none text-center turnSignature p-0"
>
<button
:disabled="isFirstSignatureZone()"
:disabled="
userSignatureZone === null ||
userSignatureZone?.index < 1
"
class="btn btn-light btn-sm"
@click="turnSignature(-1)"
>
{{ trans(SIGNATURES_LAST_ZONE) }}
{{ $t("last_zone") }}
</button>
<span>|</span>
<button
:disabled="isLastSignatureZone()"
:disabled="
userSignatureZone?.index >= signature.zones.length - 1
"
class="btn btn-light btn-sm"
@click="turnSignature(1)"
>
{{ trans(SIGNATURES_NEXT_ZONE) }}
{{ $t("next_zone") }}
</button>
</div>
<div
v-if="signature.zones.length === 1 && signedState !== 'signed'"
v-if="signature.zones.length > 0 && signedState !== 'signed'"
class="col-4 d-none d-xl-flex p-0 text-center turnSignature"
>
<button
class="btn btn-light btn-sm"
@click="goToSignatureZoneUnique"
>
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
</button>
</div>
<div
v-if="signature.zones.length > 1 && signedState !== 'signed'"
class="col-4 d-none d-xl-flex p-0 text-center turnSignature"
>
<button
:disabled="isFirstSignatureZone()"
:disabled="
userSignatureZone === null ||
userSignatureZone?.index < 1
"
class="btn btn-light btn-sm"
@click="turnSignature(-1)"
>
{{ trans(SIGNATURES_LAST_SIGN_ZONE) }}
{{ $t("last_sign_zone") }}
</button>
<span>|</span>
<button
:disabled="isLastSignatureZone()"
:disabled="
userSignatureZone?.index >= signature.zones.length - 1
"
class="btn btn-light btn-sm"
@click="turnSignature(1)"
>
{{ trans(SIGNATURES_NEXT_SIGN_ZONE) }}
{{ $t("next_sign_zone") }}
</button>
</div>
<div class="col text-end" v-if="signedState !== 'signed'">
@@ -272,7 +252,7 @@
@click="undoSign"
v-if="signature.zones.length > 1"
>
{{ trans(SIGNATURES_CHOOSE_ANOTHER_SIGNATURE) }}
{{ $t("choose_another_signature") }}
</button>
<button
class="btn btn-misc btn-sm"
@@ -280,7 +260,7 @@
@click="undoSign"
v-else
>
{{ trans(SIGNATURES_CANCEL) }}
{{ $t("cancel") }}
</button>
<button
v-if="userSignatureZone === null"
@@ -292,13 +272,13 @@
active: canvasEvent === 'add',
}"
@click="toggleAddZone()"
:title="trans(SIGNATURES_ADD_SIGN_ZONE)"
:title="$t('add_sign_zone')"
>
<template v-if="canvasEvent !== 'add'">
{{ trans(SIGNATURES_ADD_ZONE) }}
{{ $t("add_zone") }}
</template>
<template v-else>
{{ trans(SIGNATURES_CLICK_ON_DOCUMENT) }}
{{ $t("click_on_document") }}
<div
class="spinner-border spinner-border-sm"
role="status"
@@ -332,10 +312,10 @@
v-if="signedState !== 'signed'"
:href="getReturnPath()"
>
{{ trans(SIGNATURES_CANCEL) }}
{{ $t("cancel") }}
</a>
<a class="btn btn-misc" v-else :href="getReturnPath()">
{{ trans(SIGNATURES_RETURN) }}
{{ $t("return") }}
</a>
</div>
<div class="col text-end" v-if="signedState !== 'signed'">
@@ -344,7 +324,7 @@
:disabled="!userSignatureZone"
@click="sign"
>
{{ trans(SIGNATURES_SIGN) }}
{{ $t("sign") }}
</button>
</div>
<div class="col-4" v-else></div>
@@ -353,7 +333,7 @@
</template>
<script setup lang="ts">
import { ref, Ref } from "vue";
import { ref, Ref, reactive } from "vue";
import { useToast } from "vue-toast-notification";
import "vue-toast-notification/dist/theme-sugar.css";
import {
@@ -364,47 +344,25 @@ import {
SignedState,
ZoomLevel,
} from "../../types";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import * as pdfjsLib from "pdfjs-dist";
import {
PDFDocumentProxy,
PDFPageProxy,
} from "pdfjs-dist/types/src/display/api";
import {
SIGNATURES_YES,
SIGNATURES_ARE_YOU_SURE,
SIGNATURES_YOU_ARE_GOING_TO_SIGN,
SIGNATURES_SIGNATURE_CONFIRMATION,
SIGNATURES_SIGN,
SIGNATURES_CHOOSE_ANOTHER_SIGNATURE,
SIGNATURES_CANCEL,
SIGNATURES_LAST_SIGN_ZONE,
SIGNATURES_NEXT_SIGN_ZONE,
SIGNATURES_ADD_SIGN_ZONE,
SIGNATURES_CLICK_ON_DOCUMENT,
SIGNATURES_LAST_ZONE,
SIGNATURES_NEXT_ZONE,
SIGNATURES_ADD_ZONE,
SIGNATURES_ANOTHER_ZONE,
SIGNATURES_ELECTRONIC_SIGNATURE_IN_PROGRESS,
SIGNATURES_LOADING,
SIGNATURES_RETURN,
SIGNATURES_SEE_ALL_PAGES,
SIGNATURES_ALL_PAGES,
SIGNATURES_GO_TO_SIGNATURE_UNIQUE,
trans,
} from "translator";
// @ts-ignore incredible but the console.log is needed
// @ts-ignore
import * as PdfWorker from "pdfjs-dist/build/pdf.worker.mjs";
console.log(PdfWorker);
console.log(PdfWorker); // incredible but this is needed
// import { PdfWorker } from 'pdfjs-dist/build/pdf.worker.mjs'
// pdfjsLib.GlobalWorkerOptions.workerSrc = PdfWorker;
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import { download_doc_as_pdf } from "../StoredObjectButton/helpers";
import {
download_and_decrypt_doc,
download_doc_as_pdf,
} from "../StoredObjectButton/helpers";
pdfjsLib.GlobalWorkerOptions.workerSrc = "pdfjs-dist/build/pdf.worker.mjs";
@@ -475,16 +433,6 @@ const $toast = useToast();
const signature = window.signature;
const isFirstSignatureZone = () =>
userSignatureZone.value?.index != null
? userSignatureZone.value.index < 1
: false;
const isLastSignatureZone = () =>
userSignatureZone.value?.index
? userSignatureZone.value.index >= signature.zones.length - 1
: false;
const setZoomLevel = async (zoomLevel: string) => {
zoom.value = Number.parseFloat(zoomLevel);
await resetPages();
@@ -656,15 +604,6 @@ const turnPage = async (upOrDown: number) => {
}
};
const selectZoneInCanvas = (signatureZone: SignatureZone) => {
page.value = signatureZone.PDFPage.index + 1;
const canvas = getCanvas(signatureZone.PDFPage.index + 1);
selectZone(signatureZone, canvas);
canvas.scrollIntoView();
};
const goToSignatureZoneUnique = () => selectZoneInCanvas(signature.zones[0]);
const turnSignature = async (upOrDown: number) => {
let zoneIndex = userSignatureZone.value?.index ?? -1;
if (zoneIndex < -1) {
@@ -677,7 +616,10 @@ const turnSignature = async (upOrDown: number) => {
}
let currentZone = signature.zones[zoneIndex];
if (currentZone) {
selectZoneInCanvas(currentZone);
page.value = currentZone.PDFPage.index + 1;
const canvas = getCanvas(currentZone.PDFPage.index + 1);
selectZone(currentZone, canvas);
canvas.scrollIntoView();
}
};
@@ -812,7 +754,7 @@ const confirmSign = () => {
zone: userSignatureZone.value,
};
makeFetch("POST", url, body)
.then(() => {
.then((r) => {
checkForReady();
})
.catch((error) => {
@@ -834,7 +776,9 @@ const undoSign = async () => {
};
const toggleAddZone = () => {
canvasEvent.value = canvasEvent.value === "select" ? "add" : "select";
canvasEvent.value === "select"
? (canvasEvent.value = "add")
: (canvasEvent.value = "select");
};
const addZoneEvent = async (e: PointerEvent, canvas: HTMLCanvasElement) => {

View File

@@ -23,7 +23,6 @@ const emit =
{
stored_object_version: StoredObjectVersionCreated,
stored_object: StoredObject,
file_name: string,
},
) => void
>();
@@ -115,21 +114,7 @@ const handleFile = async (file: File): Promise<void> => {
persisted: false,
};
const fileName = file.name;
let file_name = "Nouveau document";
const file_name_split = fileName.split(".");
if (file_name_split.length > 1) {
const extension = file_name_split
? file_name_split[file_name_split.length - 1]
: "";
file_name = fileName.replace(extension, "").slice(0, -1);
}
emit("addDocument", {
stored_object,
stored_object_version,
file_name: file_name,
});
emit("addDocument", { stored_object, stored_object_version });
uploading.value = false;
};
</script>

View File

@@ -20,7 +20,6 @@ const emit = defineEmits<{
{
stored_object: StoredObject,
stored_object_version: StoredObjectVersion,
file_name: string,
},
): void;
(e: "removeDocument"): void;
@@ -43,16 +42,14 @@ const buttonState = computed<"add" | "replace">(() => {
function onAddDocument({
stored_object,
stored_object_version,
file_name,
}: {
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
file_name: string;
}): void {
const message =
buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
$toast.success(message);
emit("addDocument", { stored_object_version, stored_object, file_name });
emit("addDocument", { stored_object_version, stored_object });
state.showModal = false;
}

View File

@@ -19,7 +19,6 @@ const emit = defineEmits<{
{
stored_object: StoredObject,
stored_object_version: StoredObjectVersion,
file_name: string,
},
): void;
(e: "removeDocument"): void;
@@ -54,13 +53,11 @@ const dav_link_href = computed<string | undefined>(() => {
const onAddDocument = ({
stored_object,
stored_object_version,
file_name,
}: {
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
file_name: string;
}): void => {
emit("addDocument", { stored_object, stored_object_version, file_name });
emit("addDocument", { stored_object, stored_object_version });
};
const onRemoveDocument = (e: Event): void => {

View File

@@ -53,7 +53,7 @@ const onRestored = ({
<template>
<template v-if="props.versions.length > 0">
<div class="container">
<template v-for="v in props.versions" :key="v.id">
<template v-for="v in props.versions">
<history-button-list-item
:version="v"
:can-edit="canEdit"

View File

@@ -32,17 +32,13 @@ const onRestore = ({
emit("restoreVersion", { newVersion });
};
const isKeptBeforeConversion = computed<boolean>(() => {
if ("point-in-times" in props.version) {
return props.version["point-in-times"].reduce(
const isKeptBeforeConversion = computed<boolean>(() =>
props.version["point-in-times"].reduce(
(accumulator: boolean, pit: StoredObjectPointInTime) =>
accumulator || "keep-before-conversion" === pit.reason,
false,
);
} else {
return false;
}
});
),
);
const isRestored = computed<boolean>(
() => props.version.version > 0 && null !== props.version["from-restored"],
@@ -94,11 +90,11 @@ const classes = computed<{
<div class="col-12">
<file-icon :type="version.type"></file-icon>
<span
><strong>&nbsp;#{{ version.version + 1 }}&nbsp;</strong></span
><strong>#{{ version.version + 1 }}</strong></span
>
<template
v-if="version.createdBy !== null && version.createdAt !== null"
><strong v-if="version.version == 0">créé par</strong
><strong v-if="version.version == 0">Créé par</strong
><strong v-else>modifié par</strong>
<span class="badge-user"
><UserRenderBoxBadge

View File

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

View File

@@ -23,7 +23,7 @@ License * along with this program. If not, see <http://www.gnu.org/licenses/>.
{{ encore_entry_link_tags("mod_document_action_buttons_group") }}
{% endblock %} {% block content %}
<div class="document-list">
<div class="col-md-10 col-xxl">
<h1>
{{ 'Documents for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}
</h1>

View File

@@ -3,15 +3,27 @@
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
<div class="item-row">
<!-- person document or accompanying course document -->
<div class="item-two-col-grid">
<div class="title">
<div class="item-col" style="width: unset">
{% if document.object.isPending %}
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.object.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
{% elseif document.object.isFailure %}
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
{% endif %}
{% if context == 'person' and accompanyingCourse is defined %}
<div>
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ accompanyingCourse.id }}
</span>&nbsp;
</div>
{% elseif context == 'accompanying-period' and person is defined %}
<div>
<span class="badge bg-primary">
{{ 'Document from person %name%'|trans({ '%name%': document.person|chill_entity_render_string }) }}
</span>&nbsp;
</div>
{% endif %}
<div class="denomination h2">
{{ document.title|chill_print_or_message("No title") }}
</div>
@@ -20,39 +32,25 @@
{{ mm.mimeIcon(document.object.type) }}
</div>
{% endif %}
{% if document.category %}
<div>
<p>{{ document.category.name|localize_translatable_string }}</p>
</div>
{% endif %}
{% if document.object.hasTemplate %}
<div>
<p>{{ document.object.template.name|localize_translatable_string }}</p>
</div>
{% endif %}
</div>
<div class="item-col">
<div class="container">
{% if document.date is not null %}
<div class="aside">
<div class="dates row text-end">
<span>{{ document.date|format_date('short') }}</span>
</div>
{% if context == 'person' and accompanyingCourse is defined %}
<div class="text-end">
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ accompanyingCourse.id }}
</span>&nbsp;
</div>
{% elseif context == 'accompanying-period' and person is defined %}
<div class="text-end">
<span class="badge bg-primary">
{{ document.person|chill_entity_render_string }}
</span>&nbsp;
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
{% if document.description is not empty %}
<div class="item-row">

View File

@@ -62,15 +62,7 @@ final readonly class RemoveOldVersionMessageHandler implements MessageHandlerInt
$storedObject = $storedObjectVersion->getStoredObject();
if ($this->storedObjectManager->exists($storedObjectVersion)) {
$this->storedObjectManager->delete($storedObjectVersion);
} else {
$this->logger->notice(
self::LOG_PREFIX.'Stored object version does not exists any more.',
['storedObjectVersionName' => $storedObjectVersion->getFilename()],
);
}
// to ensure an immediate deletion
$this->entityManager->remove($storedObjectVersion);

View File

@@ -44,7 +44,6 @@ class RemoveOldVersionMessageHandlerTest extends TestCase
$entityManager->expects($this->once())->method('clear');
$storedObjectManager = $this->createMock(StoredObjectManagerInterface::class);
$storedObjectManager->expects($this->once())->method('exists')->willReturn(true);
$storedObjectManager->expects($this->once())->method('delete')->with($this->identicalTo($version));
$handler = new RemoveOldVersionMessageHandler($storedObjectVersionRepository, new NullLogger(), $entityManager, $storedObjectManager, new MockClock());
@@ -52,29 +51,6 @@ class RemoveOldVersionMessageHandlerTest extends TestCase
$handler(new RemoveOldVersionMessage(1));
}
public function testInvokeForVersionNotExisting(): void
{
$object = new StoredObject();
$version = $object->registerVersion();
$storedObjectVersionRepository = $this->createMock(StoredObjectVersionRepository::class);
$storedObjectVersionRepository->expects($this->once())->method('find')
->with($this->identicalTo(1))
->willReturn($version);
$entityManager = $this->createMock(EntityManagerInterface::class);
$entityManager->expects($this->once())->method('remove')->with($this->identicalTo($version));
$entityManager->expects($this->once())->method('flush');
$entityManager->expects($this->once())->method('clear');
$storedObjectManager = $this->createMock(StoredObjectManagerInterface::class);
$storedObjectManager->expects($this->once())->method('exists')->willReturn(false);
$storedObjectManager->expects($this->never())->method('delete')->with($this->identicalTo($version));
$handler = new RemoveOldVersionMessageHandler($storedObjectVersionRepository, new NullLogger(), $entityManager, $storedObjectManager, new MockClock());
$handler(new RemoveOldVersionMessage(1));
}
public function testInvokeWithStoredObjectToDelete(): void
{
$object = new StoredObject();
@@ -147,6 +123,6 @@ class DummyStoredObjectManager implements StoredObjectManagerInterface
public function exists(StoredObject|StoredObjectVersion $document): bool
{
return true;
throw new \RuntimeException();
}
}

View File

@@ -99,30 +99,3 @@ CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE: Modifier un document
entity_display_title:
Document (n°%doc%): "Document (n°%doc%)"
Doc for evaluation (n°%eval%): Document de l'évaluation n°%eval%
# SIGNATURES
signatures:
yes: Oui
are_you_sure: Êtes-vous sûr·e?
you_are_going_to_sign: Vous allez signer le document
signature_confirmation: Confirmation de la signature
sign: Signer
choose_another_signature: Choisir une autre zone
cancel: Annuler
last_sign_zone: Zone de signature précédente
next_sign_zone: Zone de signature suivante
add_sign_zone: Ajouter une zone de signature
click_on_document: Cliquer sur le document
last_zone: Zone précédente
next_zone: Zone suivante
add_zone: Ajouter une zone
another_zone: Autre zone
electronic_signature_in_progress: Signature électronique en cours...
loading: Chargement...
remove_sign_zone: Enlever la zone
return: Retour
see_all_pages: Voir toutes les pages
all_pages: Toutes les pages
go_to_signature_unique: Aller à la zone de signature

View File

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

View File

@@ -63,6 +63,7 @@ abstract class AbstractCRUDController extends AbstractController
parent::getSubscribedServices(),
[
'chill_main.paginator_factory' => PaginatorFactory::class,
ManagerRegistry::class => ManagerRegistry::class,
'translator' => TranslatorInterface::class,
AuthorizationHelper::class => AuthorizationHelper::class,
EventDispatcherInterface::class => EventDispatcherInterface::class,
@@ -212,7 +213,7 @@ abstract class AbstractCRUDController extends AbstractController
protected function getManagerRegistry(): ManagerRegistry
{
return $this->container->get('doctrine');
return $this->container->get(ManagerRegistry::class);
}
/**
@@ -225,7 +226,7 @@ abstract class AbstractCRUDController extends AbstractController
protected function getValidator(): ValidatorInterface
{
return $this->container->get('validator');
return $this->get('validator');
}
/**

View File

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

View File

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

View File

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

View File

@@ -1,39 +0,0 @@
.comment-container {
margin-top: 1.5rem;
}
.toggle-button {
background-color: white;
font-size: .8rem;
text-decoration: none;
position: absolute;
bottom: -10px;
left: 20px;
padding: 2px 6px;
cursor: pointer;
z-index: 10;
transition: left 0.1s ease-in-out;
}
.rich-editor-active .toggle-button {
bottom: 0;
}
.editor-wrapper textarea {
resize: vertical;
min-height: 100px;
}
.editor-container {
position: relative;
display: flex;
flex-direction: column;
}
.editor-wrapper {
position: relative;
}
.hidden-textarea {
display: none;
}

View File

@@ -25,34 +25,7 @@ div.flex-table {
div.item-col:last-child {
display: flex;
}
div.item-two-col-grid {
display: grid;
width: 100%;
justify-content: stretch;
@include media-breakpoint-up(lg) {
grid-template-areas:
"title aside";
grid-template-columns: 1fr minmax(8rem, 1fr);
column-gap: 0.5em;
}
@include media-breakpoint-down(lg) {
grid-template-areas:
"aside"
"title";
}
& > div.title {
grid-area: title;
}
& > div.aside {
grid-area: aside;
}
}
}
}
h2, h3, h4, dl, p {

View File

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

View File

@@ -1,23 +1,14 @@
import App from "../../vuejs/CommentEditor/App.vue"
import { createApp, reactive } from "vue";
import ClassicEditor from "./editor_config";
const ckeditorFields: NodeListOf<HTMLTextAreaElement> =
document.querySelectorAll("[id^='comment-app']");
const globalState = reactive({
isSimple: localStorage.getItem('editorMode') === 'simple'
});
window.addEventListener('storage', () => {
globalState.isSimple = localStorage.getItem('editorMode') === 'simple';
});
document.querySelectorAll("textarea[ckeditor]");
ckeditorFields.forEach((field: HTMLTextAreaElement): void => {
const app = createApp(App,{
fieldName: field.dataset.fieldName,
template: `<app></app>`
ClassicEditor.create(field)
.then((editor) => {
//console.log( 'CkEditor was initialized', editor );
})
.catch((error) => {
console.error(error.stack);
});
app.provide('globalState', globalState)
.component("app", App)
.mount(field);
});
//Fields.push.apply(Fields, document.querySelectorAll('.cf-fields textarea'));

View File

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

View File

@@ -10,10 +10,6 @@ let appsPerInput = new Map();
function loadDynamicPicker(element) {
let apps = element.querySelectorAll('[data-module="pick-dynamic"]');
let suggested;
let as_id;
let submit_on_adding_new_entity;
let label;
apps.forEach(function (el) {
const isMultiple = parseInt(el.dataset.multiple) === 1,
@@ -26,11 +22,11 @@ function loadDynamicPicker(element) {
? JSON.parse(input.value)
: input.value === "[]" || input.value === ""
? null
: [JSON.parse(input.value)],
suggested = JSON.parse(el.dataset.suggested),
as_id = parseInt(el.dataset.asId) === 1,
submit_on_adding_new_entity =
parseInt(el.dataset.submitOnAddingNewEntity) === 1,
: [JSON.parse(input.value)];
(suggested = JSON.parse(el.dataset.suggested)),
(as_id = parseInt(el.dataset.asId) === 1),
(submit_on_adding_new_entity =
parseInt(el.dataset.submitOnAddingNewEntity) === 1);
label = el.dataset.label;
if (!isMultiple) {
@@ -177,7 +173,7 @@ document.addEventListener("pick-entity-type-action", function (e) {
}
});
document.addEventListener("DOMContentLoaded", function () {
document.addEventListener("DOMContentLoaded", function (e) {
loadDynamicPicker(document);
});

View File

@@ -0,0 +1,45 @@
import { createApp } from "vue";
import OpenWopiLink from "ChillMainAssets/vuejs/_components/OpenWopiLink";
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
const i18n = _createI18n({});
//TODO move to chillDocStore or ChillWopi
/*
tags to load module:
<span data-module="wopi-link"
data-wopi-url="{{ path('chill_wopi_file_edit', {'fileId': document.uuid}) }}"
data-doc-type="{{ document.type|e('html_attr') }}"
data-options="{{ options|json_encode }}"
></span>
*/
window.addEventListener("DOMContentLoaded", function (e) {
document
.querySelectorAll('span[data-module="wopi-link"]')
.forEach(function (el) {
createApp({
template:
'<open-wopi-link :wopiUrl="wopiUrl" :type="type" :options="options"></open-wopi-link>',
components: {
OpenWopiLink,
},
data() {
return {
wopiUrl: el.dataset.wopiUrl,
type: el.dataset.docType,
options:
el.dataset.options !== "null"
? JSON.parse(el.dataset.options)
: {},
};
},
})
.use(i18n)
.mount(el);
});
});

View File

@@ -203,3 +203,5 @@ export interface WorkflowAttachment {
updatedBy: User | null;
genericDoc: null | GenericDoc;
}
export type PrivateCommentEmbeddable = any;

View File

@@ -1,36 +0,0 @@
<template>
<div>
<div>
<comment-editor
:isSimple="globalState.isSimple"
:fieldName="fieldName"
@toggle="toggleEditorMode"
></comment-editor>
</div>
</div>
</template>
<script>
import { defineComponent, inject } from 'vue';
import CommentEditor from "../CommentEditor/component/CommentEditor.vue";
export default defineComponent({
name: "App",
components: { CommentEditor },
props: {
fieldName: String
},
setup() {
const globalState = inject('globalState');
const toggleEditorMode = () => {
globalState.isSimple = !globalState.isSimple;
localStorage.setItem('editorMode', globalState.isSimple ? 'simple' : 'rich');
};
return {
globalState,
toggleEditorMode,
};
}
});
</script>

View File

@@ -1,58 +0,0 @@
<template>
<div :class="{'editor-container': true, 'rich-editor-active': !isSimple}">
<div v-if="!isSimple" class="editor-wrapper">
<ckeditor
:name="fieldName"
:editor="classicEditor"
:config="editorConfig"
v-model.lazy="content"
tag-name="textarea"
/>
</div>
<div v-else class="editor-wrapper">
<textarea
v-model.lazy="content"
:name="fieldName"
class="form-control"
></textarea>
</div>
<a @click="toggleSimpleEditor" class="toggle-button">{{ isSimple ? "rich" : "simple" }}</a>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed, toRefs } from 'vue';
import { Ckeditor } from "@ckeditor/ckeditor5-vue";
import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config";
import { ClassicEditor } from "ckeditor5";
export default defineComponent({
name: "CommentEditor",
components: {
ckeditor: Ckeditor,
},
props: {
type: String,
isSimple: Boolean,
fieldName: String,
},
setup(props, { emit }) {
const { isSimple } = toRefs(props);
const content = ref("");
const classicEditor = ClassicEditor;
const editorConfig = classicEditorConfig;
const toggleSimpleEditor = () => {
emit("toggle");
};
return {
isSimple,
content,
classicEditor,
editorConfig,
toggleSimpleEditor,
};
}
});
</script>

View File

@@ -1,14 +0,0 @@
import {personMessages} from "ChillPersonAssets/vuejs/_js/i18n";
import {calendarUserSelectorMessages} from "ChillCalendarAssets/vuejs/_components/CalendarUserSelector/js/i18n";
import {activityMessages} from "ChillActivityAssets/vuejs/Activity/i18n";
const appMessages = {
fr: {
mode: {
simple: "Editeur simple",
rich: "Editeur riche"
}
},
};
export { appMessages };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,253 @@
<template>
<a
v-if="isOpenDocument"
class="btn"
:class="[
isChangeIcon ? 'change-icon' : '',
isChangeClass ? options.changeClass : 'btn-wopilink',
]"
@click="openModal"
>
<i v-if="isChangeIcon" class="fa me-2" :class="options.changeIcon" />
<span v-if="!noText">
{{ $t("online_edit_document") }}
</span>
</a>
<teleport to="body">
<div class="wopi-frame" v-if="isOpenDocument">
<modal
v-if="modal.showModal"
:modal-dialog-class="modal.modalDialogClass"
:hide-footer="true"
@close="modal.showModal = false"
>
<template #header>
<img class="logo" :src="logo" height="45" />
<span class="ms-auto me-3">
<span v-if="options.title">{{ options.title }}</span>
</span>
<!--
<a class="btn btn-outline-light">
<i class="fa fa-save fa-fw"></i>
{{ $t('save_and_quit') }}
</a>
-->
</template>
<template #body>
<div v-if="loading" class="loading">
<i
class="fa fa-circle-o-notch fa-spin fa-3x"
:title="$t('loading')"
/>
</div>
<iframe :src="this.wopiUrl" @load="loaded" />
</template>
</modal>
</div>
<div v-else>
<modal
v-if="modal.showModal"
modal-dialog-class="modal-sm"
@close="modal.showModal = false"
>
<template #header>
<h3>{{ $t("invalid_title") }}</h3>
</template>
<template #body>
<div class="alert alert-warning">
{{ $t("invalid_message") }}
</div>
</template>
</modal>
</div>
</teleport>
</template>
<script>
import Modal from "ChillMainAssets/vuejs/_components/Modal";
import logo from "ChillMainAssets/chill/img/logo-chill-sans-slogan_white.png";
export default {
name: "OpenWopiLink",
components: {
Modal,
},
props: {
wopiUrl: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
options: {
type: Object,
required: false,
},
},
data() {
return {
modal: {
showModal: false,
modalDialogClass: "modal-fullscreen", //modal-dialog-scrollable
},
logo: logo,
loading: false,
mime: [
// TODO temporary hardcoded. to be replaced by twig extension or a collabora server query
"application/clarisworks",
"application/coreldraw",
"application/macwriteii",
"application/msword",
"application/pdf",
"application/vnd.lotus-1-2-3",
"application/vnd.ms-excel",
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
"application/vnd.ms-excel.sheet.macroEnabled.12",
"application/vnd.ms-excel.template.macroEnabled.12",
"application/vnd.ms-powerpoint",
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
"application/vnd.ms-powerpoint.template.macroEnabled.12",
"application/vnd.ms-visio.drawing",
"application/vnd.ms-word.document.macroEnabled.12",
"application/vnd.ms-word.template.macroEnabled.12",
"application/vnd.ms-works",
"application/vnd.oasis.opendocument.chart",
"application/vnd.oasis.opendocument.formula",
"application/vnd.oasis.opendocument.graphics",
"application/vnd.oasis.opendocument.graphics-flat-xml",
"application/vnd.oasis.opendocument.graphics-template",
"application/vnd.oasis.opendocument.presentation",
"application/vnd.oasis.opendocument.presentation-flat-xml",
"application/vnd.oasis.opendocument.presentation-template",
"application/vnd.oasis.opendocument.spreadsheet",
"application/vnd.oasis.opendocument.spreadsheet-flat-xml",
"application/vnd.oasis.opendocument.spreadsheet-template",
"application/vnd.oasis.opendocument.text",
"application/vnd.oasis.opendocument.text-flat-xml",
"application/vnd.oasis.opendocument.text-master",
"application/vnd.oasis.opendocument.text-master-template",
"application/vnd.oasis.opendocument.text-template",
"application/vnd.oasis.opendocument.text-web",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
"application/vnd.openxmlformats-officedocument.presentationml.template",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
"application/vnd.sun.xml.calc",
"application/vnd.sun.xml.calc.template",
"application/vnd.sun.xml.chart",
"application/vnd.sun.xml.draw",
"application/vnd.sun.xml.draw.template",
"application/vnd.sun.xml.impress",
"application/vnd.sun.xml.impress.template",
"application/vnd.sun.xml.math",
"application/vnd.sun.xml.writer",
"application/vnd.sun.xml.writer.global",
"application/vnd.sun.xml.writer.template",
"application/vnd.visio",
"application/vnd.visio2013",
"application/vnd.wordperfect",
"application/x-abiword",
"application/x-aportisdoc",
"application/x-dbase",
"application/x-dif-document",
"application/x-fictionbook+xml",
"application/x-gnumeric",
"application/x-hwp",
"application/x-iwork-keynote-sffkey",
"application/x-iwork-numbers-sffnumbers",
"application/x-iwork-pages-sffpages",
"application/x-mspublisher",
"application/x-mswrite",
"application/x-pagemaker",
"application/x-sony-bbeb",
"application/x-t602",
],
};
},
computed: {
isOpenDocument() {
if (this.mime.indexOf(this.type) !== -1) {
return true;
}
return false;
},
noText() {
if (typeof this.options.noText !== "undefined") {
return this.options.noText === true;
}
return false;
},
isChangeIcon() {
if (typeof this.options.changeIcon !== "undefined") {
return !(
this.options.changeIcon === null ||
this.options.changeIcon === ""
);
}
return false;
},
isChangeClass() {
if (typeof this.options.changeClass !== "undefined") {
return !(
this.options.changeClass === null ||
this.options.changeClass === ""
);
}
return false;
},
},
methods: {
openModal() {
this.loading = true;
this.modal.showModal = true;
},
loaded() {
this.loading = false;
},
},
i18n: {
messages: {
fr: {
online_edit_document: "Éditer en ligne",
save_and_quit: "Enregistrer et quitter",
loading: "Chargement de l'éditeur en ligne",
invalid_title: "Format incompatible",
invalid_message:
"Désolé, ce format de document n'est pas éditable en ligne.",
},
},
},
};
</script>
<style lang="scss">
div.wopi-frame {
div.modal-header {
border-bottom: 0;
background-color: var(--bs-primary);
color: white;
}
div.modal-body {
padding: 0;
overflow-y: unset !important;
iframe {
height: 100%;
width: 100%;
}
div.loading {
position: absolute;
color: var(--bs-chill-gray);
top: calc(50% - 30px);
left: calc(50% - 30px);
}
}
}
</style>

View File

@@ -54,11 +54,6 @@ const messages = {
residential_address: "Adresse de résidence",
located_at: "réside chez",
},
comment: {
label: "Commentaire",
editor_simple: "Simple",
editor_rich: "Riche"
}
},
};

View File

@@ -136,59 +136,6 @@
</div>
</div>
<h2>Fix the title in the flex table</h2>
<p>This will fix the layout of the row, with a "title" element, and an aside element. Using <code>css grid</code>, this is quite safe and won't overflow</p>
<xmp>
<div class="flex-table">
<div class="item-bloc">
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">This is my title</div>
<div class="aside">Aside value</div>
</div>
</div>
</div>
<div class="item-bloc">
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">
<div><h3>This is my title, which can be very long and take a lot of place. But it is wrapped successfully, and won't disturb the placement of the aside block</h3></div>
<div>This is a second line</div>
</div>
<div class="aside">Aside value</div>
</div>
</div>
</div>
</div>
</xmp>
<p>will render:</p>
<div class="flex-table">
<div class="item-bloc">
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">This is my title</div>
<div class="aside">Aside value</div>
</div>
</div>
</div>
<div class="item-bloc">
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">
<div><h3>This is my title, which can be very long and take a lot of place. But it is wrapped successfully, and won't disturb the placement of the aside block</h3></div>
<div>This is a second line</div>
</div>
<div class="aside">Aside value</div>
</div>
</div>
</div>
</div>
<h2>Wrap-list</h2>
<p>Une liste inline qui s'aligne, puis glisse sous son titre.</p>
<div class="wrap-list debug">
@@ -445,12 +392,4 @@ Toutes les classes btn-* de bootstrap sont fonctionnelles
</div>
</div>
<div class="row">
<h1>Badges</h1>
<span class="badge-accompanying-work-type-simple">Action d'accompagnement</span>
<span class="badge-activity-type-simple">Type d'échange</span>
<span class="badge-calendar-simple">Rendez-vous</span>
</div>
{% endblock %}

View File

@@ -214,9 +214,7 @@
{% block private_comment_widget %}
{% for entry in form %}
<div id="comment-app-{{ form.vars.id }}" data-field-name="{{ form.vars.full_name }}">
{{ form_widget(entry, { attr: { ckeditor: 'true' } }) }}
</div>
{{ form_widget(entry) }}
{% endfor %}
{% endblock %}
@@ -226,9 +224,7 @@
{% block comment_widget %}
{% for entry in form %}
<div id="comment-app-{{ form.vars.id }}" data-field-name="{{ form.vars.full_name }}">
{{ form_widget(entry, { attr: { ckeditor: 'true' } }) }}
</div>
{{ form_widget(entry) }}
{% endfor %}
{% endblock comment_widget %}

View File

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

View File

@@ -34,7 +34,6 @@ class GenderDocGenNormalizer implements ContextAwareNormalizerInterface, Normali
'id' => $gender->getId(),
'label' => $this->translatableStringHelper->localize($gender->getLabel()),
'genderTranslation' => $gender->getGenderTranslation(),
'type' => 'chill_main_gender',
];
}
}

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