Compare commits

...

66 Commits

Author SHA1 Message Date
73ccdd8b6e Pipeline fixes 2025-03-27 19:04:46 +01:00
4f170bf4cf Move cancel reason into red label 2025-03-27 18:45:34 +01:00
cfbf7b4599 Use translation with capital letter 2025-03-27 18:40:17 +01:00
8941016038 Merge branch 'master' into 285-cancel-calendar 2025-03-27 18:35:20 +01:00
f059edcc8e Add button unique signature zone 2025-03-24 12:49:32 +01:00
fe6949ea26 Update chill bundles to v3.10.3 2025-03-18 13:47:31 +01:00
8a444a12f4 Turn off eslint error ban-ts-comment 2025-03-18 10:57:24 +01:00
8b7b5ceed7 Release v3.10.2 2025-03-17 22:30:17 +01:00
b0e826d05a Revert "Remove unnecessary ts-expect-error"
This reverts commit 7f101ba616.
2025-03-17 22:22:37 +01:00
6fb9c3af3f Release v3.10.1 2025-03-17 21:59:32 +01:00
7f101ba616 Remove unnecessary ts-expect-error 2025-03-17 21:59:16 +01:00
cea82fac10 Merge branch '333-removing-node-deps-to-symfony-ux-translator' into 'master'
Copy Symfony UX Translator module into to chill-bundles

Closes #333

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

Closes #368

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

Closes #362

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

Closes #363

See merge request Chill-Projet/chill-bundles!802
2025-03-04 14:40:48 +00:00
b39997f00a Resolve "Change display of social issues and social actions" 2025-03-04 14:40:48 +00:00
55c7dbf44e Only change sms status upon cancellation if sendSMS is true 2025-03-03 13:25:08 +01:00
8336a93aeb Re-establish canceled tag and strikethrough 2025-03-03 13:21:35 +01:00
caf0407451 Resolve merge conflicts after rebase 2025-03-03 13:14:00 +01:00
e3b666921e Add condition for preparing cancellation message 2025-03-03 13:10:36 +01:00
0306af1a0c template adjustments + add missing translations 2025-03-03 13:10:04 +01:00
309cc64275 work on syncing with remote calendar 2025-03-03 12:18:50 +01:00
129690ca3f Set the sms status to 'canceled' 2025-03-03 12:18:50 +01:00
54d9f7152b Add translation, make reason required, and change template for form 2025-03-03 12:18:14 +01:00
809fda470b Fix form to submit cancel reason 2025-03-03 12:18:14 +01:00
42b3ab3698 Add cancel reason form 2025-03-03 12:18:11 +01:00
be532cb14c Initial setup of controller and templates 2025-03-03 12:17:48 +01:00
4ed3bec115 Pipeline corrections 2025-03-03 12:14:30 +01:00
8f84b1e137 Adjust display of canceled calendar items in the list 2025-03-03 12:14:30 +01:00
88a84ed996 Add a logger notice when calendar is canceled 2025-03-03 12:14:30 +01:00
d87523beb7 Set the sms status to 'canceled' 2025-03-03 12:14:30 +01:00
33edb22b50 Use a different button type 2025-03-03 12:14:30 +01:00
607f1b7c3f Only show cancel button with EDIT permission 2025-03-03 12:14:30 +01:00
57bc61b218 Add translation, make reason required, and change template for form 2025-03-03 12:14:30 +01:00
5efbec9b42 Fix form to submit cancel reason 2025-03-03 12:14:30 +01:00
626a77b040 Add cancel reason form 2025-03-03 12:14:30 +01:00
a0e0d600e6 Initial setup of controller and templates 2025-03-03 12:14:30 +01:00
f1221286f3 work on syncing with remote calendar 2025-01-15 11:22:38 +01:00
ed99d8d60b resolve merge conflict 2025-01-14 13:37:07 +01:00
b2e27700bb Pipeline corrections 2025-01-14 13:34:16 +01:00
63b207a2e7 Adjust display of canceled calendar items in the list 2025-01-14 13:34:16 +01:00
b1c3daf246 Add a logger notice when calendar is canceled 2025-01-14 13:34:16 +01:00
761b0a3ecc Set the sms status to 'canceled' 2025-01-14 13:34:16 +01:00
e8587d5be8 Use a different button type 2025-01-14 13:34:16 +01:00
9d159e79fa Only show cancel button with EDIT permission 2025-01-14 13:34:16 +01:00
c779f53617 Add translation, make reason required, and change template for form 2025-01-14 13:34:16 +01:00
c4560b3d89 Fix form to submit cancel reason 2025-01-14 13:34:16 +01:00
6472c45b42 Add cancel reason form 2025-01-14 13:34:16 +01:00
c307aa5a12 Initial setup of controller and templates 2025-01-14 13:34:16 +01:00
5c85f0478b Set the sms status to 'canceled' 2024-12-18 13:48:56 +01:00
45be73161a Use a different button type 2024-12-18 13:36:20 +01:00
e76e815c49 Only show cancel button with EDIT permission 2024-12-18 13:20:17 +01:00
cce79de711 Add translation, make reason required, and change template for form 2024-12-17 16:04:05 +01:00
d0d3be9bc1 Fix form to submit cancel reason 2024-12-17 09:35:52 +01:00
ace377abba Add cancel reason form 2024-12-16 19:08:42 +01:00
fc3eda7b76 Initial setup of controller and templates 2024-12-12 15:40:51 +01:00
43 changed files with 855 additions and 157 deletions

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

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

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

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

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

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

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

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

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

@@ -68,14 +68,23 @@
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 socialActionsList"
v-for="action in group.actions"
:key="action.id"
:action="action"
:selection="socialActionsSelected"
@updateSelected="updateActionsSelected"
>
</check-social-action>
</div>
</template>
<span
@@ -249,7 +258,23 @@ export default {
</script>
<style lang="scss" scoped>
@import "ChillMainAssets/module/bootstrap/shared";
@import "ChillPersonAssets/chill/scss/mixins";
@import "ChillMainAssets/chill/scss/chill_variables";
span.multiselect__single {
display: none !important;
}
#actionsList {
border-radius: 0.5rem;
padding: 1rem;
margin: 0.5rem;
background-color: whitesmoke;
}
span.badge {
margin-bottom: 0.5rem;
@include badge_social($social-issue-color);
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -96,13 +96,13 @@
</div>
</div>
<FullCalendar :options="calendarOptions" ref="calendarRef">
<template v-slot:eventContent="arg: EventApi">
<template v-slot:eventContent="{ arg }: { arg: { event: EventApi } }">
<span :class="eventClasses(arg.event)">
<b v-if="arg.event.extendedProps.is === 'remote'">{{
arg.event.title
}}</b>
<b v-else-if="arg.event.extendedProps.is === 'range'"
>{{ arg.timeText }} -
>{{ arg.event.startStr }} -
{{ arg.event.extendedProps.locationName }}</b
>
<b v-else-if="arg.event.extendedProps.is === 'local'">{{

View File

@@ -10,6 +10,9 @@
<div class="item-col">
<div class="wrap-header">
<div class="wl-row">
{% if calendar.status == 'canceled' %}
<p class="badge rounded-pill bg-danger">{{ 'chill_calendar.canceled'|trans }}: <span>{{ calendar.cancelReason.name|localize_translatable_string }}</span></p>
{% endif %}
<div class="wl-col title">
<p class="date-label">
{% if context == 'person' and calendar.context == 'accompanying_period' %}
@@ -19,6 +22,9 @@
</span>
</a>
{% endif %}
{% if calendar.status == 'canceled' %}
<del>
{% endif %}
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
{{ calendar.startDate|format_datetime('short', 'short') }}
- {{ calendar.endDate|format_datetime('short', 'short') }}
@@ -26,13 +32,21 @@
{{ calendar.startDate|format_datetime('short', 'short') }}
- {{ calendar.endDate|format_datetime('none', 'short') }}
{% endif %}
</p>
{% if calendar.status == 'canceled' %}
</del>
{% endif %}
{# {% if calendar.status == 'canceled' %}#}
{# <p>#}
{# <span>{{ 'chill_calendar.cancel_reason'|trans }}:</span>#}
{##}
{# </p>#}
{# {% endif %}#}
<div class="duration short-message">
<i class="fa fa-fw fa-hourglass-end"></i>
{{ calendar.duration|date('%H:%I') }}
{% if false == calendar.sendSMS or null == calendar.sendSMS %}
<!-- no sms will be send -->
<!-- no sms will be sent -->
{% else %}
{% if calendar.smsStatus == 'sms_sent' %}
<span title="{{ 'SMS already sent'|trans }}" class="badge bg-info">
@@ -63,8 +77,7 @@
</div>
</div>
{% if calendar.comment.comment is not empty
or calendar.users|length > 0
{% if calendar.users|length > 0
or calendar.thirdParties|length > 0
or calendar.users|length > 0 %}
<div class="item-row details separator">
@@ -103,12 +116,13 @@
</div>
{% endif %}
{% if calendar.documents is not empty %}
<div class="item-row separator column">
<div>
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
</div>
</div>
{% endif %}
{% if calendar.activity is not null %}
<div class="item-row separator">
@@ -151,7 +165,7 @@
<div class="item-row separator">
<ul class="record_actions">
{% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) %}
{% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) and calendar.status is not constant('STATUS_CANCELED', calendar) %}
{% if templates|length == 0 %}
<li>
<a class="btn btn-create"
@@ -191,6 +205,7 @@
or
(calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person))
)
and calendar.status is not constant('STATUS_CANCELED', calendar)
%}
<li>
<a class="btn btn-create"
@@ -213,11 +228,16 @@
class="btn btn-show "></a>
</li>
{% endif %}
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', calendar) %}
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', calendar) and calendar.status is not constant('STATUS_CANCELED', calendar) %}
<li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id }) }}"
class="btn btn-update "></a>
</li>
<li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_cancel', { 'id': calendar.id } ) }}"
class="btn btn-remove">{{ 'Cancel'|trans }}</a>
</li>
{% endif %}
{% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %}
<li>

View File

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

View File

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

View File

@@ -19,6 +19,7 @@ declare(strict_types=1);
namespace Chill\CalendarBundle\Service\ShortMessageNotification;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\CancelReason;
use libphonenumber\PhoneNumberFormat;
use libphonenumber\PhoneNumberUtil;
use Symfony\Component\Notifier\Message\SmsMessage;
@@ -57,7 +58,7 @@ class DefaultShortMessageForCalendarBuilder implements ShortMessageForCalendarBu
$this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164),
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message.txt.twig', ['calendar' => $calendar]),
);
} elseif (Calendar::SMS_CANCEL_PENDING === $calendar->getSmsStatus()) {
} elseif (Calendar::SMS_CANCEL_PENDING === $calendar->getSmsStatus() && !(CancelReason::CANCELEDBY_PERSON === $calendar->getCancelReason()->getCanceledBy())) {
$toUsers[] = new SmsMessage(
$this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164),
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message_canceled.txt.twig', ['calendar' => $calendar]),

View File

@@ -44,6 +44,10 @@ crud:
title_edit: Modifier le motif d'annulation
chill_calendar:
canceled: Annulé
cancel_reason: Raison d'annulation
cancel_calendar_item: Annuler rendez-vous
calendar_canceled: Le rendez-vous a été annulé
Document: Document d'un rendez-vous
form:
The main user is mandatory. He will organize the appointment.: L'utilisateur principal est obligatoire. Il est l'organisateur de l'événement.
@@ -66,8 +70,6 @@ chill_calendar:
Are you sure you want to remove the doc?: Êtes-vous sûr·e de vouloir supprimer le document associé ?
Document outdated: La date et l'heure du rendez-vous ont été modifiés après la création du document
remote_ms_graph:
freebusy_statuses:
busy: Occupé

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,10 +12,6 @@ function loadDynamicPicker(element) {
let apps = element.querySelectorAll('[data-module="pick-dynamic"]');
apps.forEach(function (el) {
let suggested;
let as_id;
let submit_on_adding_new_entity;
let label;
const isMultiple = parseInt(el.dataset.multiple) === 1,
uniqId = el.dataset.uniqid,
input = element.querySelector(
@@ -26,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;
: [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;
parseInt(el.dataset.submitOnAddingNewEntity) === 1,
label = el.dataset.label;
if (!isMultiple) {

View File

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

View File

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

View File

@@ -68,8 +68,8 @@ class AddressReferenceBEFromBestAddress
$csv->setDelimiter(',');
$csv->setHeaderOffset(0);
$stmt = Statement::create()
->process($csv);
$stmt = new Statement();
$stmt = $stmt->process($csv);
foreach ($stmt as $record) {
$this->baseImporter->importAddress(

View File

@@ -55,8 +55,8 @@ class AddressReferenceFromBAN
$csv = Reader::createFromStream($csvDecompressed);
$csv->setDelimiter(';')->setHeaderOffset(0);
$stmt = Statement::create()
->process($csv, [
$stmt = new Statement();
$stmt = $stmt->process($csv, [
'id',
'id_fantoir',
'numero',

View File

@@ -43,8 +43,8 @@ class AddressReferenceFromBano
$csv = Reader::createFromStream($file);
$csv->setDelimiter(',');
$stmt = Statement::create()
->process($csv, [
$stmt = new Statement();
$stmt = $stmt->process($csv, [
'refId',
'streetNumber',
'street',

View File

@@ -54,7 +54,8 @@ class AddressReferenceLU
private function process_address(Reader $csv, ?string $sendAddressReportToEmail = null): void
{
$stmt = Statement::create()->process($csv);
$stmt = new Statement();
$stmt = $stmt->process($csv);
foreach ($stmt as $record) {
$this->addressBaseImporter->importAddress(
$record['id_geoportail'],
@@ -74,7 +75,8 @@ class AddressReferenceLU
private function process_postal_code(Reader $csv): void
{
$stmt = Statement::create()->process($csv);
$stmt = new Statement();
$stmt = $stmt->process($csv);
$arr_postal_codes = [];
foreach ($stmt as $record) {
if (false === \array_key_exists($record['code_postal'], $arr_postal_codes)) {