Merge branch 'implement_eslint' into 'master'

Implement ESLint

See merge request Chill-Projet/chill-bundles!752
This commit is contained in:
LenaertsJ 2024-12-11 10:26:35 +00:00
commit e8962782ed
294 changed files with 28543 additions and 22472 deletions

1986
.eslint-baseline.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ cache:
paths: paths:
- /vendor/ - /vendor/
- .cache - .cache
- node_modules/
# Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service # Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
# See http://docs.gitlab.com/ee/ci/services/README.html for examples. # See http://docs.gitlab.com/ee/ci/services/README.html for examples.
@ -103,6 +104,32 @@ rector_tests:
paths: paths:
- vendor/ - vendor/
lint:
stage: Tests
image: node:20-alpine
before_script:
- apk add --no-cache python3 make g++ py3-setuptools
- export PYTHON="$(which python3)"
- export PATH="./node_modules/.bin:$PATH"
script:
- yarn install --ignore-optional
- npx eslint-baseline "**/*.{js,vue}"
cache:
paths:
- node_modules/
# psalm_tests:
# stage: Tests
# image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82
# script:
# - bin/psalm
# allow_failure: true
# artifacts:
# expire_in: 30 min
# paths:
# - bin
# - tests/app/vendor/
unit_tests: unit_tests:
stage: Tests stage: Tests
image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82

View File

@ -0,0 +1,53 @@
ESLint
======
To improve the quality of our JS and VueJS code, ESLint and eslint-plugin-vue are implemented within the chill-bundles project.
Commands
--------
To run ESLint, you can simply use the ``eslint`` command within the chill-bundles directory.
Interesting options are:
- ``--quiet`` to only get errors and silence the warnings
- ``--fix`` to have ESLint fix what it can, automatically. This will not fix everything.
Rules
-----
We use Vue 3, so the rules can be configured as follows within the ``eslint.config.mjs`` file:
- ``.configs["flat/base"]`` ... Settings and rules to enable correct ESLint parsing.
Configurations for using Vue.js 3.x:
- ``.configs["flat/essential"]`` : Base rules plus rules to prevent errors or unintended behavior.
- ``.configs["flat/strongly-recommended"]`` ... Above, plus rules to considerably improve code readability and/or dev experience.
- ``.configs["flat/recommended"]`` ... Above, plus rules to enforce subjective community defaults to ensure consistency.
Detailed information about which rules each set includes can be found here:
`https://eslint.vuejs.org/rules/ <https://eslint.vuejs.org/rules/>`_
Manual Rule Configuration
-------------------------
We can also manually configure certain rules or override rules that are part of the ruleset specified above.
For example, if we want to turn off a certain rule, we can do so as follows:
.. code-block:: javascript
rules: {
'vue/multi-word-component': 'off'
}
We could also change the severity of a certain rule from 'error' to 'warning', for example.
Within specific ``.js`` or ``.vue`` files, we can also override a certain rule only for that specific file by adding a comment:
.. code-block:: javascript
/* eslint multi-word-component: "off", no-child-content: "error"
--------
Here's a description about why this configuration is necessary. */

View File

@ -31,6 +31,7 @@ As Chill relies on the `symfony <http://symfony.com>`_ framework, reading the fr
Exports <exports.rst> Exports <exports.rst>
Embeddable comments <embeddable-comments.rst> Embeddable comments <embeddable-comments.rst>
Run tests <run-tests.rst> Run tests <run-tests.rst>
ESLint <es-lint.rst>
Useful snippets <useful-snippets.rst> Useful snippets <useful-snippets.rst>
manual/index.rst manual/index.rst
Assets <assets.rst> Assets <assets.rst>

View File

@ -1,19 +1,18 @@
import { ShowHide } from 'ShowHide/show_hide.js'; import { ShowHide } from "ShowHide/show_hide.js";
var var div_accompagnement = document.getElementById("form_accompagnement"),
div_accompagnement = document.getElementById("form_accompagnement"), div_accompagnement_comment = document.getElementById(
div_accompagnement_comment = document.getElementById("form_accompagnement_comment"), "form_accompagnement_comment",
),
div_caf_id = document.getElementById("cafId"), div_caf_id = document.getElementById("cafId"),
div_caf_inscription_date = document.getElementById("cafInscriptionDate"), div_caf_inscription_date = document.getElementById("cafInscriptionDate");
;
// let show/hide the div_accompagnement_comment if the input with value `'autre'` is checked // let show/hide the div_accompagnement_comment if the input with value `'autre'` is checked
new ShowHide({ new ShowHide({
"froms": [div_accompagnement], froms: [div_accompagnement],
"test": function(froms, event) { test: function (froms, event) {
for (let el of froms.values()) { for (let el of froms.values()) {
for (let input of el.querySelectorAll('input').values()) { for (let input of el.querySelectorAll("input").values()) {
if (input.value === 'autre') { if (input.value === "autre") {
return input.checked; return input.checked;
} }
} }
@ -21,7 +20,7 @@ new ShowHide({
return false; return false;
}, },
"container": [div_accompagnement_comment] container: [div_accompagnement_comment],
}); });
// let show the date input only if the the id is filled // let show the date input only if the the id is filled
@ -34,6 +33,5 @@ new ShowHide({
}, },
container: [div_caf_inscription_date], container: [div_caf_inscription_date],
// using this option, we use the event `input` instead of `change` // using this option, we use the event `input` instead of `change`
event_name: 'input' event_name: "input",
}); });

35
eslint.config.mjs Normal file
View File

@ -0,0 +1,35 @@
import eslintPluginVue from "eslint-plugin-vue";
import ts from "typescript-eslint";
import eslintPluginPrettier from "eslint-plugin-prettier";
export default ts.config(
...ts.configs.recommended,
...ts.configs.stylistic,
...eslintPluginVue.configs["flat/essential"],
{
files: ["**/*.vue"],
languageOptions: {
parserOptions: {
parser: "@typescript-eslint/parser",
},
},
},
{
ignores: [
"**/vendor/*",
"**/import-png.d.ts",
"**/chill.webpack.config.js",
],
},
{
plugins: {
prettier: eslintPluginPrettier,
},
rules: {
"prettier/prettier": "error",
// override/add rules settings here, such as:
"vue/multi-word-component-names": "off",
"@typescript-eslint/no-require-imports": "off",
},
},
);

View File

@ -13,26 +13,36 @@
"@ckeditor/ckeditor5-markdown-gfm": "^41.4.2", "@ckeditor/ckeditor5-markdown-gfm": "^41.4.2",
"@ckeditor/ckeditor5-theme-lark": "^41.4.2", "@ckeditor/ckeditor5-theme-lark": "^41.4.2",
"@ckeditor/ckeditor5-vue": "^5.1.0", "@ckeditor/ckeditor5-vue": "^5.1.0",
"@eslint/js": "^9.14.0",
"@luminateone/eslint-baseline": "^1.0.9",
"@symfony/webpack-encore": "^4.1.0", "@symfony/webpack-encore": "^4.1.0",
"@tsconfig/node14": "^1.0.1", "@tsconfig/node14": "^1.0.1",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
"@types/eslint__js": "^8.42.3",
"@typescript-eslint/parser": "^8.12.2",
"bindings": "^1.5.0", "bindings": "^1.5.0",
"bootstrap": "5.2.3", "bootstrap": "5.2.3",
"chokidar": "^3.5.1", "chokidar": "^3.5.1",
"dompurify": "^3.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", "fork-awesome": "^1.1.7",
"jquery": "^3.6.0", "jquery": "^3.6.0",
"marked": "^12.0.1", "marked": "^12.0.1",
"node-sass": "^8.0.0", "node-sass": "^8.0.0",
"popper.js": "^1.16.1", "popper.js": "^1.16.1",
"postcss-loader": "^7.0.2", "postcss-loader": "^7.0.2",
"prettier": "^3.3.3",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"sass-loader": "^14.0.0", "sass-loader": "^14.0.0",
"select2": "^4.0.13", "select2": "^4.0.13",
"select2-bootstrap-theme": "0.1.0-beta.10", "select2-bootstrap-theme": "0.1.0-beta.10",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"ts-loader": "^9.3.1", "ts-loader": "^9.3.1",
"typescript": "^5.4.5", "typescript": "^5.6.3",
"typescript-eslint": "^8.13.0",
"vue-loader": "^17.0.0", "vue-loader": "^17.0.0",
"webpack": "^5.75.0", "webpack": "^5.75.0",
"webpack-cli": "^5.0.1" "webpack-cli": "^5.0.1"
@ -68,6 +78,7 @@
"scripts": { "scripts": {
"dev-server": "encore dev-server", "dev-server": "encore dev-server",
"dev": "encore dev", "dev": "encore dev",
"prettier": "prettier --write \"**/*.{js,ts,vue}\"",
"watch": "encore dev --watch", "watch": "encore dev --watch",
"build": "encore production --progress" "build": "encore production --progress"
}, },

View File

@ -1 +1 @@
require('./chillactivity.scss'); require("./chillactivity.scss");

View File

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

View File

@ -1,37 +1,38 @@
import { getSocialIssues } from 'ChillPersonAssets/vuejs/AccompanyingCourse/api.js'; import { getSocialIssues } from "ChillPersonAssets/vuejs/AccompanyingCourse/api.js";
import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods'; import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
/* /*
* Load socialActions by socialIssue (id) * Load socialActions by socialIssue (id)
*/ */
const getSocialActionByIssue = (id) => { const getSocialActionByIssue = (id) => {
const url = `/api/1.0/person/social/social-action/by-social-issue/${id}.json`; const url = `/api/1.0/person/social/social-action/by-social-issue/${id}.json`;
return fetch(url) return fetch(url).then((response) => {
.then(response => { if (response.ok) {
if (response.ok) { return response.json(); } return response.json();
throw Error('Error with request resource response'); }
throw Error("Error with request resource response");
}); });
}; };
const getLocations = () => fetchResults('/api/1.0/main/location.json'); const getLocations = () => fetchResults("/api/1.0/main/location.json");
const getLocationTypes = () => fetchResults('/api/1.0/main/location-type.json'); const getLocationTypes = () => fetchResults("/api/1.0/main/location-type.json");
const getUserCurrentLocation = const getUserCurrentLocation = () =>
() => fetch('/api/1.0/main/user-current-location.json') fetch("/api/1.0/main/user-current-location.json").then((response) => {
.then(response => { if (response.ok) {
if (response.ok) { return response.json(); } return response.json();
throw Error('Error with request resource response'); }
throw Error("Error with request resource response");
}); });
/* /*
* Load Location Type by defaultFor * Load Location Type by defaultFor
* @param {string} entity - can be "person" or "thirdparty" * @param {string} entity - can be "person" or "thirdparty"
*/ */
const getLocationTypeByDefaultFor = (entity) => { const getLocationTypeByDefaultFor = (entity) => {
return getLocationTypes().then(results => return getLocationTypes().then(
results.filter(t => t.defaultFor === entity)[0] (results) => results.filter((t) => t.defaultFor === entity)[0],
); );
}; };
@ -45,15 +46,16 @@ const getLocationTypeByDefaultFor = (entity) => {
const postLocation = (body) => { const postLocation = (body) => {
const url = `/api/1.0/main/location.json`; const url = `/api/1.0/main/location.json`;
return fetch(url, { return fetch(url, {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json;charset=utf-8' "Content-Type": "application/json;charset=utf-8",
}, },
body: JSON.stringify(body) body: JSON.stringify(body),
}) }).then((response) => {
.then(response => { if (response.ok) {
if (response.ok) { return response.json(); } return response.json();
throw Error('Error with request resource response'); }
throw Error("Error with request resource response");
}); });
}; };
@ -64,5 +66,5 @@ export {
getLocationTypes, getLocationTypes,
getLocationTypeByDefaultFor, getLocationTypeByDefaultFor,
postLocation, postLocation,
getUserCurrentLocation getUserCurrentLocation,
}; };

View File

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

View File

@ -2,21 +2,20 @@
<li> <li>
<span :title="person.text" @click.prevent="$emit('remove', person)"> <span :title="person.text" @click.prevent="$emit('remove', person)">
<span class="chill_denomination"> <span class="chill_denomination">
<person-text :person="person" :isCut="true"></person-text> <person-text :person="person" :is-cut="true" />
</span> </span>
</span> </span>
</li> </li>
</template> </template>
<script> <script>
import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue'; import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
export default { export default {
name: "PersonBadge", name: "PersonBadge",
props: ['person'], props: ["person"],
components: { components: {
PersonText PersonText,
}, },
// computed: { // computed: {
// textCutted() { // textCutted() {
@ -24,9 +23,8 @@ export default {
// return this.person.text.slice(0,15) + more; // return this.person.text.slice(0,15) + more;
// } // }
// }, // },
emits: ['remove'], emits: ["remove"],
} };
</script> </script>
<style lang="css" scoped> <style lang="css" scoped></style>
</style>

View File

@ -8,10 +8,10 @@
<ul class="list-suggest remove-items"> <ul class="list-suggest remove-items">
<person-badge <person-badge
v-for="person in bloc.persons" v-for="person in bloc.persons"
v-bind:key="person.id" :key="person.id"
v-bind:person="person" :person="person"
@remove="removePerson"> @remove="removePerson"
</person-badge> />
</ul> </ul>
</div> </div>
</div> </div>
@ -19,23 +19,21 @@
</template> </template>
<script> <script>
import PersonBadge from './PersonBadge.vue'; import PersonBadge from "./PersonBadge.vue";
export default { export default {
name: "PersonsBloc", name: "PersonsBloc",
components: { components: {
PersonBadge PersonBadge,
}, },
props: ['bloc', 'setPersonsInBloc', 'blocWidth'], props: ["bloc", "setPersonsInBloc", "blocWidth"],
methods: { methods: {
removePerson(item) { removePerson(item) {
console.log('@@ CLICK remove person: item', item); console.log("@@ CLICK remove person: item", item);
this.$store.dispatch('removePersonInvolved', item); this.$store.dispatch("removePersonInvolved", item);
this.setPersonsInBloc(); this.setPersonsInBloc();
} },
} },
} };
</script> </script>
<style lang="scss"> <style lang="scss"></style>
</style>

View File

@ -22,9 +22,8 @@
group-values="locations" group-values="locations"
group-label="locationGroup" group-label="locationGroup"
v-model="location" v-model="location"
> />
</VueMultiselect> <new-location :available-locations="availableLocations" />
<new-location v-bind:availableLocations="availableLocations"></new-location>
</div> </div>
</div> </div>
</teleport> </teleport>
@ -43,9 +42,8 @@ export default {
}, },
data() { data() {
return { return {
locationClassList: locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
`col-form-label col-sm-4 ${document.querySelector('input#chill_activitybundle_activity_location').getAttribute('required') ? 'required' : ''}`, };
}
}, },
computed: { computed: {
...mapState(["activity", "availableLocations"]), ...mapState(["activity", "availableLocations"]),
@ -61,16 +59,16 @@ export default {
}, },
methods: { methods: {
labelAccompanyingCourseLocation(value) { labelAccompanyingCourseLocation(value) {
return `${value.address.text} (${value.locationType.title.fr})` return `${value.address.text} (${value.locationType.title.fr})`;
}, },
customLabel(value) { customLabel(value) {
return value.locationType return value.locationType
? value.name ? value.name
? value.name === '__AccompanyingCourseLocation__' ? value.name === "__AccompanyingCourseLocation__"
? this.labelAccompanyingCourseLocation(value) ? this.labelAccompanyingCourseLocation(value)
: `${value.name} (${value.locationType.title.fr})` : `${value.name} (${value.locationType.title.fr})`
: value.locationType.title.fr : value.locationType.title.fr
: ''; : "";
}, },
}, },
}; };

View File

@ -3,84 +3,129 @@
<ul class="record_actions"> <ul class="record_actions">
<li> <li>
<a class="btn btn-sm btn-create" @click="openModal"> <a class="btn btn-sm btn-create" @click="openModal">
{{ $t('activity.create_new_location') }} {{ $t("activity.create_new_location") }}
</a> </a>
</li> </li>
</ul> </ul>
<teleport to="body"> <teleport to="body">
<modal v-if="modal.showModal" <modal
:modalDialogClass="modal.modalDialogClass" v-if="modal.showModal"
@close="modal.showModal = false"> :modal-dialog-class="modal.modalDialogClass"
@close="modal.showModal = false"
<template v-slot:header> >
<h3 class="modal-title">{{ $t('activity.create_new_location') }}</h3> <template #header>
<h3 class="modal-title">
{{ $t("activity.create_new_location") }}
</h3>
</template> </template>
<template v-slot:body> <template #body>
<form> <form>
<div class="alert alert-warning" v-if="errors.length"> <div class="alert alert-warning" v-if="errors.length">
<ul> <ul>
<li v-for="(e, i) in errors" :key="i">{{ e }}</li> <li v-for="(e, i) in errors" :key="i">
{{ e }}
</li>
</ul> </ul>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<select class="form-select form-select-lg" id="type" required v-model="selectType"> <select
<option selected disabled value="">{{ $t('activity.choose_location_type') }}</option> class="form-select form-select-lg"
<option v-for="t in locationTypes" :value="t" :key="t.id"> id="type"
required
v-model="selectType"
>
<option selected disabled value="">
{{ $t("activity.choose_location_type") }}
</option>
<option
v-for="t in locationTypes"
:value="t"
:key="t.id"
>
{{ t.title.fr }} {{ t.title.fr }}
</option> </option>
</select> </select>
<label>{{ $t('activity.location_fields.type') }}</label> <label>{{
$t("activity.location_fields.type")
}}</label>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input class="form-control form-control-lg" id="name" v-model="inputName" placeholder /> <input
<label for="name">{{ $t('activity.location_fields.name') }}</label> class="form-control form-control-lg"
id="name"
v-model="inputName"
placeholder
/>
<label for="name">{{
$t("activity.location_fields.name")
}}</label>
</div> </div>
<add-address <add-address
:context="addAddress.context" :context="addAddress.context"
:options="addAddress.options" :options="addAddress.options"
:addressChangedCallback="submitNewAddress" :address-changed-callback="submitNewAddress"
v-if="showAddAddress" v-if="showAddAddress"
ref="addAddress"> ref="addAddress"
</add-address> />
<div class="form-floating mb-3" v-if="showContactData"> <div class="form-floating mb-3" v-if="showContactData">
<input class="form-control form-control-lg" id="phonenumber1" v-model="inputPhonenumber1" placeholder /> <input
<label for="phonenumber1">{{ $t('activity.location_fields.phonenumber1') }}</label> class="form-control form-control-lg"
id="phonenumber1"
v-model="inputPhonenumber1"
placeholder
/>
<label for="phonenumber1">{{
$t("activity.location_fields.phonenumber1")
}}</label>
</div> </div>
<div class="form-floating mb-3" v-if="hasPhonenumber1"> <div class="form-floating mb-3" v-if="hasPhonenumber1">
<input class="form-control form-control-lg" id="phonenumber2" v-model="inputPhonenumber2" placeholder /> <input
<label for="phonenumber2">{{ $t('activity.location_fields.phonenumber2') }}</label> class="form-control form-control-lg"
id="phonenumber2"
v-model="inputPhonenumber2"
placeholder
/>
<label for="phonenumber2">{{
$t("activity.location_fields.phonenumber2")
}}</label>
</div> </div>
<div class="form-floating mb-3" v-if="showContactData"> <div class="form-floating mb-3" v-if="showContactData">
<input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder /> <input
<label for="email">{{ $t('activity.location_fields.email') }}</label> class="form-control form-control-lg"
id="email"
v-model="inputEmail"
placeholder
/>
<label for="email">{{
$t("activity.location_fields.email")
}}</label>
</div> </div>
</form> </form>
</template> </template>
<template v-slot:footer> <template #footer>
<button class="btn btn-save" <button
class="btn btn-save"
@click.prevent="saveNewLocation" @click.prevent="saveNewLocation"
> >
{{ $t('action.save') }} {{ $t("action.save") }}
</button> </button>
</template> </template>
</modal> </modal>
</teleport> </teleport>
</div> </div>
</template> </template>
<script> <script>
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue'; import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue"; import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
import { mapState } from "vuex"; import { mapState } from "vuex";
import { getLocationTypes } from "../../api"; import { getLocationTypes } from "../../api";
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods'; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
export default { export default {
name: "NewLocation", name: "NewLocation",
@ -88,7 +133,7 @@ export default {
Modal, Modal,
AddAddress, AddAddress,
}, },
props: ['availableLocations'], props: ["availableLocations"],
data() { data() {
return { return {
errors: [], errors: [],
@ -103,35 +148,42 @@ export default {
locationTypes: [], locationTypes: [],
modal: { modal: {
showModal: false, showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl" modalDialogClass: "modal-dialog-scrollable modal-xl",
}, },
addAddress: { addAddress: {
options: { options: {
button: { button: {
text: { create: 'activity.create_address', edit: 'activity.edit_address' }, text: {
size: 'btn-sm' create: "activity.create_address",
edit: "activity.edit_address",
},
size: "btn-sm",
},
title: {
create: "activity.create_address",
edit: "activity.edit_address",
}, },
title: { create: 'activity.create_address', edit: 'activity.edit_address' },
}, },
context: { context: {
target: { //name, id target: {
//name, id
}, },
edit: false, edit: false,
addressId: null, addressId: null,
defaults: window.addaddress defaults: window.addaddress,
} },
} },
} };
}, },
computed: { computed: {
...mapState(['activity']), ...mapState(["activity"]),
selectType: { selectType: {
get() { get() {
return this.selected.type; return this.selected.type;
}, },
set(value) { set(value) {
this.selected.type = value; this.selected.type = value;
} },
}, },
inputName: { inputName: {
get() { get() {
@ -139,7 +191,7 @@ export default {
}, },
set(value) { set(value) {
this.selected.name = value; this.selected.name = value;
} },
}, },
inputEmail: { inputEmail: {
get() { get() {
@ -147,7 +199,7 @@ export default {
}, },
set(value) { set(value) {
this.selected.email = value; this.selected.email = value;
} },
}, },
inputPhonenumber1: { inputPhonenumber1: {
get() { get() {
@ -155,7 +207,7 @@ export default {
}, },
set(value) { set(value) {
this.selected.phonenumber1 = value; this.selected.phonenumber1 = value;
} },
}, },
inputPhonenumber2: { inputPhonenumber2: {
get() { get() {
@ -163,15 +215,18 @@ export default {
}, },
set(value) { set(value) {
this.selected.phonenumber2 = value; this.selected.phonenumber2 = value;
} },
}, },
hasPhonenumber1() { hasPhonenumber1() {
return this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== ""; return (
this.selected.phonenumber1 !== null &&
this.selected.phonenumber1 !== ""
);
}, },
showAddAddress() { showAddAddress() {
let cond = false; let cond = false;
if (this.selected.type) { if (this.selected.type) {
if (this.selected.type.addressRequired !== 'never') { if (this.selected.type.addressRequired !== "never") {
cond = true; cond = true;
} }
} }
@ -180,7 +235,7 @@ export default {
showContactData() { showContactData() {
let cond = false; let cond = false;
if (this.selected.type) { if (this.selected.type) {
if (this.selected.type.contactData !== 'never') { if (this.selected.type.contactData !== "never") {
cond = true; cond = true;
} }
} }
@ -195,28 +250,39 @@ export default {
let cond = true; let cond = true;
this.errors = []; this.errors = [];
if (!this.selected.type) { if (!this.selected.type) {
this.errors.push('Type de localisation requis'); this.errors.push("Type de localisation requis");
cond = false; cond = false;
} else { } else {
if (this.selected.type.addressRequired === 'required' && !this.selected.addressId) { if (
this.errors.push('Adresse requise'); this.selected.type.addressRequired === "required" &&
!this.selected.addressId
) {
this.errors.push("Adresse requise");
cond = false; cond = false;
} }
if (this.selected.type.contactData === 'required' && !this.selected.phonenumber1) { if (
this.errors.push('Numéro de téléphone requis'); this.selected.type.contactData === "required" &&
!this.selected.phonenumber1
) {
this.errors.push("Numéro de téléphone requis");
cond = false; cond = false;
} }
if (this.selected.type.contactData === 'required' && !this.selected.email) { if (
this.errors.push('Adresse email requise'); this.selected.type.contactData === "required" &&
!this.selected.email
) {
this.errors.push("Adresse email requise");
cond = false; cond = false;
} }
} }
return cond; return cond;
}, },
getLocationTypesList() { getLocationTypesList() {
getLocationTypes().then(results => { getLocationTypes().then((results) => {
this.locationTypes = results.filter(t => t.availableForUsers === true); this.locationTypes = results.filter(
}) (t) => t.availableForUsers === true,
);
});
}, },
openModal() { openModal() {
this.modal.showModal = true; this.modal.showModal = true;
@ -224,11 +290,11 @@ export default {
saveNewLocation() { saveNewLocation() {
if (this.checkForm()) { if (this.checkForm()) {
let body = { let body = {
type: 'location', type: "location",
name: this.selected.name, name: this.selected.name,
locationType: { locationType: {
id: this.selected.type.id, id: this.selected.type.id,
type: 'location-type' type: "location-type",
}, },
phonenumber1: this.selected.phonenumber1, phonenumber1: this.selected.phonenumber1,
phonenumber2: this.selected.phonenumber2, phonenumber2: this.selected.phonenumber2,
@ -237,36 +303,36 @@ export default {
if (this.selected.addressId) { if (this.selected.addressId) {
body = Object.assign(body, { body = Object.assign(body, {
address: { address: {
id: this.selected.addressId id: this.selected.addressId,
} },
}); });
} }
makeFetch('POST', '/api/1.0/main/location.json', body) makeFetch("POST", "/api/1.0/main/location.json", body)
.then(response => { .then((response) => {
this.$store.dispatch('addAvailableLocationGroup', { this.$store.dispatch("addAvailableLocationGroup", {
locationGroup: 'Localisations nouvellement créées', locationGroup: "Localisations nouvellement créées",
locations: [response] locations: [response],
}); });
this.$store.dispatch('updateLocation', response); this.$store.dispatch("updateLocation", response);
this.modal.showModal = false; this.modal.showModal = false;
}) })
.catch((error) => { .catch((error) => {
if (error.name === 'ValidationException') { if (error.name === "ValidationException") {
for (let v of error.violations) { for (let v of error.violations) {
this.errors.push(v); this.errors.push(v);
} }
} else { } else {
this.errors.push('An error occurred'); this.errors.push("An error occurred");
}
});
} }
})
};
}, },
submitNewAddress(payload) { submitNewAddress(payload) {
this.selected.addressId = payload.addressId; this.selected.addressId = payload.addressId;
this.addAddress.context.addressId = payload.addressId; this.addAddress.context.addressId = payload.addressId;
this.addAddress.context.edit = true; this.addAddress.context.edit = true;
} },
} },
} };
</script> </script>

View File

@ -1,19 +1,19 @@
<template> <template>
<teleport to="#social-issues-acc"> <teleport to="#social-issues-acc">
<div class="mb-3 row"> <div class="mb-3 row">
<div class="col-4"> <div class="col-4">
<label :class="socialIssuesClassList">{{ $t('activity.social_issues') }}</label> <label :class="socialIssuesClassList">{{
$t("activity.social_issues")
}}</label>
</div> </div>
<div class="col-8"> <div class="col-8">
<check-social-issue <check-social-issue
v-for="issue in socialIssuesList" v-for="issue in socialIssuesList"
:key="issue.id" :key="issue.id"
:issue="issue" :issue="issue"
:selection="socialIssuesSelected" :selection="socialIssuesSelected"
@updateSelected="updateIssuesSelected"> @update-selected="updateIssuesSelected"
</check-social-issue> />
<div class="my-3"> <div class="my-3">
<VueMultiselect <VueMultiselect
@ -33,72 +33,81 @@
:loading="issueIsLoading" :loading="issueIsLoading"
:placeholder="$t('activity.choose_other_social_issue')" :placeholder="$t('activity.choose_other_social_issue')"
:options="socialIssuesOther" :options="socialIssuesOther"
@select="addIssueInList"> @select="addIssueInList"
</VueMultiselect> />
</div> </div>
</div> </div>
</div> </div>
<div class="mb-3 row"> <div class="mb-3 row">
<div class="col-4"> <div class="col-4">
<label :class="socialActionsClassList">{{ $t('activity.social_actions') }}</label> <label :class="socialActionsClassList">{{
$t("activity.social_actions")
}}</label>
</div> </div>
<div class="col-8"> <div class="col-8">
<div v-if="actionIsLoading === true"> <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> </div>
<span v-else-if="socialIssuesSelected.length === 0" class="inline-choice chill-no-data-statement mt-3"> <span
{{ $t('activity.select_first_a_social_issue') }} v-else-if="socialIssuesSelected.length === 0"
class="inline-choice chill-no-data-statement mt-3"
>
{{ $t("activity.select_first_a_social_issue") }}
</span> </span>
<template v-else-if="socialActionsList.length > 0"> <template v-else-if="socialActionsList.length > 0">
<div
v-if="
socialIssuesSelected.length ||
socialActionsSelected.length
"
>
<check-social-action <check-social-action
v-if="socialIssuesSelected.length || socialActionsSelected.length"
v-for="action in socialActionsList" v-for="action in socialActionsList"
:key="action.id" :key="action.id"
:action="action" :action="action"
:selection="socialActionsSelected" :selection="socialActionsSelected"
@updateSelected="updateActionsSelected"> @update-selected="updateActionsSelected"
</check-social-action> />
</div>
</template> </template>
<span v-else-if="actionAreLoaded && socialActionsList.length === 0" class="inline-choice chill-no-data-statement mt-3"> <span
{{ $t('activity.social_action_list_empty') }} v-else-if="
actionAreLoaded && socialActionsList.length === 0
"
class="inline-choice chill-no-data-statement mt-3"
>
{{ $t("activity.social_action_list_empty") }}
</span> </span>
</div> </div>
</div> </div>
</teleport> </teleport>
</template> </template>
<script> <script>
import VueMultiselect from 'vue-multiselect'; import VueMultiselect from "vue-multiselect";
import CheckSocialIssue from './SocialIssuesAcc/CheckSocialIssue.vue'; import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
import CheckSocialAction from './SocialIssuesAcc/CheckSocialAction.vue'; import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
import { getSocialIssues, getSocialActionByIssue } from '../api.js'; import { getSocialIssues, getSocialActionByIssue } from "../api.js";
export default { export default {
name: "SocialIssuesAcc", name: "SocialIssuesAcc",
components: { components: {
CheckSocialIssue, CheckSocialIssue,
CheckSocialAction, CheckSocialAction,
VueMultiselect VueMultiselect,
}, },
data() { data() {
return { return {
issueIsLoading: false, issueIsLoading: false,
actionIsLoading: false, actionIsLoading: false,
actionAreLoaded: false, actionAreLoaded: false,
socialIssuesClassList: socialIssuesClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialIssues").getAttribute("required") ? "required" : ""}`,
`col-form-label ${document.querySelector('input#chill_activitybundle_activity_socialIssues').getAttribute('required') ? 'required' : ''}`, socialActionsClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialActions").getAttribute("required") ? "required" : ""}`,
socialActionsClassList: };
`col-form-label ${document.querySelector('input#chill_activitybundle_activity_socialActions').getAttribute('required') ? 'required' : ''}`,
}
}, },
computed: { computed: {
socialIssuesList() { socialIssuesList() {
@ -115,109 +124,115 @@ export default {
}, },
socialActionsSelected() { socialActionsSelected() {
return this.$store.state.activity.socialActions; return this.$store.state.activity.socialActions;
} },
}, },
mounted() { mounted() {
/* Load others issues in multiselect /* Load others issues in multiselect
*/ */
this.issueIsLoading = true; this.issueIsLoading = true;
this.actionAreLoaded = false; this.actionAreLoaded = false;
getSocialIssues().then(response => new Promise((resolve, reject) => { getSocialIssues().then(
this.$store.commit('updateIssuesOther', response.results); (response) =>
new Promise((resolve, reject) => {
this.$store.commit("updateIssuesOther", response.results);
/* 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 => { this.socialIssuesSelected.forEach((issue) => {
if (this.socialIssuesList.filter(i => i.id === issue.id).length !== 1) { if (
this.$store.commit('addIssueInList', issue); this.socialIssuesList.filter(
(i) => i.id === issue.id,
).length !== 1
) {
this.$store.commit("addIssueInList", issue);
} }
}, this); }, this);
/* Remove from multiselect the issues that are not yet in checkbox list /* Remove from multiselect the issues that are not yet in checkbox list
*/ */
this.socialIssuesList.forEach(issue => { this.socialIssuesList.forEach((issue) => {
this.$store.commit('removeIssueInOther', issue); this.$store.commit("removeIssueInOther", issue);
}, this); }, this);
/* Filter issues /* Filter issues
*/ */
this.$store.commit('filterList', '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.socialActionsSelected.forEach((action) => {
this.$store.commit('addActionInList', action); this.$store.commit("addActionInList", action);
}, this); }, this);
/* Filter issues /* Filter issues
*/ */
this.$store.commit('filterList', 'actions'); this.$store.commit("filterList", "actions");
this.issueIsLoading = false; this.issueIsLoading = false;
this.actionAreLoaded = true; this.actionAreLoaded = true;
this.updateActionsList(); this.updateActionsList();
resolve(); resolve();
})); }),
);
}, },
methods: { methods: {
/* When choosing an issue in multiselect, add it in checkboxes (as selected), /* When choosing an issue in multiselect, add it in checkboxes (as selected),
remove it from multiselect, and add socialActions concerned remove it from multiselect, and add socialActions concerned
*/ */
addIssueInList(value) { addIssueInList(value) {
//console.log('addIssueInList', value); //console.log('addIssueInList', value);
this.$store.commit('addIssueInList', value); this.$store.commit("addIssueInList", value);
this.$store.commit('removeIssueInOther', value); this.$store.commit("removeIssueInOther", value);
this.$store.dispatch('addIssueSelected', value); this.$store.dispatch("addIssueSelected", value);
this.updateActionsList(); this.updateActionsList();
}, },
/* Update value for selected issues checkboxes /* Update value for selected issues checkboxes
*/ */
updateIssuesSelected(issues) { updateIssuesSelected(issues) {
//console.log('updateIssuesSelected', issues); //console.log('updateIssuesSelected', issues);
this.$store.dispatch('updateIssuesSelected', issues); this.$store.dispatch("updateIssuesSelected", issues);
this.updateActionsList(); this.updateActionsList();
}, },
/* Update value for selected actions checkboxes /* Update value for selected actions checkboxes
*/ */
updateActionsSelected(actions) { updateActionsSelected(actions) {
//console.log('updateActionsSelected', actions); //console.log('updateActionsSelected', actions);
this.$store.dispatch('updateActionsSelected', actions); this.$store.dispatch("updateActionsSelected", actions);
}, },
/* Add socialActions concerned: after reset, loop on each issue selected /* Add socialActions concerned: after reset, loop on each issue selected
to get social actions concerned to get social actions concerned
*/ */
updateActionsList() { updateActionsList() {
this.resetActionsList(); this.resetActionsList();
this.socialIssuesSelected.forEach(item => { this.socialIssuesSelected.forEach((item) => {
this.actionIsLoading = true; this.actionIsLoading = true;
getSocialActionByIssue(item.id) getSocialActionByIssue(item.id).then(
.then(actions => new Promise((resolve, reject) => { (actions) =>
new Promise((resolve, reject) => {
actions.results.forEach(action => { actions.results.forEach((action) => {
this.$store.commit('addActionInList', action); this.$store.commit("addActionInList", action);
}, this); }, this);
this.$store.commit('filterList', 'actions'); this.$store.commit("filterList", "actions");
this.actionIsLoading = false; this.actionIsLoading = false;
this.actionAreLoaded = true; this.actionAreLoaded = true;
resolve(); resolve();
})); }),
);
}, this); }, this);
}, },
/* Reset socialActions List: flush list and restore selected actions /* Reset socialActions List: flush list and restore selected actions
*/ */
resetActionsList() { resetActionsList() {
this.$store.commit('resetActionsList'); this.$store.commit("resetActionsList");
this.actionAreLoaded = false; this.actionAreLoaded = false;
this.socialActionsSelected.forEach(item => { this.socialActionsSelected.forEach((item) => {
this.$store.commit('addActionInList', item); this.$store.commit("addActionInList", item);
}, this); }, this);
} },
} },
} };
</script> </script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style> <style src="vue-multiselect/dist/vue-multiselect.css"></style>

View File

@ -1,18 +1,17 @@
<template> <template>
<span class="inline-choice"> <span class="inline-choice">
<div class="form-check"> <div class="form-check">
<input
<input class="form-check-input" class="form-check-input"
type="checkbox" type="checkbox"
v-model="selected" v-model="selected"
name="action" name="action"
v-bind:id="action.id" :id="action.id"
v-bind:value="action" :value="action"
/> />
<label class="form-check-label" v-bind:for="action.id"> <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">{{ action.text }}</span>
</label> </label>
</div> </div>
</span> </span>
</template> </template>
@ -20,25 +19,25 @@
<script> <script>
export default { export default {
name: "CheckSocialAction", name: "CheckSocialAction",
props: [ 'action', 'selection' ], props: ["action", "selection"],
emits: [ 'updateSelected' ], emits: ["updateSelected"],
computed: { computed: {
selected: { selected: {
set(value) { set(value) {
this.$emit('updateSelected', value); this.$emit("updateSelected", value);
}, },
get() { get() {
return this.selection; return this.selection;
} },
} },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import 'ChillMainAssets/module/bootstrap/shared'; @import "ChillMainAssets/module/bootstrap/shared";
@import 'ChillPersonAssets/chill/scss/mixins'; @import "ChillPersonAssets/chill/scss/mixins";
@import 'ChillMainAssets/chill/scss/chill_variables'; @import "ChillMainAssets/chill/scss/chill_variables";
span.badge { span.badge {
@include badge_social($social-action-color); @include badge_social($social-action-color);
font-size: 95%; font-size: 95%;

View File

@ -1,18 +1,19 @@
<template> <template>
<span class="inline-choice"> <span class="inline-choice">
<div class="form-check"> <div class="form-check">
<input
<input class="form-check-input" class="form-check-input"
type="checkbox" type="checkbox"
v-model="selected" v-model="selected"
name="issue" name="issue"
v-bind:id="issue.id" :id="issue.id"
v-bind:value="issue" :value="issue"
/> />
<label class="form-check-label" v-bind:for="issue.id"> <label class="form-check-label" :for="issue.id">
<span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span> <span class="badge bg-chill-l-gray text-dark">{{
issue.text
}}</span>
</label> </label>
</div> </div>
</span> </span>
</template> </template>
@ -20,25 +21,25 @@
<script> <script>
export default { export default {
name: "CheckSocialIssue", name: "CheckSocialIssue",
props: [ 'issue', 'selection' ], props: ["issue", "selection"],
emits: [ 'updateSelected' ], emits: ["updateSelected"],
computed: { computed: {
selected: { selected: {
set(value) { set(value) {
this.$emit('updateSelected', value); this.$emit("updateSelected", value);
}, },
get() { get() {
return this.selection; return this.selection;
} },
} },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import 'ChillMainAssets/module/bootstrap/shared'; @import "ChillMainAssets/module/bootstrap/shared";
@import 'ChillPersonAssets/chill/scss/mixins'; @import "ChillPersonAssets/chill/scss/mixins";
@import 'ChillMainAssets/chill/scss/chill_variables'; @import "ChillMainAssets/chill/scss/chill_variables";
span.badge { span.badge {
@include badge_social($social-issue-color); @include badge_social($social-issue-color);
font-size: 95%; font-size: 95%;

View File

@ -1,5 +1,5 @@
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n' import { personMessages } from "ChillPersonAssets/vuejs/_js/i18n";
import { multiSelectMessages } from 'ChillMainAssets/vuejs/_js/i18n' import { multiSelectMessages } from "ChillMainAssets/vuejs/_js/i18n";
const activityMessages = { const activityMessages = {
fr: { fr: {
@ -9,7 +9,8 @@ const activityMessages = {
social_issues: "Problématiques sociales", social_issues: "Problématiques sociales",
choose_other_social_issue: "Ajouter une autre problématique sociale...", choose_other_social_issue: "Ajouter une autre problématique sociale...",
social_actions: "Actions d'accompagnement", social_actions: "Actions d'accompagnement",
select_first_a_social_issue: "Sélectionnez d'abord une problématique sociale", select_first_a_social_issue:
"Sélectionnez d'abord une problématique sociale",
social_action_list_empty: "Aucune action sociale disponible", social_action_list_empty: "Aucune action sociale disponible",
// //
@ -32,14 +33,12 @@ const activityMessages = {
phonenumber2: "Autre téléphone", phonenumber2: "Autre téléphone",
email: "Adresse courriel", email: "Adresse courriel",
}, },
create_address: 'Créer une adresse', create_address: "Créer une adresse",
edit_address: "Modifier l'adresse" edit_address: "Modifier l'adresse",
} },
} },
} };
Object.assign(activityMessages.fr, personMessages.fr, multiSelectMessages.fr); Object.assign(activityMessages.fr, personMessages.fr, multiSelectMessages.fr);
export { export { activityMessages };
activityMessages
};

View File

@ -1,19 +1,19 @@
import { createApp } from 'vue'; import { createApp } from "vue";
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
import { activityMessages } from './i18n' import { activityMessages } from "./i18n";
import store from './store' import store from "./store";
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue'; import PickTemplate from "ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue";
import {fetchTemplates} from 'ChillDocGeneratorAssets/api/pickTemplate.js'; import { fetchTemplates } from "ChillDocGeneratorAssets/api/pickTemplate.js";
import App from './App.vue'; import App from "./App.vue";
const i18n = _createI18n(activityMessages); const i18n = _createI18n(activityMessages);
// app for activity // app for activity
const hasSocialIssues = document.querySelector('#social-issues-acc') !== null; const hasSocialIssues = document.querySelector("#social-issues-acc") !== null;
const hasLocation = document.querySelector('#location') !== null; const hasLocation = document.querySelector("#location") !== null;
const hasPerson = document.querySelector('#add-persons') !== null; const hasPerson = document.querySelector("#add-persons") !== null;
const app = createApp({ const app = createApp({
template: `<app template: `<app
@ -27,20 +27,19 @@ const app = createApp({
hasLocation, hasLocation,
hasPerson, hasPerson,
}; };
} },
}) })
.use(store) .use(store)
.use(i18n) .use(i18n)
.component('app', App) .component("app", App)
.mount('#activity'); .mount("#activity");
// app for picking template // app for picking template
const i18nGendoc = _createI18n({}); const i18nGendoc = _createI18n({});
document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => { document.querySelectorAll("div[data-docgen-template-picker]").forEach((el) => {
fetchTemplates(el.dataset.entityClass).then(templates => { fetchTemplates(el.dataset.entityClass).then((templates) => {
const picker = { const picker = {
template: template:
'<pick-template :templates="this.templates" :preventDefaultMoveToGenerate="true" ' + '<pick-template :templates="this.templates" :preventDefaultMoveToGenerate="true" ' +
@ -52,35 +51,36 @@ document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => {
return { return {
templates: templates, templates: templates,
entityId: el.dataset.entityId, entityId: el.dataset.entityId,
} };
}, },
methods: { methods: {
generateDoc({ event, link, template }) { generateDoc({ event, link, template }) {
console.log('generateDoc'); console.log("generateDoc");
console.log('link', link); console.log("link", link);
console.log('template', template); console.log("template", template);
let hiddenInput = document.querySelector("input[data-template-id]"); let hiddenInput = document.querySelector("input[data-template-id]");
if (hiddenInput === null) { if (hiddenInput === null) {
console.error('hidden input not found'); console.error("hidden input not found");
return; return;
} }
hiddenInput.value = template; hiddenInput.value = template;
let form = document.querySelector('form[name="chill_activitybundle_activity"'); let form = document.querySelector(
'form[name="chill_activitybundle_activity"',
);
if (form === null) { if (form === null) {
console.error('form not found'); console.error("form not found");
return; return;
} }
form.submit(); form.submit();
} },
} },
}; };
createApp(picker).use(i18nGendoc).mount(el); createApp(picker).use(i18nGendoc).mount(el);
}) });
}); });

View File

@ -1,21 +1,21 @@
import 'es6-promise/auto'; import "es6-promise/auto";
import { createStore } from 'vuex'; import { createStore } from "vuex";
import { postLocation } from './api'; import { postLocation } from "./api";
import prepareLocations from './store.locations.js'; import prepareLocations from "./store.locations.js";
const debug = process.env.NODE_ENV !== 'production'; const debug = process.env.NODE_ENV !== "production";
//console.log('window.activity', window.activity); //console.log('window.activity', window.activity);
const addIdToValue = (string, id) => { const addIdToValue = (string, id) => {
let array = string ? string.split(',') : []; let array = string ? string.split(",") : [];
array.push(id.toString()); array.push(id.toString());
let str = array.join(); let str = array.join();
return str; return str;
}; };
const removeIdFromValue = (string, id) => { const removeIdFromValue = (string, id) => {
let array = string.split(','); let array = string.split(",");
array = array.filter(el => el !== id.toString()); array = array.filter((el) => el !== id.toString());
let str = array.join(); let str = array.join();
return str; return str;
}; };
@ -30,7 +30,10 @@ const store = createStore({
}, },
getters: { getters: {
suggestedEntities(state) { suggestedEntities(state) {
if (typeof state.activity.accompanyingPeriod === "undefined" || state.activity.accompanyingPeriod === null) { if (
typeof state.activity.accompanyingPeriod === "undefined" ||
state.activity.accompanyingPeriod === null
) {
return []; return [];
} }
const allEntities = [ const allEntities = [
@ -44,7 +47,7 @@ const store = createStore({
]; ];
return Array.from( return Array.from(
uniqueIds, uniqueIds,
(id) => allEntities.filter((r) => `${r.type}-${r.id}` === id)[0] (id) => allEntities.filter((r) => `${r.type}-${r.id}` === id)[0],
); );
}, },
suggestedPersons(state) { suggestedPersons(state) {
@ -62,7 +65,7 @@ const store = createStore({
} }
const existingPersonIds = state.activity.persons.map((p) => p.id); const existingPersonIds = state.activity.persons.map((p) => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map( const existingThirdPartyIds = state.activity.thirdParties.map(
(p) => p.id (p) => p.id,
); );
return [state.activity.accompanyingPeriod.requestor].filter( return [state.activity.accompanyingPeriod.requestor].filter(
@ -72,7 +75,7 @@ const store = createStore({
state.activity.activityType.personsVisible !== 0) || state.activity.activityType.personsVisible !== 0) ||
(r.type === "thirdparty" && (r.type === "thirdparty" &&
!existingThirdPartyIds.includes(r.id) && !existingThirdPartyIds.includes(r.id) &&
state.activity.activityType.thirdPartiesVisible !== 0) state.activity.activityType.thirdPartiesVisible !== 0),
); );
}, },
suggestedUser(state) { suggestedUser(state) {
@ -80,14 +83,14 @@ const store = createStore({
return state.activity.activityType.usersVisible === 0 return state.activity.activityType.usersVisible === 0
? [] ? []
: [state.activity.accompanyingPeriod.user].filter( : [state.activity.accompanyingPeriod.user].filter(
(u) => u !== null && !existingUserIds.includes(u.id) (u) => u !== null && !existingUserIds.includes(u.id),
); );
}, },
suggestedResources(state) { suggestedResources(state) {
const resources = state.activity.accompanyingPeriod.resources; const resources = state.activity.accompanyingPeriod.resources;
const existingPersonIds = state.activity.persons.map((p) => p.id); const existingPersonIds = state.activity.persons.map((p) => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map( const existingThirdPartyIds = state.activity.thirdParties.map(
(p) => p.id (p) => p.id,
); );
return state.activity.accompanyingPeriod.resources return state.activity.accompanyingPeriod.resources
.map((r) => r.resource) .map((r) => r.resource)
@ -98,11 +101,13 @@ const store = createStore({
state.activity.activityType.personsVisible !== 0) || state.activity.activityType.personsVisible !== 0) ||
(r.type === "thirdparty" && (r.type === "thirdparty" &&
!existingThirdPartyIds.includes(r.id) && !existingThirdPartyIds.includes(r.id) &&
state.activity.activityType.thirdPartiesVisible !== 0) state.activity.activityType.thirdPartiesVisible !== 0),
); );
}, },
socialActionsListSorted(state) { socialActionsListSorted(state) {
return [ ...state.socialActionsList].sort((a, b) => a.ordering - b.ordering); return [...state.socialActionsList].sort(
(a, b) => a.ordering - b.ordering,
);
}, },
}, },
mutations: { mutations: {
@ -126,7 +131,7 @@ const store = createStore({
removeIssueInOther(state, issue) { removeIssueInOther(state, issue) {
//console.log('remove issue other', issue.id); //console.log('remove issue other', issue.id);
state.socialIssuesOther = state.socialIssuesOther.filter( state.socialIssuesOther = state.socialIssuesOther.filter(
(i) => i.id !== issue.id (i) => i.id !== issue.id,
); );
}, },
resetActionsList(state) { resetActionsList(state) {
@ -145,18 +150,15 @@ const store = createStore({
// remove duplicates entries // remove duplicates entries
list = list.filter( list = list.filter(
(value, index) => (value, index) =>
list.findIndex((array) => array.id === value.id) === list.findIndex((array) => array.id === value.id) === index,
index
); );
// alpha sort // alpha sort
list.sort((a, b) => list.sort((a, b) => (a.text > b.text ? 1 : b.text > a.text ? -1 : 0));
a.text > b.text ? 1 : b.text > a.text ? -1 : 0
);
return list; return list;
}; };
if (list === "issues") { if (list === "issues") {
state.activity.accompanyingPeriod.socialIssues = filterList( state.activity.accompanyingPeriod.socialIssues = filterList(
state.activity.accompanyingPeriod.socialIssues state.activity.accompanyingPeriod.socialIssues,
); );
} }
if (list === "actions") { if (list === "actions") {
@ -184,18 +186,17 @@ const store = createStore({
switch (payload.type) { switch (payload.type) {
case "person": case "person":
state.activity.persons = state.activity.persons.filter( state.activity.persons = state.activity.persons.filter(
(person) => person !== payload (person) => person !== payload,
); );
break; break;
case "thirdparty": case "thirdparty":
state.activity.thirdParties = state.activity.thirdParties = state.activity.thirdParties.filter(
state.activity.thirdParties.filter( (thirdparty) => thirdparty !== payload,
(thirdparty) => thirdparty !== payload
); );
break; break;
case "user": case "user":
state.activity.users = state.activity.users.filter( state.activity.users = state.activity.users.filter(
(user) => user !== payload (user) => user !== payload,
); );
break; break;
} }
@ -206,39 +207,33 @@ const store = createStore({
}, },
addAvailableLocationGroup(state, group) { addAvailableLocationGroup(state, group) {
state.availableLocations.push(group); state.availableLocations.push(group);
} },
}, },
actions: { actions: {
addIssueSelected({ commit }, issue) { addIssueSelected({ commit }, issue) {
let aSocialIssues = document.getElementById( let aSocialIssues = document.getElementById(
"chill_activitybundle_activity_socialIssues" "chill_activitybundle_activity_socialIssues",
); );
aSocialIssues.value = addIdToValue(aSocialIssues.value, issue.id); aSocialIssues.value = addIdToValue(aSocialIssues.value, issue.id);
commit("addIssueSelected", issue); commit("addIssueSelected", issue);
}, },
updateIssuesSelected({ commit }, payload) { updateIssuesSelected({ commit }, payload) {
let aSocialIssues = document.getElementById( let aSocialIssues = document.getElementById(
"chill_activitybundle_activity_socialIssues" "chill_activitybundle_activity_socialIssues",
); );
aSocialIssues.value = ""; aSocialIssues.value = "";
payload.forEach((item) => { payload.forEach((item) => {
aSocialIssues.value = addIdToValue( aSocialIssues.value = addIdToValue(aSocialIssues.value, item.id);
aSocialIssues.value,
item.id
);
}); });
commit("updateIssuesSelected", payload); commit("updateIssuesSelected", payload);
}, },
updateActionsSelected({ commit }, payload) { updateActionsSelected({ commit }, payload) {
let aSocialActions = document.getElementById( let aSocialActions = document.getElementById(
"chill_activitybundle_activity_socialActions" "chill_activitybundle_activity_socialActions",
); );
aSocialActions.value = ""; aSocialActions.value = "";
payload.forEach((item) => { payload.forEach((item) => {
aSocialActions.value = addIdToValue( aSocialActions.value = addIdToValue(aSocialActions.value, item.id);
aSocialActions.value,
item.id
);
}); });
commit("updateActionsSelected", payload); commit("updateActionsSelected", payload);
}, },
@ -250,30 +245,24 @@ const store = createStore({
switch (payload.result.type) { switch (payload.result.type) {
case "person": case "person":
let aPersons = document.getElementById( let aPersons = document.getElementById(
"chill_activitybundle_activity_persons" "chill_activitybundle_activity_persons",
);
aPersons.value = addIdToValue(
aPersons.value,
payload.result.id
); );
aPersons.value = addIdToValue(aPersons.value, payload.result.id);
break; break;
case "thirdparty": case "thirdparty":
let aThirdParties = document.getElementById( let aThirdParties = document.getElementById(
"chill_activitybundle_activity_thirdParties" "chill_activitybundle_activity_thirdParties",
); );
aThirdParties.value = addIdToValue( aThirdParties.value = addIdToValue(
aThirdParties.value, aThirdParties.value,
payload.result.id payload.result.id,
); );
break; break;
case "user": case "user":
let aUsers = document.getElementById( let aUsers = document.getElementById(
"chill_activitybundle_activity_users" "chill_activitybundle_activity_users",
);
aUsers.value = addIdToValue(
aUsers.value,
payload.result.id
); );
aUsers.value = addIdToValue(aUsers.value, payload.result.id);
break; break;
} }
commit("addPersonsInvolved", payload); commit("addPersonsInvolved", payload);
@ -283,25 +272,22 @@ const store = createStore({
switch (payload.type) { switch (payload.type) {
case "person": case "person":
let aPersons = document.getElementById( let aPersons = document.getElementById(
"chill_activitybundle_activity_persons" "chill_activitybundle_activity_persons",
);
aPersons.value = removeIdFromValue(
aPersons.value,
payload.id
); );
aPersons.value = removeIdFromValue(aPersons.value, payload.id);
break; break;
case "thirdparty": case "thirdparty":
let aThirdParties = document.getElementById( let aThirdParties = document.getElementById(
"chill_activitybundle_activity_thirdParties" "chill_activitybundle_activity_thirdParties",
); );
aThirdParties.value = removeIdFromValue( aThirdParties.value = removeIdFromValue(
aThirdParties.value, aThirdParties.value,
payload.id payload.id,
); );
break; break;
case "user": case "user":
let aUsers = document.getElementById( let aUsers = document.getElementById(
"chill_activitybundle_activity_users" "chill_activitybundle_activity_users",
); );
aUsers.value = removeIdFromValue(aUsers.value, payload.id); aUsers.value = removeIdFromValue(aUsers.value, payload.id);
break; break;
@ -311,32 +297,30 @@ const store = createStore({
updateLocation({ commit }, value) { updateLocation({ commit }, value) {
console.log("### action: updateLocation", value); console.log("### action: updateLocation", value);
let hiddenLocation = document.getElementById( let hiddenLocation = document.getElementById(
"chill_activitybundle_activity_location" "chill_activitybundle_activity_location",
); );
if (value.onthefly) { if (value.onthefly) {
const body = { const body = {
"type": "location", type: "location",
"name": value.name === '__AccompanyingCourseLocation__' ? null : value.name, name:
"locationType": { value.name === "__AccompanyingCourseLocation__" ? null : value.name,
"id": value.locationType.id, locationType: {
"type": "location-type" id: value.locationType.id,
} type: "location-type",
},
}; };
if (value.address.id) { if (value.address.id) {
Object.assign(body, { Object.assign(body, {
"address": { address: {
"id": value.address.id id: value.address.id,
}, },
}) });
} }
postLocation(body) postLocation(body)
.then( .then((location) => (hiddenLocation.value = location.id))
location => hiddenLocation.value = location.id .catch((err) => {
).catch(
err => {
console.log(err.message); console.log(err.message);
} });
);
} else { } else {
hiddenLocation.value = value.id; hiddenLocation.value = value.id;
} }

View File

@ -1,41 +1,41 @@
import {getLocations, getLocationTypeByDefaultFor, getUserCurrentLocation} from "./api"; import {
getLocations,
getLocationTypeByDefaultFor,
getUserCurrentLocation,
} from "./api";
const makeConcernedPersonsLocation = (locationType, store) => { const makeConcernedPersonsLocation = (locationType, store) => {
let locations = []; let locations = [];
store.getters.suggestedEntities.forEach( store.getters.suggestedEntities.forEach((e) => {
(e) => { if (e.type === "person" && e.current_household_address !== null) {
if (e.type === 'person' && e.current_household_address !== null){
locations.push({ locations.push({
type: 'location', type: "location",
id: -store.getters.suggestedEntities.indexOf(e) * 10, id: -store.getters.suggestedEntities.indexOf(e) * 10,
onthefly: true, onthefly: true,
name: e.text, name: e.text,
address: { address: {
id: e.current_household_address.address_id, id: e.current_household_address.address_id,
}, },
locationType: locationType locationType: locationType,
}); });
} }
} });
)
return locations; return locations;
}; };
const makeConcernedThirdPartiesLocation = (locationType, store) => { const makeConcernedThirdPartiesLocation = (locationType, store) => {
let locations = []; let locations = [];
store.getters.suggestedEntities.forEach( store.getters.suggestedEntities.forEach((e) => {
(e) => { if (e.type === "thirdparty" && e.address !== null) {
if (e.type === 'thirdparty' && e.address !== null){
locations.push({ locations.push({
type: 'location', type: "location",
id: -store.getters.suggestedEntities.indexOf(e) * 10, id: -store.getters.suggestedEntities.indexOf(e) * 10,
onthefly: true, onthefly: true,
name: e.text, name: e.text,
address: { id: e.address.address_id }, address: { id: e.address.address_id },
locationType: locationType locationType: locationType,
}); });
} }
} });
)
return locations; return locations;
}; };
const makeAccompanyingPeriodLocation = (locationType, store) => { const makeAccompanyingPeriodLocation = (locationType, store) => {
@ -44,78 +44,86 @@ const makeAccompanyingPeriodLocation = (locationType, store) => {
} }
const accPeriodLocation = store.state.activity.accompanyingPeriod.location; const accPeriodLocation = store.state.activity.accompanyingPeriod.location;
return { return {
type: 'location', type: "location",
id: -1, id: -1,
onthefly: true, onthefly: true,
name: '__AccompanyingCourseLocation__', name: "__AccompanyingCourseLocation__",
address: { address: {
id: accPeriodLocation.address_id, id: accPeriodLocation.address_id,
text: `${accPeriodLocation.text} - ${accPeriodLocation.postcode.code} ${accPeriodLocation.postcode.name}` text: `${accPeriodLocation.text} - ${accPeriodLocation.postcode.code} ${accPeriodLocation.postcode.name}`,
}, },
locationType: locationType locationType: locationType,
} };
}; };
export default function prepareLocations(store) { export default function prepareLocations(store) {
// find the locations // find the locations
let allLocations = getLocations().then( let allLocations = getLocations().then((results) => {
(results) => { store.commit("addAvailableLocationGroup", {
store.commit('addAvailableLocationGroup', { locationGroup: "Autres localisations",
locationGroup: 'Autres localisations', locations: results,
locations: results });
}); });
}
);
let currentLocation = getUserCurrentLocation().then( let currentLocation = getUserCurrentLocation().then((userCurrentLocation) => {
userCurrentLocation => {
if (null !== userCurrentLocation) { if (null !== userCurrentLocation) {
store.commit('addAvailableLocationGroup', { store.commit("addAvailableLocationGroup", {
locationGroup: 'Ma localisation', locationGroup: "Ma localisation",
locations: [userCurrentLocation] locations: [userCurrentLocation],
}); });
} }
} });
);
let partiesLocations = [], partyPromise; let partiesLocations = [],
['person', 'thirdparty'].forEach(kind => { partyPromise;
["person", "thirdparty"].forEach((kind) => {
partyPromise = getLocationTypeByDefaultFor(kind).then( partyPromise = getLocationTypeByDefaultFor(kind).then(
(kindLocationType) => { (kindLocationType) => {
if (kindLocationType) { if (kindLocationType) {
let concernedKindLocations; let concernedKindLocations;
if (kind === 'person') { if (kind === "person") {
concernedKindLocations = makeConcernedPersonsLocation(kindLocationType, store); concernedKindLocations = makeConcernedPersonsLocation(
kindLocationType,
store,
);
// add location for the parcours into suggestions // add location for the parcours into suggestions
const personLocation = makeAccompanyingPeriodLocation(kindLocationType, store); const personLocation = makeAccompanyingPeriodLocation(
store.commit('addAvailableLocationGroup', { kindLocationType,
locationGroup: 'Localisation du parcours', store,
locations: [personLocation] );
store.commit("addAvailableLocationGroup", {
locationGroup: "Localisation du parcours",
locations: [personLocation],
}); });
} else { } else {
concernedKindLocations = makeConcernedThirdPartiesLocation(kindLocationType, store); concernedKindLocations = makeConcernedThirdPartiesLocation(
kindLocationType,
store,
);
} }
store.commit('addAvailableLocationGroup', { store.commit("addAvailableLocationGroup", {
locationGroup: kind === 'person' ? 'Usagers concernés' : 'Tiers concernés', locationGroup:
kind === "person" ? "Usagers concernés" : "Tiers concernés",
locations: concernedKindLocations, locations: concernedKindLocations,
}); });
} }
} },
); );
partiesLocations.push(partyPromise); partiesLocations.push(partyPromise);
}); });
// when all location are loaded // when all location are loaded
Promise.all([allLocations, currentLocation, ...partiesLocations]).then(() => { Promise.all([allLocations, currentLocation, ...partiesLocations]).then(() => {
console.log('current location in activity', store.state.activity.location); console.log("current location in activity", store.state.activity.location);
console.log('default loation id', window.default_location_id); console.log("default loation id", window.default_location_id);
if (window.default_location_id) { if (window.default_location_id) {
for (let group of store.state.availableLocations) { for (let group of store.state.availableLocations) {
let location = group.locations.find((l) => l.id === window.default_location_id); let location = group.locations.find(
(l) => l.id === window.default_location_id,
);
if (location !== undefined && store.state.activity.location === null) { if (location !== undefined && store.state.activity.location === null) {
store.dispatch('updateLocation', location); store.dispatch("updateLocation", location);
break; break;
} }
} }

View File

@ -1,13 +1,18 @@
// this file loads all assets from the Chill person bundle // this file loads all assets from the Chill person bundle
module.exports = function(encore, entries) module.exports = function (encore, entries) {
{ entries.push(__dirname + "/Resources/public/chill/index.js");
entries.push(__dirname + '/Resources/public/chill/index.js');
encore.addAliases({ encore.addAliases({
ChillActivityAssets: __dirname + '/Resources/public' ChillActivityAssets: __dirname + "/Resources/public",
}); });
encore.addEntry('page_edit_activity', __dirname + '/Resources/public/page/edit_activity/index.scss'); encore.addEntry(
"page_edit_activity",
__dirname + "/Resources/public/page/edit_activity/index.scss",
);
encore.addEntry('vue_activity', __dirname + '/Resources/public/vuejs/Activity/index.js'); encore.addEntry(
"vue_activity",
__dirname + "/Resources/public/vuejs/Activity/index.js",
);
}; };

View File

@ -1 +1 @@
require('./chillbudget.scss'); require("./chillbudget.scss");

View File

@ -1,9 +1,8 @@
// this file loads all assets from the Chill budget bundle // this file loads all assets from the Chill budget bundle
module.exports = function(encore, entries) module.exports = function (encore, entries) {
{
encore.addAliases({ encore.addAliases({
ChillBudgetAssets: __dirname + '/Resources/public' ChillBudgetAssets: __dirname + "/Resources/public",
}); });
encore.addEntry('page_budget', __dirname + '/Resources/public/page/index.js'); encore.addEntry("page_budget", __dirname + "/Resources/public/page/index.js");
}; };

View File

@ -1,2 +1,2 @@
import './scss/badge.scss'; import "./scss/badge.scss";
import './scss/calendar-list.scss'; import "./scss/calendar-list.scss";

View File

@ -1 +1 @@
require('./scss/calendar.scss'); require("./scss/calendar.scss");

View File

@ -1,14 +1,13 @@
import { createApp } from "vue";
import { createApp } from 'vue'; import Answer from "ChillCalendarAssets/vuejs/Invite/Answer";
import Answer from 'ChillCalendarAssets/vuejs/Invite/Answer'; import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
const i18n = _createI18n({}); const i18n = _createI18n({});
document.addEventListener('DOMContentLoaded', function (e) { document.addEventListener("DOMContentLoaded", function (e) {
console.log('dom loaded answer'); console.log("dom loaded answer");
document.querySelectorAll('div[invite-answer]').forEach(function (el) { document.querySelectorAll("div[invite-answer]").forEach(function (el) {
console.log('element found', el); console.log("element found", el);
const app = createApp({ const app = createApp({
components: { components: {
@ -18,14 +17,15 @@ document.addEventListener('DOMContentLoaded', function (e) {
return { return {
status: el.dataset.status, status: el.dataset.status,
calendarId: Number.parseInt(el.dataset.calendarId), calendarId: Number.parseInt(el.dataset.calendarId),
} };
}, },
template: '<answer :calendarId="calendarId" :status="status" @statusChanged="onStatusChanged"></answer>', template:
'<answer :calendarId="calendarId" :status="status" @statusChanged="onStatusChanged"></answer>',
methods: { methods: {
onStatusChanged: function (newStatus) { onStatusChanged: function (newStatus) {
this.$data.status = newStatus; this.$data.status = newStatus;
}, },
} },
}); });
app.use(i18n).mount(el); app.use(i18n).mount(el);

View File

@ -1,5 +1,10 @@
import {EventInput} from '@fullcalendar/core'; import { EventInput } from "@fullcalendar/core";
import {DateTime, Location, User, UserAssociatedInterface} from '../../../ChillMainBundle/Resources/public/types' ; import {
DateTime,
Location,
User,
UserAssociatedInterface,
} from "../../../ChillMainBundle/Resources/public/types";
import { Person } from "../../../ChillPersonBundle/Resources/public/types"; import { Person } from "../../../ChillPersonBundle/Resources/public/types";
export interface CalendarRange { export interface CalendarRange {
@ -22,8 +27,8 @@ export interface CalendarRangeCreate {
} }
export interface CalendarRangeEdit { export interface CalendarRangeEdit {
startDate?: DateTime, startDate?: DateTime;
endDate?: DateTime endDate?: DateTime;
location?: Location; location?: Location;
} }
@ -49,19 +54,23 @@ export interface CalendarRemote {
} }
export type EventInputCalendarRange = EventInput & { export type EventInputCalendarRange = EventInput & {
id: string, id: string;
userId: number, userId: number;
userLabel: string, userLabel: string;
calendarRangeId: number, calendarRangeId: number;
locationId: number, locationId: number;
locationName: string, locationName: string;
start: string, start: string;
end: string, end: string;
is: "range" is: "range";
}; };
export function isEventInputCalendarRange(toBeDetermined: EventInputCalendarRange | EventInput): toBeDetermined is EventInputCalendarRange { export function isEventInputCalendarRange(
return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"; toBeDetermined: EventInputCalendarRange | EventInput,
): toBeDetermined is EventInputCalendarRange {
return (
typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
);
} }
export {}; export {};

View File

@ -1,63 +1,82 @@
<template> <template>
<teleport to="#mainUser"> <teleport to="#mainUser">
<h2 class="chill-red">Utilisateur principal</h2> <h2 class="chill-red">Utilisateur principal</h2>
<div> <div>
<div> <div>
<div v-if="null !== this.$store.getters.getMainUser"> <div v-if="null !== this.$store.getters.getMainUser">
<calendar-active :user="this.$store.getters.getMainUser" ></calendar-active> <calendar-active :user="this.$store.getters.getMainUser" />
</div> </div>
<pick-entity <pick-entity
:multiple="false" :multiple="false"
:types="['user']" :types="['user']"
:uniqid="'main_user_calendar'" :uniqid="'main_user_calendar'"
:picked="null !== this.$store.getters.getMainUser ? [this.$store.getters.getMainUser] : []" :picked="
:removableIfSet="false" null !== this.$store.getters.getMainUser
:displayPicked="false" ? [this.$store.getters.getMainUser]
: []
"
:removable-if-set="false"
:display-picked="false"
:suggested="this.suggestedUsers" :suggested="this.suggestedUsers"
:label="'main_user'" :label="'main_user'"
@addNewEntity="setMainUser" @add-new-entity="setMainUser"
></pick-entity> />
</div> </div>
</div> </div>
</teleport> </teleport>
<concerned-groups></concerned-groups> <concerned-groups />
<teleport to="#schedule"> <teleport to="#schedule">
<div class="row mb-3" v-if="activity.startDate !== null"> <div class="row mb-3" v-if="activity.startDate !== null">
<label class="col-form-label col-sm-4">Date</label> <label class="col-form-label col-sm-4">Date</label>
<div class="col-sm-8"> <div class="col-sm-8">
{{ $d(activity.startDate, 'long') }} - {{ $d(activity.endDate, 'hoursOnly') }} {{ $d(activity.startDate, "long") }} -
<span v-if="activity.calendarRange === null">(Pas de plage de disponibilité sélectionnée)</span> {{ $d(activity.endDate, "hoursOnly") }}
<span v-if="activity.calendarRange === null"
>(Pas de plage de disponibilité sélectionnée)</span
>
<span v-else>(Une plage de disponibilité sélectionnée)</span> <span v-else>(Une plage de disponibilité sélectionnée)</span>
</div> </div>
</div> </div>
</teleport> </teleport>
<location></location> <location />
<teleport to="#fullCalendar"> <teleport to="#fullCalendar">
<div class="calendar-actives"> <div class="calendar-actives">
<template class="" v-for="u in getActiveUsers" :key="u.id"> <template v-for="u in getActiveUsers" :key="u.id">
<calendar-active :user="u" :invite="this.$store.getters.getInviteForUser(u)"></calendar-active> <calendar-active
:user="u"
:invite="this.$store.getters.getInviteForUser(u)"
/>
</template> </template>
</div> </div>
<div class="display-options row justify-content-between" style="margin-top: 1rem;"> <div
class="display-options row justify-content-between"
style="margin-top: 1rem"
>
<div class="col-sm-9 col-xs-12"> <div class="col-sm-9 col-xs-12">
<div class="input-group mb-3"> <div class="input-group mb-3">
<label class="input-group-text" for="slotDuration">Durée des créneaux</label> <label class="input-group-text" for="slotDuration"
<select v-model="slotDuration" id="slotDuration" class="form-select"> >Durée des créneaux</label
>
<select
v-model="slotDuration"
id="slotDuration"
class="form-select"
>
<option value="00:05:00">5 minutes</option> <option value="00:05:00">5 minutes</option>
<option value="00:10:00">10 minutes</option> <option value="00:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option> <option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option> <option value="00:30:00">30 minutes</option>
</select> </select>
<label class="input-group-text" for="slotMinTime">De</label> <label class="input-group-text" for="slotMinTime">De</label>
<select v-model="slotMinTime" id="slotMinTime" class="form-select"> <select
v-model="slotMinTime"
id="slotMinTime"
class="form-select"
>
<option value="00:00:00">0h</option> <option value="00:00:00">0h</option>
<option value="01:00:00">1h</option> <option value="01:00:00">1h</option>
<option value="02:00:00">2h</option> <option value="02:00:00">2h</option>
@ -73,7 +92,11 @@
<option value="12:00:00">12h</option> <option value="12:00:00">12h</option>
</select> </select>
<label class="input-group-text" for="slotMaxTime">À</label> <label class="input-group-text" for="slotMaxTime">À</label>
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select"> <select
v-model="slotMaxTime"
id="slotMaxTime"
class="form-select"
>
<option value="12:00:00">12h</option> <option value="12:00:00">12h</option>
<option value="13:00:00">13h</option> <option value="13:00:00">13h</option>
<option value="14:00:00">14h</option> <option value="14:00:00">14h</option>
@ -94,21 +117,44 @@
<div class="float-end"> <div class="float-end">
<div class="form-check input-group"> <div class="form-check input-group">
<span class="input-group-text"> <span class="input-group-text">
<input id="showHideWE" class="mt-0" type="checkbox" v-model="hideWeekends"> <input
id="showHideWE"
class="mt-0"
type="checkbox"
v-model="hideWeekends"
/>
</span> </span>
<label for="showHideWE" class="form-check-label input-group-text">Week-ends</label> <label
for="showHideWE"
class="form-check-label input-group-text"
>Week-ends</label
>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<FullCalendar ref="fullCalendar" :options="calendarOptions"> <FullCalendar ref="fullCalendar" :options="calendarOptions">
<template v-slot:eventContent='arg'> <template #eventContent="arg">
<span> <span>
<b v-if="arg.event.extendedProps.is === 'remote'">{{ arg.event.title}}</b> <b v-if="arg.event.extendedProps.is === 'remote'">{{
<b v-else-if="arg.event.extendedProps.is === 'range'">{{ arg.timeText }} {{ arg.event.extendedProps.locationName }} <small>{{ arg.event.extendedProps.userLabel }}</small></b> arg.event.title
<b v-else-if="arg.event.extendedProps.is === 'current'">{{ arg.timeText }} {{ $t('current_selected')}} </b> }}</b>
<b v-else-if="arg.event.extendedProps.is === 'local'">{{ arg.event.title}}</b> <b v-else-if="arg.event.extendedProps.is === 'range'"
<b v-else>{{ arg.timeText }} {{ $t('current_selected')}} </b> >{{ arg.timeText }}
{{ arg.event.extendedProps.locationName }}
<small>{{
arg.event.extendedProps.userLabel
}}</small></b
>
<b v-else-if="arg.event.extendedProps.is === 'current'"
>{{ arg.timeText }} {{ $t("current_selected") }}
</b>
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
arg.event.title
}}</b>
<b v-else
>{{ arg.timeText }} {{ $t("current_selected") }}
</b>
</span> </span>
</template> </template>
</FullCalendar> </FullCalendar>
@ -116,16 +162,16 @@
</template> </template>
<script> <script>
import ConcernedGroups from 'ChillActivityAssets/vuejs/Activity/components/ConcernedGroups.vue'; import ConcernedGroups from "ChillActivityAssets/vuejs/Activity/components/ConcernedGroups.vue";
import Location from 'ChillActivityAssets/vuejs/Activity/components/Location.vue'; import Location from "ChillActivityAssets/vuejs/Activity/components/Location.vue";
import frLocale from '@fullcalendar/core/locales/fr'; import frLocale from "@fullcalendar/core/locales/fr";
import FullCalendar from '@fullcalendar/vue3'; import FullCalendar from "@fullcalendar/vue3";
import dayGridPlugin from '@fullcalendar/daygrid'; import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from '@fullcalendar/interaction'; import interactionPlugin from "@fullcalendar/interaction";
import timeGridPlugin from '@fullcalendar/timegrid'; import timeGridPlugin from "@fullcalendar/timegrid";
import listPlugin from '@fullcalendar/list'; import listPlugin from "@fullcalendar/list";
import CalendarActive from './Components/CalendarActive'; import CalendarActive from "./Components/CalendarActive";
import PickEntity from 'ChillMainAssets/vuejs/PickEntity/PickEntity.vue'; import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
import { mapGetters, mapState } from "vuex"; import { mapGetters, mapState } from "vuex";
export default { export default {
@ -141,24 +187,30 @@ export default {
return { return {
errorMsg: [], errorMsg: [],
showMyCalendar: false, showMyCalendar: false,
slotDuration: '00:05:00', slotDuration: "00:05:00",
slotMinTime: '09:00:00', slotMinTime: "09:00:00",
slotMaxTime: '18:00:00', slotMaxTime: "18:00:00",
hideWeekEnds: true, hideWeekEnds: true,
previousUser: [], previousUser: [],
} };
}, },
computed: { computed: {
...mapGetters(['getMainUser']), ...mapGetters(["getMainUser"]),
...mapState(['activity']), ...mapState(["activity"]),
events() { events() {
return this.$store.getters.getEventSources; return this.$store.getters.getEventSources;
}, },
calendarOptions() { calendarOptions() {
return { return {
locale: frLocale, locale: frLocale,
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin, dayGridPlugin, listPlugin], plugins: [
initialView: 'timeGridWeek', dayGridPlugin,
interactionPlugin,
timeGridPlugin,
dayGridPlugin,
listPlugin,
],
initialView: "timeGridWeek",
initialDate: this.$store.getters.getInitialDate, initialDate: this.$store.getters.getInitialDate,
eventSources: this.events, eventSources: this.events,
selectable: true, selectable: true,
@ -173,9 +225,9 @@ export default {
editable: true, editable: true,
weekends: !this.hideWeekEnds, weekends: !this.hideWeekEnds,
headerToolbar: { headerToolbar: {
left: 'prev,next today', left: "prev,next today",
center: 'title', center: "title",
right: 'timeGridWeek,timeGridDay,listWeek', right: "timeGridWeek,timeGridDay,listWeek",
}, },
views: { views: {
timeGrid: { timeGrid: {
@ -195,9 +247,9 @@ export default {
suggestedUsers() { suggestedUsers() {
const suggested = []; const suggested = [];
this.$data.previousUser.forEach(u => { this.$data.previousUser.forEach((u) => {
if (u.id !== this.$store.getters.getMainUser.id) { if (u.id !== this.$store.getters.getMainUser.id) {
suggested.push(u) suggested.push(u);
} }
}); });
@ -207,81 +259,120 @@ export default {
methods: { methods: {
setMainUser({ entity }) { setMainUser({ entity }) {
const user = entity; const user = entity;
console.log('setMainUser APP', entity); console.log("setMainUser APP", entity);
if (user.id !== this.$store.getters.getMainUser && ( if (
this.$store.state.activity.calendarRange !== null user.id !== this.$store.getters.getMainUser &&
|| this.$store.state.activity.startDate !== null (this.$store.state.activity.calendarRange !== null ||
|| this.$store.state.activity.endDate !== null this.$store.state.activity.startDate !== null ||
this.$store.state.activity.endDate !== null)
) {
if (
!window.confirm(
this.$t("change_main_user_will_reset_event_data"),
) )
) { ) {
if (!window.confirm(this.$t('change_main_user_will_reset_event_data'))) {
return; return;
} }
} }
// add the previous user, if any, in the previous user list (in use for suggestion) // add the previous user, if any, in the previous user list (in use for suggestion)
if (null !== this.$store.getters.getMainUser) { if (null !== this.$store.getters.getMainUser) {
const suggestedUids = new Set(this.$data.previousUser.map(u => u.id)); const suggestedUids = new Set(
this.$data.previousUser.map((u) => u.id),
);
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) { if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
this.$data.previousUser.push(this.$store.getters.getMainUser); this.$data.previousUser.push(
this.$store.getters.getMainUser,
);
} }
} }
this.$store.dispatch('setMainUser', user); this.$store.dispatch("setMainUser", user);
this.$store.commit('showUserOnCalendar', {user, ranges: true, remotes: true}); this.$store.commit("showUserOnCalendar", {
user,
ranges: true,
remotes: true,
});
}, },
removeMainUser(user) { removeMainUser(user) {
console.log('removeMainUser APP', user); console.log("removeMainUser APP", user);
window.alert(this.$t('main_user_is_mandatory')); window.alert(this.$t("main_user_is_mandatory"));
return; return;
}, },
onDatesSet(event) { onDatesSet(event) {
console.log('onDatesSet', event); console.log("onDatesSet", event);
this.$store.dispatch('setCurrentDatesView', {start: event.start, end: event.end}); this.$store.dispatch("setCurrentDatesView", {
start: event.start,
end: event.end,
});
}, },
onDateSelect(payload) { onDateSelect(payload) {
console.log('onDateSelect', payload); console.log("onDateSelect", payload);
// show an alert if changing mainUser // show an alert if changing mainUser
if ((this.$store.getters.getMainUser !== null if (
&& this.$store.state.me.id !== this.$store.getters.getMainUser.id) (this.$store.getters.getMainUser !== null &&
|| this.$store.getters.getMainUser === null) { this.$store.state.me.id !==
if (!window.confirm(this.$t('will_change_main_user_for_me'))) { this.$store.getters.getMainUser.id) ||
this.$store.getters.getMainUser === null
) {
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
return; return;
} else { } else {
this.$store.commit('showUserOnCalendar', {user: this.$store.state.me, remotes: true, ranges: true}) this.$store.commit("showUserOnCalendar", {
user: this.$store.state.me,
remotes: true,
ranges: true,
});
} }
} }
this.$store.dispatch('setEventTimes', {start: payload.start, end: payload.end}); this.$store.dispatch("setEventTimes", {
start: payload.start,
end: payload.end,
});
}, },
onEventChange(payload) { onEventChange(payload) {
console.log('onEventChange', payload); console.log("onEventChange", payload);
if (this.$store.state.activity.calendarRange !== null) { if (this.$store.state.activity.calendarRange !== null) {
throw new Error("not allowed to edit a calendar associated with a calendar range"); throw new Error(
"not allowed to edit a calendar associated with a calendar range",
);
} }
this.$store.dispatch('setEventTimes', {start: payload.event.start, end: payload.event.end}); this.$store.dispatch("setEventTimes", {
start: payload.event.start,
end: payload.event.end,
});
}, },
onEventClick(payload) { onEventClick(payload) {
if (payload.event.extendedProps.is !== 'range') { if (payload.event.extendedProps.is !== "range") {
// do nothing when clicking on remote // do nothing when clicking on remote
return; return;
} }
// show an alert if changing mainUser // show an alert if changing mainUser
if (this.$store.getters.getMainUser !== null if (
&& payload.event.extendedProps.userId !== this.$store.getters.getMainUser.id) { this.$store.getters.getMainUser !== null &&
if (!window.confirm(this.$t('this_calendar_range_will_change_main_user'))) { payload.event.extendedProps.userId !==
this.$store.getters.getMainUser.id
) {
if (
!window.confirm(
this.$t("this_calendar_range_will_change_main_user"),
)
) {
return; return;
} }
} }
this.$store.dispatch('associateCalendarToRange', {range: payload.event}); this.$store.dispatch("associateCalendarToRange", {
range: payload.event,
});
}, },
}, },
} };
</script> </script>
<style> <style>

View File

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

View File

@ -1,7 +1,7 @@
import {fetchResults} from '../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods'; import { fetchResults } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import {datetimeToISO} from '../../../../../ChillMainBundle/Resources/public/chill/js/date'; import { datetimeToISO } from "../../../../../ChillMainBundle/Resources/public/chill/js/date";
import {User} from '../../../../../ChillMainBundle/Resources/public/types'; import { User } from "../../../../../ChillMainBundle/Resources/public/types";
import {CalendarLight, CalendarRange, CalendarRemote} from '../../types'; import { CalendarLight, CalendarRange, CalendarRemote } from "../../types";
// re-export whoami // re-export whoami
export { whoami } from "../../../../../ChillMainBundle/Resources/public/lib/api/user"; export { whoami } from "../../../../../ChillMainBundle/Resources/public/lib/api/user";
@ -13,26 +13,38 @@ export {whoami} from "../../../../../ChillMainBundle/Resources/public/lib/api/us
* @param Date end * @param Date end
* @return Promise * @return Promise
*/ */
export const fetchCalendarRangeForUser = (user: User, start: Date, end: Date): Promise<CalendarRange[]> => { export const fetchCalendarRangeForUser = (
user: User,
start: Date,
end: Date,
): Promise<CalendarRange[]> => {
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`; const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
const dateFrom = datetimeToISO(start); const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end); const dateTo = datetimeToISO(end);
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo }); return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
} };
export const fetchCalendarRemoteForUser = (user: User, start: Date, end: Date): Promise<CalendarRemote[]> => { export const fetchCalendarRemoteForUser = (
user: User,
start: Date,
end: Date,
): Promise<CalendarRemote[]> => {
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`; const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
const dateFrom = datetimeToISO(start); const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end); const dateTo = datetimeToISO(end);
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo }); return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
} };
export const fetchCalendarLocalForUser = (user: User, start: Date, end: Date): Promise<CalendarLight[]> => { export const fetchCalendarLocalForUser = (
user: User,
start: Date,
end: Date,
): Promise<CalendarLight[]> => {
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`; const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
const dateFrom = datetimeToISO(start); const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end); const dateTo = datetimeToISO(end);
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo }); return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
} };

View File

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

View File

@ -1,6 +1,6 @@
import {personMessages} from 'ChillPersonAssets/vuejs/_js/i18n' import { personMessages } from "ChillPersonAssets/vuejs/_js/i18n";
import {calendarUserSelectorMessages} from '../_components/CalendarUserSelector/js/i18n'; import { calendarUserSelectorMessages } from "../_components/CalendarUserSelector/js/i18n";
import {activityMessages} from 'ChillActivityAssets/vuejs/Activity/i18n'; import { activityMessages } from "ChillActivityAssets/vuejs/Activity/i18n";
const appMessages = { const appMessages = {
fr: { fr: {
@ -13,20 +13,22 @@ const appMessages = {
bloc_thirdparty: "Tiers professionnels", bloc_thirdparty: "Tiers professionnels",
bloc_users: "T(M)S", bloc_users: "T(M)S",
}, },
this_calendar_range_will_change_main_user: "Cette plage de disponibilité n'est pas celle de l'utilisateur principal. Si vous continuez, l'utilisateur principal sera adapté. Êtes-vous sûr·e ?", this_calendar_range_will_change_main_user:
will_change_main_user_for_me: "Vous ne pouvez pas écrire dans le calendrier d'un autre utilisateur. Voulez-vous être l'utilisateur principal de ce rendez-vous ?", "Cette plage de disponibilité n'est pas celle de l'utilisateur principal. Si vous continuez, l'utilisateur principal sera adapté. Êtes-vous sûr·e ?",
main_user_is_mandatory: "L'utilisateur principal est requis. Vous pouvez le modifier, mais pas le supprimer", will_change_main_user_for_me:
change_main_user_will_reset_event_data: "Modifier l'utilisateur principal nécessite de choisir une autre plage de disponibilité ou un autre horaire. Ces informations seront perdues. Êtes-vous sûr·e de vouloir continuer ?", "Vous ne pouvez pas écrire dans le calendrier d'un autre utilisateur. Voulez-vous être l'utilisateur principal de ce rendez-vous ?",
list_three_days: 'Liste 3 jours', main_user_is_mandatory:
current_selected: 'Rendez-vous fixé', "L'utilisateur principal est requis. Vous pouvez le modifier, mais pas le supprimer",
change_main_user_will_reset_event_data:
"Modifier l'utilisateur principal nécessite de choisir une autre plage de disponibilité ou un autre horaire. Ces informations seront perdues. Êtes-vous sûr·e de vouloir continuer ?",
list_three_days: "Liste 3 jours",
current_selected: "Rendez-vous fixé",
main_user: "Utilisateur principal", main_user: "Utilisateur principal",
} },
} };
Object.assign(appMessages.fr, personMessages.fr); Object.assign(appMessages.fr, personMessages.fr);
Object.assign(appMessages.fr, calendarUserSelectorMessages.fr); Object.assign(appMessages.fr, calendarUserSelectorMessages.fr);
Object.assign(appMessages.fr, activityMessages.fr); Object.assign(appMessages.fr, activityMessages.fr);
export { export { appMessages };
appMessages
};

View File

@ -1,9 +1,9 @@
import { createApp } from 'vue'; import { createApp } from "vue";
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
import { appMessages } from './i18n' import { appMessages } from "./i18n";
import store from './store' import store from "./store";
import App from './App.vue'; import App from "./App.vue";
const i18n = _createI18n(appMessages); const i18n = _createI18n(appMessages);
@ -12,5 +12,5 @@ const app = createApp({
}) })
.use(store) .use(store)
.use(i18n) .use(i18n)
.component('app', App) .component("app", App)
.mount('#calendar'); .mount("#calendar");

View File

@ -1,14 +1,11 @@
import { import { addIdToValue, removeIdFromValue } from "./utils";
addIdToValue,
removeIdFromValue,
} from './utils';
import { import {
fetchCalendarRangeForUser, fetchCalendarRangeForUser,
fetchCalendarRemoteForUser, fetchCalendarRemoteForUser,
fetchCalendarLocalForUser, fetchCalendarLocalForUser,
} from './../api'; } from "./../api";
import {datetimeToISO} from 'ChillMainAssets/chill/js/date'; import { datetimeToISO } from "ChillMainAssets/chill/js/date";
import {postLocation} from 'ChillActivityAssets/vuejs/Activity/api'; import { postLocation } from "ChillActivityAssets/vuejs/Activity/api";
/** /**
* This will store a unique key for each value, and prevent to launch the same * This will store a unique key for each value, and prevent to launch the same
@ -25,9 +22,9 @@ const fetchings = new Set();
export default { export default {
setCurrentDatesView({ commit, dispatch }, { start, end }) { setCurrentDatesView({ commit, dispatch }, { start, end }) {
commit('setCurrentDatesView', {start, end}); commit("setCurrentDatesView", { start, end });
return dispatch('fetchCalendarEvents'); return dispatch("fetchCalendarEvents");
}, },
fetchCalendarEvents({ state, getters, dispatch }) { fetchCalendarEvents({ state, getters, dispatch }) {
if (state.currentView.start === null && state.currentView.end === null) { if (state.currentView.start === null && state.currentView.end === null) {
@ -39,29 +36,32 @@ export default {
let unique = `${uid}, ${state.currentView.start.toISOString()}, ${state.currentView.end.toISOString()}`; let unique = `${uid}, ${state.currentView.start.toISOString()}, ${state.currentView.end.toISOString()}`;
if (fetchings.has(unique)) { if (fetchings.has(unique)) {
console.log('prevent from fetching for a user', unique); console.log("prevent from fetching for a user", unique);
continue; continue;
} }
fetchings.add(unique); fetchings.add(unique);
promises.push( promises.push(
dispatch( dispatch("fetchCalendarRangeForUser", {
'fetchCalendarRangeForUser', user: state.usersData.get(uid).user,
{user: state.usersData.get(uid).user, start: state.currentView.start, end: state.currentView.end} start: state.currentView.start,
) end: state.currentView.end,
}),
); );
promises.push( promises.push(
dispatch( dispatch("fetchCalendarRemotesForUser", {
'fetchCalendarRemotesForUser', user: state.usersData.get(uid).user,
{user: state.usersData.get(uid).user, start: state.currentView.start, end: state.currentView.end} start: state.currentView.start,
) end: state.currentView.end,
}),
); );
promises.push( promises.push(
dispatch( dispatch("fetchCalendarLocalsForUser", {
'fetchCalendarLocalsForUser', user: state.usersData.get(uid).user,
{user: state.usersData.get(uid).user, start: state.currentView.start, end: state.currentView.end} start: state.currentView.start,
) end: state.currentView.end,
}),
); );
} }
@ -70,7 +70,7 @@ export default {
fetchCalendarRangeForUser({ commit, getters }, { user, start, end }) { fetchCalendarRangeForUser({ commit, getters }, { user, start, end }) {
if (!getters.isCalendarRangeLoadedForUser({ user, start, end })) { if (!getters.isCalendarRangeLoadedForUser({ user, start, end })) {
return fetchCalendarRangeForUser(user, start, end).then((ranges) => { return fetchCalendarRangeForUser(user, start, end).then((ranges) => {
commit('addCalendarRangesForUser', {user, ranges, start, end}); commit("addCalendarRangesForUser", { user, ranges, start, end });
return Promise.resolve(); return Promise.resolve();
}); });
@ -79,7 +79,7 @@ export default {
fetchCalendarRemotesForUser({ commit, getters }, { user, start, end }) { fetchCalendarRemotesForUser({ commit, getters }, { user, start, end }) {
if (!getters.isCalendarRemoteLoadedForUser({ user, start, end })) { if (!getters.isCalendarRemoteLoadedForUser({ user, start, end })) {
return fetchCalendarRemoteForUser(user, start, end).then((remotes) => { return fetchCalendarRemoteForUser(user, start, end).then((remotes) => {
commit('addCalendarRemotesForUser', {user, remotes, start, end}); commit("addCalendarRemotesForUser", { user, remotes, start, end });
return Promise.resolve(); return Promise.resolve();
}); });
@ -88,52 +88,72 @@ export default {
fetchCalendarLocalsForUser({ commit, getters }, { user, start, end }) { fetchCalendarLocalsForUser({ commit, getters }, { user, start, end }) {
if (!getters.isCalendarRemoteLoadedForUser({ user, start, end })) { if (!getters.isCalendarRemoteLoadedForUser({ user, start, end })) {
return fetchCalendarLocalForUser(user, start, end).then((locals) => { return fetchCalendarLocalForUser(user, start, end).then((locals) => {
commit('addCalendarLocalsForUser', {user, locals, start, end}); commit("addCalendarLocalsForUser", { user, locals, start, end });
return Promise.resolve(); return Promise.resolve();
}); });
} }
}, },
addPersonsInvolved({ commit, dispatch }, payload) { addPersonsInvolved({ commit, dispatch }, payload) {
console.log('### action addPersonsInvolved', payload.result.type); console.log("### action addPersonsInvolved", payload.result.type);
console.log('### action addPersonsInvolved payload result', payload.result); console.log("### action addPersonsInvolved payload result", payload.result);
switch (payload.result.type) { switch (payload.result.type) {
case 'person': case "person":
let aPersons = document.getElementById("chill_activitybundle_activity_persons"); let aPersons = document.getElementById(
"chill_activitybundle_activity_persons",
);
aPersons.value = addIdToValue(aPersons.value, payload.result.id); aPersons.value = addIdToValue(aPersons.value, payload.result.id);
break; break;
case 'thirdparty': case "thirdparty":
let aThirdParties = document.getElementById("chill_activitybundle_activity_professionals"); let aThirdParties = document.getElementById(
aThirdParties.value = addIdToValue(aThirdParties.value, payload.result.id); "chill_activitybundle_activity_professionals",
);
aThirdParties.value = addIdToValue(
aThirdParties.value,
payload.result.id,
);
break; break;
case 'user': case "user":
let aUsers = document.getElementById("chill_activitybundle_activity_users"); let aUsers = document.getElementById(
"chill_activitybundle_activity_users",
);
aUsers.value = addIdToValue(aUsers.value, payload.result.id); aUsers.value = addIdToValue(aUsers.value, payload.result.id);
commit('showUserOnCalendar', {user: payload.result, ranges: false, remotes: true}); commit("showUserOnCalendar", {
dispatch('fetchCalendarEvents'); user: payload.result,
ranges: false,
remotes: true,
});
dispatch("fetchCalendarEvents");
break; break;
} }
; commit("addPersonsInvolved", payload);
commit('addPersonsInvolved', payload);
}, },
removePersonInvolved({ commit }, payload) { removePersonInvolved({ commit }, payload) {
//console.log('### action removePersonInvolved', payload); //console.log('### action removePersonInvolved', payload);
switch (payload.type) { switch (payload.type) {
case 'person': case "person":
let aPersons = document.getElementById("chill_activitybundle_activity_persons"); let aPersons = document.getElementById(
"chill_activitybundle_activity_persons",
);
aPersons.value = removeIdFromValue(aPersons.value, payload.id); aPersons.value = removeIdFromValue(aPersons.value, payload.id);
break; break;
case 'thirdparty': case "thirdparty":
let aThirdParties = document.getElementById("chill_activitybundle_activity_professionals"); let aThirdParties = document.getElementById(
aThirdParties.value = removeIdFromValue(aThirdParties.value, payload.id); "chill_activitybundle_activity_professionals",
);
aThirdParties.value = removeIdFromValue(
aThirdParties.value,
payload.id,
);
break; break;
case 'user': case "user":
let aUsers = document.getElementById("chill_activitybundle_activity_users"); let aUsers = document.getElementById(
"chill_activitybundle_activity_users",
);
aUsers.value = removeIdFromValue(aUsers.value, payload.id); aUsers.value = removeIdFromValue(aUsers.value, payload.id);
break; break;
} }
; commit("removePersonInvolved", payload);
commit('removePersonInvolved', payload);
}, },
// Calendar // Calendar
@ -149,30 +169,48 @@ export default {
* @param end * @param end
*/ */
setEventTimes({ commit, state, getters }, { start, end }) { setEventTimes({ commit, state, getters }, { start, end }) {
console.log('### action createEvent', {start, end}); console.log("### action createEvent", { start, end });
let startDateInput = document.getElementById("chill_activitybundle_activity_startDate"); let startDateInput = document.getElementById(
startDateInput.value = null !== start ? datetimeToISO(start) : ''; "chill_activitybundle_activity_startDate",
let endDateInput = document.getElementById("chill_activitybundle_activity_endDate"); );
endDateInput.value = null !== end ? datetimeToISO(end) : ''; startDateInput.value = null !== start ? datetimeToISO(start) : "";
let calendarRangeInput = document.getElementById("chill_activitybundle_activity_calendarRange"); let endDateInput = document.getElementById(
"chill_activitybundle_activity_endDate",
);
endDateInput.value = null !== end ? datetimeToISO(end) : "";
let calendarRangeInput = document.getElementById(
"chill_activitybundle_activity_calendarRange",
);
calendarRangeInput.value = ""; calendarRangeInput.value = "";
if (getters.getMainUser === null || getters.getMainUser.id !== state.me.id) { if (
let mainUserInput = document.getElementById("chill_activitybundle_activity_mainUser"); getters.getMainUser === null ||
getters.getMainUser.id !== state.me.id
) {
let mainUserInput = document.getElementById(
"chill_activitybundle_activity_mainUser",
);
mainUserInput.value = state.me.id; mainUserInput.value = state.me.id;
commit('setMainUser', state.me); commit("setMainUser", state.me);
} }
commit('setEventTimes', {start, end}); commit("setEventTimes", { start, end });
}, },
associateCalendarToRange({ state, commit, dispatch, getters }, { range }) { associateCalendarToRange({ state, commit, dispatch, getters }, { range }) {
console.log('### action associateCAlendarToRange', range); console.log("### action associateCAlendarToRange", range);
let startDateInput = document.getElementById("chill_activitybundle_activity_startDate"); let startDateInput = document.getElementById(
"chill_activitybundle_activity_startDate",
);
startDateInput.value = null !== range ? datetimeToISO(range.start) : ""; startDateInput.value = null !== range ? datetimeToISO(range.start) : "";
let endDateInput = document.getElementById("chill_activitybundle_activity_endDate"); let endDateInput = document.getElementById(
"chill_activitybundle_activity_endDate",
);
endDateInput.value = null !== range ? datetimeToISO(range.end) : ""; endDateInput.value = null !== range ? datetimeToISO(range.end) : "";
let calendarRangeInput = document.getElementById("chill_activitybundle_activity_calendarRange"); let calendarRangeInput = document.getElementById(
calendarRangeInput.value = null !== range ? Number(range.extendedProps.calendarRangeId) : ""; "chill_activitybundle_activity_calendarRange",
);
calendarRangeInput.value =
null !== range ? Number(range.extendedProps.calendarRangeId) : "";
if (null !== range) { if (null !== range) {
let location = getters.getLocationById(range.extendedProps.locationId); let location = getters.getLocationById(range.extendedProps.locationId);
@ -181,63 +219,68 @@ export default {
console.error("location not found!", range.extendedProps.locationId); console.error("location not found!", range.extendedProps.locationId);
} }
dispatch('updateLocation', location); dispatch("updateLocation", location);
const userId = range.extendedProps.userId; const userId = range.extendedProps.userId;
if (state.activity.mainUser !== null && state.activity.mainUser.id !== userId) { if (
dispatch('setMainUser', state.usersData.get(userId).user); state.activity.mainUser !== null &&
state.activity.mainUser.id !== userId
) {
dispatch("setMainUser", state.usersData.get(userId).user);
// TODO: remove persons involved with this user // TODO: remove persons involved with this user
} }
} }
commit('associateCalendarToRange', {range}); commit("associateCalendarToRange", { range });
return Promise.resolve(); return Promise.resolve();
}, },
setMainUser({ commit, dispatch, state }, mainUser) { setMainUser({ commit, dispatch, state }, mainUser) {
console.log('setMainUser', mainUser); console.log("setMainUser", mainUser);
let mainUserInput = document.getElementById("chill_activitybundle_activity_mainUser"); let mainUserInput = document.getElementById(
"chill_activitybundle_activity_mainUser",
);
mainUserInput.value = Number(mainUser.id); mainUserInput.value = Number(mainUser.id);
return dispatch('associateCalendarToRange', { range: null }).then(() => { return dispatch("associateCalendarToRange", { range: null }).then(() => {
commit('setMainUser', mainUser); commit("setMainUser", mainUser);
return dispatch('fetchCalendarEvents'); return dispatch("fetchCalendarEvents");
}); });
}, },
// Location // Location
updateLocation({ commit }, value) { updateLocation({ commit }, value) {
console.log('### action: updateLocation', value); console.log("### action: updateLocation", value);
let hiddenLocation = document.getElementById("chill_activitybundle_activity_location"); let hiddenLocation = document.getElementById(
"chill_activitybundle_activity_location",
);
if (value.onthefly) { if (value.onthefly) {
const body = { const body = {
"type": "location", type: "location",
"name": value.name === '__AccompanyingCourseLocation__' ? null : value.name, name:
"locationType": { value.name === "__AccompanyingCourseLocation__" ? null : value.name,
"id": value.locationType.id, locationType: {
"type": "location-type" id: value.locationType.id,
} type: "location-type",
},
}; };
if (value.address.id) { if (value.address.id) {
Object.assign(body, { Object.assign(body, {
"address": { address: {
"id": value.address.id id: value.address.id,
}, },
}) });
} }
postLocation(body) postLocation(body)
.then( .then((location) => (hiddenLocation.value = location.id))
location => hiddenLocation.value = location.id .catch((err) => {
).catch(
err => {
console.log(err.message); console.log(err.message);
} });
);
} else { } else {
hiddenLocation.value = value.id; hiddenLocation.value = value.id;
} }
commit("updateLocation", value); commit("updateLocation", value);
} },
} };

View File

@ -18,7 +18,7 @@ export default {
if (null === state.activity.start) { if (null === state.activity.start) {
return new Date(); return new Date();
} }
throw 'transform date to object ?'; throw "transform date to object ?";
}, },
/** /**
* Compute the event sources to show on the FullCalendar * Compute the event sources to show on the FullCalendar
@ -33,7 +33,7 @@ export default {
// current calendar // current calendar
if (state.activity.startDate !== null && state.activity.endDate !== null) { if (state.activity.startDate !== null && state.activity.endDate !== null) {
const s = { const s = {
id: 'current', id: "current",
events: [ events: [
{ {
title: "Rendez-vous", title: "Rendez-vous",
@ -41,8 +41,8 @@ export default {
end: state.activity.endDate, end: state.activity.endDate,
allDay: false, allDay: false,
is: "current", is: "current",
classNames: ['iscurrent'], classNames: ["iscurrent"],
} },
], ],
editable: state.activity.calendarRange === null, editable: state.activity.calendarRange === null,
}; };
@ -52,7 +52,7 @@ export default {
for (const [userId, kinds] of state.currentView.users.entries()) { for (const [userId, kinds] of state.currentView.users.entries()) {
if (!state.usersData.has(userId)) { if (!state.usersData.has(userId)) {
console.log('try to get events on a user which not exists', userId); console.log("try to get events on a user which not exists", userId);
continue; continue;
} }
@ -61,11 +61,16 @@ export default {
if (kinds.ranges && userData.calendarRanges.length > 0) { if (kinds.ranges && userData.calendarRanges.length > 0) {
const s = { const s = {
id: `ranges_${userId}`, id: `ranges_${userId}`,
events: userData.calendarRanges.filter(r => state.activity.calendarRange === null || r.calendarRangeId !== state.activity.calendarRange.calendarRangeId), events: userData.calendarRanges.filter(
(r) =>
state.activity.calendarRange === null ||
r.calendarRangeId !==
state.activity.calendarRange.calendarRangeId,
),
color: userData.mainColor, color: userData.mainColor,
classNames: ['isrange'], classNames: ["isrange"],
backgroundColor: 'white', backgroundColor: "white",
textColor: 'black', textColor: "black",
editable: false, editable: false,
}; };
@ -74,10 +79,10 @@ export default {
if (kinds.remotes && userData.remotes.length > 0) { if (kinds.remotes && userData.remotes.length > 0) {
const s = { const s = {
'id': `remote_${userId}`, id: `remote_${userId}`,
events: userData.remotes, events: userData.remotes,
color: userData.mainColor, color: userData.mainColor,
textColor: 'black', textColor: "black",
editable: false, editable: false,
}; };
@ -87,10 +92,12 @@ export default {
// if remotes is checked, we display also the locals calendars // if remotes is checked, we display also the locals calendars
if (kinds.remotes && userData.locals.length > 0) { if (kinds.remotes && userData.locals.length > 0) {
const s = { const s = {
'id': `local_${userId}`, id: `local_${userId}`,
events: userData.locals.filter(l => l.originId !== state.activity.id), events: userData.locals.filter(
(l) => l.originId !== state.activity.id,
),
color: userData.mainColor, color: userData.mainColor,
textColor: 'black', textColor: "black",
editable: false, editable: false,
}; };
@ -104,7 +111,7 @@ export default {
return state.activity.startDate; return state.activity.startDate;
}, },
getInviteForUser: (state) => (user) => { getInviteForUser: (state) => (user) => {
return state.activity.invites.find(i => i.user.id === user.id); return state.activity.invites.find((i) => i.user.id === user.id);
}, },
/** /**
* get the user data for a specific user * get the user data for a specific user
@ -138,7 +145,9 @@ export default {
* @param getters * @param getters
* @returns {(function({user: *, start: *, end: *}): (boolean))|*} * @returns {(function({user: *, start: *, end: *}): (boolean))|*}
*/ */
isCalendarRangeLoadedForUser: (state, getters) => ({user, start, end}) => { isCalendarRangeLoadedForUser:
(state, getters) =>
({ user, start, end }) => {
if (!getters.hasUserData(user)) { if (!getters.hasUserData(user)) {
return false; return false;
} }
@ -159,7 +168,9 @@ export default {
* @param getters * @param getters
* @returns {(function({user: *, start: *, end: *}): (boolean))|*} * @returns {(function({user: *, start: *, end: *}): (boolean))|*}
*/ */
isCalendarRemoteLoadedForUser: (state, getters) => ({user, start, end}) => { isCalendarRemoteLoadedForUser:
(state, getters) =>
({ user, start, end }) => {
if (!getters.hasUserData(user)) { if (!getters.hasUserData(user)) {
return false; return false;
} }
@ -180,8 +191,10 @@ export default {
*/ */
isRangeShownOnCalendarForUser: (state) => (user) => { isRangeShownOnCalendarForUser: (state) => (user) => {
const k = state.currentView.users.get(user.id); const k = state.currentView.users.get(user.id);
if (typeof k === 'undefined') { if (typeof k === "undefined") {
console.error('try to determinate if calendar range is shown and user is not in currentView'); console.error(
"try to determinate if calendar range is shown and user is not in currentView",
);
return false; return false;
} }
@ -195,8 +208,10 @@ export default {
*/ */
isRemoteShownOnCalendarForUser: (state) => (user) => { isRemoteShownOnCalendarForUser: (state) => (user) => {
const k = state.currentView.users.get(user.id); const k = state.currentView.users.get(user.id);
if (typeof k === 'undefined') { if (typeof k === "undefined") {
console.error('try to determinate if calendar range is shown and user is not in currentView'); console.error(
"try to determinate if calendar range is shown and user is not in currentView",
);
return false; return false;
} }
@ -205,8 +220,8 @@ export default {
getLocationById: (state) => (id) => { getLocationById: (state) => (id) => {
for (let group of state.availableLocations) { for (let group of state.availableLocations) {
console.log('group', group); console.log("group", group);
const found = group.locations.find(l => l.id === id); const found = group.locations.find((l) => l.id === id);
if (typeof found !== "undefined") { if (typeof found !== "undefined") {
return found; return found;
} }
@ -216,57 +231,60 @@ export default {
}, },
suggestedEntities(state, getters) { suggestedEntities(state, getters) {
if (typeof (state.activity.accompanyingPeriod) === 'undefined') { if (typeof state.activity.accompanyingPeriod === "undefined") {
return []; return [];
} }
const allEntities = [ const allEntities = [
...getters.suggestedPersons, ...getters.suggestedPersons,
...getters.suggestedRequestor, ...getters.suggestedRequestor,
...getters.suggestedUser, ...getters.suggestedUser,
...getters.suggestedResources ...getters.suggestedResources,
]; ];
const uniqueIds = [...new Set(allEntities.map(i => `${i.type}-${i.id}`))]; const uniqueIds = [...new Set(allEntities.map((i) => `${i.type}-${i.id}`))];
return Array.from(uniqueIds, id => allEntities.filter(r => `${r.type}-${r.id}` === id)[0]); return Array.from(
uniqueIds,
(id) => allEntities.filter((r) => `${r.type}-${r.id}` === id)[0],
);
}, },
suggestedPersons(state) { suggestedPersons(state) {
const existingPersonIds = state.activity.persons.map(p => p.id); const existingPersonIds = state.activity.persons.map((p) => p.id);
return state.activity.accompanyingPeriod.participations return state.activity.accompanyingPeriod.participations
.filter(p => p.endDate === null) .filter((p) => p.endDate === null)
.map(p => p.person) .map((p) => p.person)
.filter(p => !existingPersonIds.includes(p.id)) .filter((p) => !existingPersonIds.includes(p.id));
}, },
suggestedRequestor(state) { suggestedRequestor(state) {
if (state.activity.accompanyingPeriod.requestor === null) { if (state.activity.accompanyingPeriod.requestor === null) {
return []; return [];
} }
const existingPersonIds = state.activity.persons.map(p => p.id); const existingPersonIds = state.activity.persons.map((p) => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map(p => p.id); const existingThirdPartyIds = state.activity.thirdParties.map((p) => p.id);
return [state.activity.accompanyingPeriod.requestor] return [state.activity.accompanyingPeriod.requestor].filter(
.filter(r => (r) =>
(r.type === 'person' && !existingPersonIds.includes(r.id)) || (r.type === "person" && !existingPersonIds.includes(r.id)) ||
(r.type === 'thirdparty' && !existingThirdPartyIds.includes(r.id)) (r.type === "thirdparty" && !existingThirdPartyIds.includes(r.id)),
); );
}, },
suggestedUser(state) { suggestedUser(state) {
if (null === state.activity.users) { if (null === state.activity.users) {
return []; return [];
} }
const existingUserIds = state.activity.users.map(p => p.id); const existingUserIds = state.activity.users.map((p) => p.id);
return [state.activity.accompanyingPeriod.user] return [state.activity.accompanyingPeriod.user].filter(
.filter( (u) => u !== null && !existingUserIds.includes(u.id),
u => u !== null && !existingUserIds.includes(u.id)
); );
}, },
suggestedResources(state) { suggestedResources(state) {
const resources = state.activity.accompanyingPeriod.resources; const resources = state.activity.accompanyingPeriod.resources;
const existingPersonIds = state.activity.persons.map(p => p.id); const existingPersonIds = state.activity.persons.map((p) => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map(p => p.id); const existingThirdPartyIds = state.activity.thirdParties.map((p) => p.id);
return state.activity.accompanyingPeriod.resources return state.activity.accompanyingPeriod.resources
.map(r => r.resource) .map((r) => r.resource)
.filter(r => .filter(
(r.type === 'person' && !existingPersonIds.includes(r.id)) || (r) =>
(r.type === 'thirdparty' && !existingThirdPartyIds.includes(r.id)) (r.type === "person" && !existingPersonIds.includes(r.id)) ||
(r.type === "thirdparty" && !existingThirdPartyIds.includes(r.id)),
); );
} },
} };

View File

@ -1,14 +1,14 @@
import 'es6-promise/auto'; import "es6-promise/auto";
import { createStore } from 'vuex'; import { createStore } from "vuex";
import { postLocation } from 'ChillActivityAssets/vuejs/Activity/api'; import { postLocation } from "ChillActivityAssets/vuejs/Activity/api";
import getters from './getters'; import getters from "./getters";
import actions from './actions'; import actions from "./actions";
import mutations from './mutations'; import mutations from "./mutations";
import { mapEntity } from './utils'; import { mapEntity } from "./utils";
import { whoami } from '../api'; import { whoami } from "../api";
import prepareLocations from "ChillActivityAssets/vuejs/Activity/store.locations"; import prepareLocations from "ChillActivityAssets/vuejs/Activity/store.locations";
const debug = process.env.NODE_ENV !== 'production'; const debug = process.env.NODE_ENV !== "production";
const store = createStore({ const store = createStore({
strict: debug, strict: debug,
@ -42,16 +42,24 @@ const store = createStore({
actions, actions,
}); });
whoami().then(me => { whoami().then((me) => {
store.commit('setWhoAmiI', me); store.commit("setWhoAmiI", me);
}); });
if (null !== store.getters.getMainUser) { if (null !== store.getters.getMainUser) {
store.commit('showUserOnCalendar', {ranges: true, remotes: true, user: store.getters.getMainUser}); store.commit("showUserOnCalendar", {
ranges: true,
remotes: true,
user: store.getters.getMainUser,
});
} }
for (let u of store.state.activity.users) { for (let u of store.state.activity.users) {
store.commit('showUserOnCalendar', {ranges: false, remotes: false, user: u}); store.commit("showUserOnCalendar", {
ranges: false,
remotes: false,
user: u,
});
} }
prepareLocations(store); prepareLocations(store);

View File

@ -3,7 +3,7 @@ import {
calendarRangeToFullCalendarEvent, calendarRangeToFullCalendarEvent,
remoteToFullCalendarEvent, remoteToFullCalendarEvent,
localsToFullCalendarEvent, localsToFullCalendarEvent,
} from './utils'; } from "./utils";
export default { export default {
setWhoAmiI(state, me) { setWhoAmiI(state, me) {
@ -20,13 +20,10 @@ export default {
const cur = state.currentView.users.get(user.id); const cur = state.currentView.users.get(user.id);
state.currentView.users.set( state.currentView.users.set(user.id, {
user.id, ranges: typeof ranges !== "undefined" ? ranges : cur.ranges,
{ remotes: typeof remotes !== "undefined" ? remotes : cur.remotes,
ranges: typeof ranges !== 'undefined' ? ranges : cur.ranges, });
remotes: typeof remotes !== 'undefined' ? remotes : cur.remotes,
}
);
}, },
/** /**
* Set the event start and end to the given start and end, * Set the event start and end to the given start and end,
@ -49,7 +46,7 @@ export default {
* @param range * @param range
*/ */
associateCalendarToRange(state, { range }) { associateCalendarToRange(state, { range }) {
console.log('associateCalendarToRange', range); console.log("associateCalendarToRange", range);
if (null === range) { if (null === range) {
state.activity.calendarRange = null; state.activity.calendarRange = null;
@ -59,22 +56,25 @@ export default {
return; return;
} }
console.log('userId', range.extendedProps.userId); console.log("userId", range.extendedProps.userId);
const r = state.usersData.get(range.extendedProps.userId).calendarRanges const r = state.usersData
.find(r => r.calendarRangeId === range.extendedProps.calendarRangeId); .get(range.extendedProps.userId)
.calendarRanges.find(
(r) => r.calendarRangeId === range.extendedProps.calendarRangeId,
);
if (typeof r === 'undefined') { if (typeof r === "undefined") {
throw Error('Could not find managed calendar range'); throw Error("Could not find managed calendar range");
} }
console.log('range found', r); console.log("range found", r);
state.activity.startDate = range.start; state.activity.startDate = range.start;
state.activity.endDate = range.end; state.activity.endDate = range.end;
state.activity.calendarRange = r; state.activity.calendarRange = r;
console.log('activity', state.activity); console.log("activity", state.activity);
}, },
setMainUser(state, user) { setMainUser(state, user) {
@ -85,32 +85,36 @@ export default {
addPersonsInvolved(state, payload) { addPersonsInvolved(state, payload) {
//console.log('### mutation addPersonsInvolved', payload.result.type); //console.log('### mutation addPersonsInvolved', payload.result.type);
switch (payload.result.type) { switch (payload.result.type) {
case 'person': case "person":
state.activity.persons.push(payload.result); state.activity.persons.push(payload.result);
break; break;
case 'thirdparty': case "thirdparty":
state.activity.thirdParties.push(payload.result); state.activity.thirdParties.push(payload.result);
break; break;
case 'user': case "user":
state.activity.users.push(payload.result); state.activity.users.push(payload.result);
break; break;
} }
;
}, },
removePersonInvolved(state, payload) { removePersonInvolved(state, payload) {
//console.log('### mutation removePersonInvolved', payload.type); //console.log('### mutation removePersonInvolved', payload.type);
switch (payload.type) { switch (payload.type) {
case 'person': case "person":
state.activity.persons = state.activity.persons.filter(person => person !== payload); state.activity.persons = state.activity.persons.filter(
(person) => person !== payload,
);
break; break;
case 'thirdparty': case "thirdparty":
state.activity.thirdParties = state.activity.thirdParties.filter(thirdparty => thirdparty !== payload); state.activity.thirdParties = state.activity.thirdParties.filter(
(thirdparty) => thirdparty !== payload,
);
break; break;
case 'user': case "user":
state.activity.users = state.activity.users.filter(user => user !== payload); state.activity.users = state.activity.users.filter(
(user) => user !== payload,
);
break; break;
} }
;
}, },
/** /**
* Add CalendarRange object for an user * Add CalendarRange object for an user
@ -131,13 +135,13 @@ export default {
} }
const eventRanges = ranges const eventRanges = ranges
.filter(r => !state.existingEvents.has(`range_${r.id}`)) .filter((r) => !state.existingEvents.has(`range_${r.id}`))
.map(r => { .map((r) => {
// add to existing ids // add to existing ids
state.existingEvents.add(`range_${r.id}`); state.existingEvents.add(`range_${r.id}`);
return r; return r;
}) })
.map(r => calendarRangeToFullCalendarEvent(r)); .map((r) => calendarRangeToFullCalendarEvent(r));
userData.calendarRanges = userData.calendarRanges.concat(eventRanges); userData.calendarRanges = userData.calendarRanges.concat(eventRanges);
userData.calendarRangesLoaded.push({ start, end }); userData.calendarRangesLoaded.push({ start, end });
@ -152,13 +156,13 @@ export default {
} }
const eventRemotes = remotes const eventRemotes = remotes
.filter(r => !state.existingEvents.has(`remote_${r.id}`)) .filter((r) => !state.existingEvents.has(`remote_${r.id}`))
.map(r => { .map((r) => {
// add to existing ids // add to existing ids
state.existingEvents.add(`remote_${r.id}`); state.existingEvents.add(`remote_${r.id}`);
return r; return r;
}) })
.map(r => remoteToFullCalendarEvent(r)); .map((r) => remoteToFullCalendarEvent(r));
userData.remotes = userData.remotes.concat(eventRemotes); userData.remotes = userData.remotes.concat(eventRemotes);
userData.remotesLoaded.push({ start, end }); userData.remotesLoaded.push({ start, end });
@ -173,20 +177,20 @@ export default {
} }
const eventRemotes = locals const eventRemotes = locals
.filter(r => !state.existingEvents.has(`locals_${r.id}`)) .filter((r) => !state.existingEvents.has(`locals_${r.id}`))
.map(r => { .map((r) => {
// add to existing ids // add to existing ids
state.existingEvents.add(`locals_${r.id}`); state.existingEvents.add(`locals_${r.id}`);
return r; return r;
}) })
.map(r => localsToFullCalendarEvent(r)); .map((r) => localsToFullCalendarEvent(r));
userData.locals = userData.locals.concat(eventRemotes); userData.locals = userData.locals.concat(eventRemotes);
userData.localsLoaded.push({ start, end }); userData.localsLoaded.push({ start, end });
}, },
// Location // Location
updateLocation(state, value) { updateLocation(state, value) {
console.log('### mutation: updateLocation', value); console.log("### mutation: updateLocation", value);
state.activity.location = value; state.activity.location = value;
}, },
addAvailableLocationGroup(state, group) { addAvailableLocationGroup(state, group) {

View File

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

View File

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

View File

@ -23,14 +23,22 @@
<label class="input-group-text" for="slotDuration" <label class="input-group-text" for="slotDuration"
>Durée des créneaux</label >Durée des créneaux</label
> >
<select v-model="slotDuration" id="slotDuration" class="form-select"> <select
v-model="slotDuration"
id="slotDuration"
class="form-select"
>
<option value="00:05:00">5 minutes</option> <option value="00:05:00">5 minutes</option>
<option value="00:10:00">10 minutes</option> <option value="00:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option> <option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option> <option value="00:30:00">30 minutes</option>
</select> </select>
<label class="input-group-text" for="slotMinTime">De</label> <label class="input-group-text" for="slotMinTime">De</label>
<select v-model="slotMinTime" id="slotMinTime" class="form-select"> <select
v-model="slotMinTime"
id="slotMinTime"
class="form-select"
>
<option value="00:00:00">0h</option> <option value="00:00:00">0h</option>
<option value="01:00:00">1h</option> <option value="01:00:00">1h</option>
<option value="02:00:00">2h</option> <option value="02:00:00">2h</option>
@ -46,7 +54,11 @@
<option value="12:00:00">12h</option> <option value="12:00:00">12h</option>
</select> </select>
<label class="input-group-text" for="slotMaxTime">À</label> <label class="input-group-text" for="slotMaxTime">À</label>
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select"> <select
v-model="slotMaxTime"
id="slotMaxTime"
class="form-select"
>
<option value="12:00:00">12h</option> <option value="12:00:00">12h</option>
<option value="13:00:00">13h</option> <option value="13:00:00">13h</option>
<option value="14:00:00">14h</option> <option value="14:00:00">14h</option>
@ -74,7 +86,9 @@
v-model="showWeekends" v-model="showWeekends"
/> />
</span> </span>
<label for="showHideWE" class="form-check-label input-group-text" <label
for="showHideWE"
class="form-check-label input-group-text"
>Week-ends</label >Week-ends</label
> >
</div> </div>
@ -88,7 +102,8 @@
arg.event.title arg.event.title
}}</b> }}</b>
<b v-else-if="arg.event.extendedProps.is === 'range'" <b v-else-if="arg.event.extendedProps.is === 'range'"
>{{ arg.timeText }} - {{ arg.event.extendedProps.locationName }}</b >{{ arg.timeText }} -
{{ arg.event.extendedProps.locationName }}</b
> >
<b v-else-if="arg.event.extendedProps.is === 'local'">{{ <b v-else-if="arg.event.extendedProps.is === 'local'">{{
arg.event.title arg.event.title
@ -106,29 +121,45 @@
<div id="copy-widget"> <div id="copy-widget">
<div class="container mt-2 mb-2"> <div class="container mt-2 mb-2">
<div class="row justify-content-between align-items-center mb-4"> <div class="row justify-content-between align-items-center mb-4">
<div class="col-xs-12 col-sm-3 col-md-2"> <div class="col-xs-12 col-sm-3 col-md-2">
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6> <h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
</div> </div>
<div class="col-xs-12 col-sm-9 col-md-2"> <div class="col-xs-12 col-sm-9 col-md-2">
<select v-model="dayOrWeek" id="dayOrWeek" class="form-select"> <select
v-model="dayOrWeek"
id="dayOrWeek"
class="form-select"
>
<option value="day">{{ $t("from_day_to_day") }}</option> <option value="day">{{ $t("from_day_to_day") }}</option>
<option value="week">{{ $t("from_week_to_week") }}</option> <option value="week">
{{ $t("from_week_to_week") }}
</option>
</select> </select>
</div> </div>
<template v-if="dayOrWeek === 'day'"> <template v-if="dayOrWeek === 'day'">
<div class="col-xs-12 col-sm-3 col-md-3"> <div class="col-xs-12 col-sm-3 col-md-3">
<input class="form-control" type="date" v-model="copyFrom" /> <input
class="form-control"
type="date"
v-model="copyFrom"
/>
</div> </div>
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron"> <div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
<i class="fa fa-angle-double-right"></i> <i class="fa fa-angle-double-right"></i>
</div> </div>
<div class="col-xs-12 col-sm-3 col-md-3"> <div class="col-xs-12 col-sm-3 col-md-3">
<input class="form-control" type="date" v-model="copyTo" /> <input
class="form-control"
type="date"
v-model="copyTo"
/>
</div> </div>
<div class="col-xs-12 col-sm-5 col-md-1"> <div class="col-xs-12 col-sm-5 col-md-1">
<button class="btn btn-action float-end" @click="copyDay"> <button
class="btn btn-action float-end"
@click="copyDay"
>
{{ $t("copy_range") }} {{ $t("copy_range") }}
</button> </button>
</div> </div>
@ -140,7 +171,11 @@
id="copyFromWeek" id="copyFromWeek"
class="form-select" class="form-select"
> >
<option v-for="w in lastWeeks" :value="w.value" :key="w.value"> <option
v-for="w in lastWeeks"
:value="w.value"
:key="w.value"
>
{{ w.text }} {{ w.text }}
</option> </option>
</select> </select>
@ -149,21 +184,31 @@
<i class="fa fa-angle-double-right"></i> <i class="fa fa-angle-double-right"></i>
</div> </div>
<div class="col-xs-12 col-sm-3 col-md-3"> <div class="col-xs-12 col-sm-3 col-md-3">
<select v-model="copyToWeek" id="copyToWeek" class="form-select"> <select
<option v-for="w in nextWeeks" :value="w.value" :key="w.value"> v-model="copyToWeek"
id="copyToWeek"
class="form-select"
>
<option
v-for="w in nextWeeks"
:value="w.value"
:key="w.value"
>
{{ w.text }} {{ w.text }}
</option> </option>
</select> </select>
</div> </div>
<div class="col-xs-12 col-sm-5 col-md-1"> <div class="col-xs-12 col-sm-5 col-md-1">
<button class="btn btn-action float-end" @click="copyWeek"> <button
class="btn btn-action float-end"
@click="copyWeek"
>
{{ $t("copy_range") }} {{ $t("copy_range") }}
</button> </button>
</div> </div>
</template> </template>
</div> </div>
</div> </div>
</div> </div>
<!-- not directly seen, but include in a modal --> <!-- not directly seen, but include in a modal -->
@ -224,7 +269,7 @@ interface Weeks {
const getMonday = (week: number): Date => { const getMonday = (week: number): Date => {
const lastMonday = new Date(); const lastMonday = new Date();
lastMonday.setDate( lastMonday.setDate(
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7 lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
); );
return lastMonday; return lastMonday;
}; };
@ -243,7 +288,7 @@ const lastWeeks = computed((): Weeks[] =>
value: dateToISO(lastMonday), value: dateToISO(lastMonday),
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`, text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
}; };
}) }),
); );
const nextWeeks = computed((): Weeks[] => const nextWeeks = computed((): Weeks[] =>
@ -253,7 +298,7 @@ const nextWeeks = computed((): Weeks[] =>
value: dateToISO(nextMonday), value: dateToISO(nextMonday),
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`, text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
}; };
}) }),
); );
const baseOptions = ref<CalendarOptions>({ const baseOptions = ref<CalendarOptions>({
@ -298,7 +343,9 @@ const pickedLocation = computed<Location | null>({
); );
}, },
set(newLocation: Location | null): void { set(newLocation: Location | null): void {
store.commit("locations/setLocationPicked", newLocation, { root: true }); store.commit("locations/setLocationPicked", newLocation, {
root: true,
});
}, },
}); });
@ -354,7 +401,7 @@ function onDatesSet(event: DatesSetArg): void {
function onDateSelect(event: DateSelectArg): void { function onDateSelect(event: DateSelectArg): void {
if (null === pickedLocation.value) { if (null === pickedLocation.value) {
window.alert( window.alert(
"Indiquez une localisation avant de créer une période de disponibilité." "Indiquez une localisation avant de créer une période de disponibilité.",
); );
return; return;
} }
@ -376,7 +423,7 @@ function onClickDelete(event: EventApi): void {
store.dispatch( store.dispatch(
"calendarRanges/deleteRange", "calendarRanges/deleteRange",
event.extendedProps.calendarRangeId event.extendedProps.calendarRangeId,
); );
} }

View File

@ -1,24 +1,26 @@
<template> <template>
<component :is="Teleport" to="body"> <component :is="Teleport" to="body">
<modal v-if="showModal" <modal v-if="showModal" @close="closeModal">
@close="closeModal">
<template v-slot:header> <template v-slot:header>
<h3>{{ 'Modifier le lieu' }}</h3> <h3>{{ "Modifier le lieu" }}</h3>
</template> </template>
<template v-slot:body> <template v-slot:body>
<div> <div></div>
</div>
<label>Localisation</label> <label>Localisation</label>
<vue-multiselect v-model="location" :options="locations" :label="'name'" :track-by="'id'"></vue-multiselect> <vue-multiselect
v-model="location"
:options="locations"
:label="'name'"
:track-by="'id'"
></vue-multiselect>
</template> </template>
<template v-slot:footer> <template v-slot:footer>
<button class="btn btn-save" @click="saveAndClose">{{ 'Enregistrer' }}</button> <button class="btn btn-save" @click="saveAndClose">
{{ "Enregistrer" }}
</button>
</template> </template>
</modal> </modal>
</component> </component>
</template> </template>
@ -34,18 +36,11 @@ import VueMultiselect from "vue-multiselect";
//import type {Teleport} from "vue"; //import type {Teleport} from "vue";
// see https://github.com/vuejs/core/issues/2855 // see https://github.com/vuejs/core/issues/2855
import { import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
Teleport as teleport_,
TeleportProps,
VNodeProps
} from 'vue'
const Teleport = teleport_ as {
new (): {
$props: VNodeProps & TeleportProps
}
}
const Teleport = teleport_ as new () => {
$props: VNodeProps & TeleportProps;
};
const store = useStore(key); const store = useStore(key);
@ -59,30 +54,36 @@ const locations = computed<Location[]>(() => {
}); });
const startEdit = function (event: EventApi): void { const startEdit = function (event: EventApi): void {
console.log('startEditing', event); console.log("startEditing", event);
calendarRangeId.value = event.extendedProps.calendarRangeId; calendarRangeId.value = event.extendedProps.calendarRangeId;
location.value = store.getters['locations/getLocationById'](event.extendedProps.locationId) || null; location.value =
store.getters["locations/getLocationById"](
event.extendedProps.locationId,
) || null;
console.log('new location value', location.value); console.log("new location value", location.value);
console.log('calendar range id', calendarRangeId.value); console.log("calendar range id", calendarRangeId.value);
showModal.value = true; showModal.value = true;
} };
const saveAndClose = function (e: Event): void { const saveAndClose = function (e: Event): void {
console.log('saveEditAndClose', e); console.log("saveEditAndClose", e);
store.dispatch('calendarRanges/patchRangeLocation', {location: location.value, calendarRangeId: calendarRangeId.value}) store
.then(_ => {showModal.value = false;}) .dispatch("calendarRanges/patchRangeLocation", {
} location: location.value,
calendarRangeId: calendarRangeId.value,
})
.then((_) => {
showModal.value = false;
});
};
const closeModal = function (_: any): void { const closeModal = function (_: any): void {
showModal.value = false; showModal.value = false;
} };
defineExpose({ startEdit }); defineExpose({ startEdit });
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -8,7 +8,8 @@ const appMessages = {
copy_range_from_to: "Copier les plages", copy_range_from_to: "Copier les plages",
from_day_to_day: "d'un jour à l'autre", from_day_to_day: "d'un jour à l'autre",
from_week_to_week: "d'une semaine à l'autre", from_week_to_week: "d'une semaine à l'autre",
copy_range_how_to: "Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.", copy_range_how_to:
"Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
new_range_to_save: "Nouvelles plages à enregistrer", new_range_to_save: "Nouvelles plages à enregistrer",
update_range_to_save: "Plages à modifier", update_range_to_save: "Plages à modifier",
delete_range_to_save: "Plages à supprimer", delete_range_to_save: "Plages à supprimer",
@ -20,9 +21,7 @@ const appMessages = {
week: "Semaine", week: "Semaine",
month: "Mois", month: "Mois",
today: "Aujourd'hui", today: "Aujourd'hui",
} },
}
export {
appMessages
}; };
export { appMessages };

View File

@ -1,9 +1,9 @@
import { createApp } from 'vue'; import { createApp } from "vue";
import { _createI18n } from '../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n' import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
import { appMessages } from './i18n' import { appMessages } from "./i18n";
import futureStore, {key} from './store/index' import futureStore, { key } from "./store/index";
import App2 from './App2.vue'; import App2 from "./App2.vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
futureStore().then((store) => { futureStore().then((store) => {
@ -14,6 +14,6 @@ futureStore().then((store) => {
}) })
.use(store, key) .use(store, key)
.use(i18n) .use(i18n)
.component('app', App2) .component("app", App2)
.mount('#myCalendar'); .mount("#myCalendar");
}); });

View File

@ -1,24 +1,26 @@
import 'es6-promise/auto'; import "es6-promise/auto";
import {Store, createStore} from 'vuex'; import { Store, createStore } from "vuex";
import { InjectionKey } from "vue"; import { InjectionKey } from "vue";
import me, {MeState} from './modules/me'; import me, { MeState } from "./modules/me";
import fullCalendar, {FullCalendarState} from './modules/fullcalendar'; import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
import calendarRanges, {CalendarRangesState} from './modules/calendarRanges'; import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
import calendarRemotes, {CalendarRemotesState} from './modules/calendarRemotes'; import calendarRemotes, {
CalendarRemotesState,
} from "./modules/calendarRemotes";
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user"; import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
import {User} from '../../../../../../ChillMainBundle/Resources/public/types'; import { User } from "../../../../../../ChillMainBundle/Resources/public/types";
import locations, { LocationState } from "./modules/location"; import locations, { LocationState } from "./modules/location";
import calendarLocals, { CalendarLocalsState } from "./modules/calendarLocals"; import calendarLocals, { CalendarLocalsState } from "./modules/calendarLocals";
const debug = process.env.NODE_ENV !== 'production'; const debug = process.env.NODE_ENV !== "production";
export interface State { export interface State {
calendarRanges: CalendarRangesState, calendarRanges: CalendarRangesState;
calendarRemotes: CalendarRemotesState, calendarRemotes: CalendarRemotesState;
calendarLocals: CalendarLocalsState, calendarLocals: CalendarLocalsState;
fullCalendar: FullCalendarState, fullCalendar: FullCalendarState;
me: MeState, me: MeState;
locations: LocationState locations: LocationState;
} }
export const key: InjectionKey<Store<State>> = Symbol(); export const key: InjectionKey<Store<State>> = Symbol();
@ -35,16 +37,20 @@ const futureStore = function(): Promise<Store<State>> {
calendarLocals, calendarLocals,
locations, locations,
}, },
mutations: {} mutations: {},
}); });
store.commit('me/setWhoAmi', user, {root: true}); store.commit("me/setWhoAmi", user, { root: true });
store.dispatch('locations/getLocations', null, {root: true}).then(_ => { store
return store.dispatch('locations/getCurrentLocation', null, {root: true}); .dispatch("locations/getLocations", null, { root: true })
.then((_) => {
return store.dispatch("locations/getCurrentLocation", null, {
root: true,
});
}); });
return Promise.resolve(store); return Promise.resolve(store);
}); });
} };
export default futureStore; export default futureStore;

View File

@ -1,33 +1,38 @@
import {State} from './../index'; import { State } from "./../index";
import {ActionContext, Module} from 'vuex'; import { ActionContext, Module } from "vuex";
import {CalendarLight} from '../../../../types'; import { CalendarLight } from "../../../../types";
import {fetchCalendarLocalForUser} from '../../../Calendar/api'; import { fetchCalendarLocalForUser } from "../../../Calendar/api";
import {EventInput} from '@fullcalendar/core'; import { EventInput } from "@fullcalendar/core";
import { localsToFullCalendarEvent } from "../../../Calendar/store/utils"; import { localsToFullCalendarEvent } from "../../../Calendar/store/utils";
import { TransportExceptionInterface } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; import { TransportExceptionInterface } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import { COLORS } from "../../../Calendar/const"; import { COLORS } from "../../../Calendar/const";
export interface CalendarLocalsState { export interface CalendarLocalsState {
locals: EventInput[], locals: EventInput[];
localsLoaded: {start: number, end: number}[], localsLoaded: { start: number; end: number }[];
localsIndex: Set<string>, localsIndex: Set<string>;
key: number key: number;
} }
type Context = ActionContext<CalendarLocalsState, State>; type Context = ActionContext<CalendarLocalsState, State>;
export default <Module<CalendarLocalsState, State>> { export default {
namespaced: true, namespaced: true,
state: (): CalendarLocalsState => ({ state: (): CalendarLocalsState => ({
locals: [], locals: [],
localsLoaded: [], localsLoaded: [],
localsIndex: new Set<string>(), localsIndex: new Set<string>(),
key: 0 key: 0,
}), }),
getters: { getters: {
isLocalsLoaded: (state: CalendarLocalsState) => ({start, end}: {start: Date, end: Date}): boolean => { isLocalsLoaded:
for (let range of state.localsLoaded) { (state: CalendarLocalsState) =>
if (start.getTime() === range.start && end.getTime() === range.end) { ({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.localsLoaded) {
if (
start.getTime() === range.start &&
end.getTime() === range.end
) {
return true; return true;
} }
} }
@ -37,11 +42,11 @@ export default <Module<CalendarLocalsState, State>> {
}, },
mutations: { mutations: {
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) { addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
console.log('addLocals', ranges); console.log("addLocals", ranges);
const toAdd = ranges const toAdd = ranges
.map(cr => localsToFullCalendarEvent(cr)) .map((cr) => localsToFullCalendarEvent(cr))
.filter(r => !state.localsIndex.has(r.id)); .filter((r) => !state.localsIndex.has(r.id));
toAdd.forEach((r) => { toAdd.forEach((r) => {
state.localsIndex.add(r.id); state.localsIndex.add(r.id);
@ -49,16 +54,25 @@ export default <Module<CalendarLocalsState, State>> {
}); });
state.key = state.key + toAdd.length; state.key = state.key + toAdd.length;
}, },
addLoaded(state: CalendarLocalsState, payload: {start: Date, end: Date}) { addLoaded(
state.localsLoaded.push({start: payload.start.getTime(), end: payload.end.getTime()}); state: CalendarLocalsState,
payload: { start: Date; end: Date },
) {
state.localsLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
}, },
}, },
actions: { actions: {
fetchLocals(ctx: Context, payload: {start: Date, end: Date}): Promise<null> { fetchLocals(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start; const start = payload.start;
const end = payload.end; const end = payload.end;
if (ctx.rootGetters['me/getMe'] === null) { if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(null); return Promise.resolve(null);
} }
@ -66,23 +80,30 @@ export default <Module<CalendarLocalsState, State>> {
return Promise.resolve(ctx.getters.getRangeSource); return Promise.resolve(ctx.getters.getRangeSource);
} }
ctx.commit('addLoaded', { ctx.commit("addLoaded", {
start: start, start: start,
end: end, end: end,
}); });
return fetchCalendarLocalForUser( return fetchCalendarLocalForUser(
ctx.rootGetters['me/getMe'], ctx.rootGetters["me/getMe"],
start, start,
end end,
) )
.then((remotes: CalendarLight[]) => { .then((remotes: CalendarLight[]) => {
// to be add when reactivity problem will be solve ? // to be add when reactivity problem will be solve ?
//ctx.commit('addRemotes', remotes); //ctx.commit('addRemotes', remotes);
const inputs = remotes const inputs = remotes
.map(cr => localsToFullCalendarEvent(cr)) .map((cr) => localsToFullCalendarEvent(cr))
.map(cr => ({...cr, backgroundColor: COLORS[0], textColor: 'black', editable: false})) .map((cr) => ({
ctx.commit('calendarRanges/addExternals', inputs, {root: true}); ...cr,
backgroundColor: COLORS[0],
textColor: "black",
editable: false,
}));
ctx.commit("calendarRanges/addExternals", inputs, {
root: true,
});
return Promise.resolve(null); return Promise.resolve(null);
}) })
.catch((e: TransportExceptionInterface) => { .catch((e: TransportExceptionInterface) => {
@ -90,6 +111,6 @@ export default <Module<CalendarLocalsState, State>> {
return Promise.resolve(null); return Promise.resolve(null);
}); });
} },
} },
}; } as Module<CalendarLocalsState, State>;

View File

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

View File

@ -1,33 +1,38 @@
import {State} from './../index'; import { State } from "./../index";
import {ActionContext, Module} from 'vuex'; import { ActionContext, Module } from "vuex";
import {CalendarRemote} from '../../../../types'; import { CalendarRemote } from "../../../../types";
import {fetchCalendarRemoteForUser} from '../../../Calendar/api'; import { fetchCalendarRemoteForUser } from "../../../Calendar/api";
import {EventInput} from '@fullcalendar/core'; import { EventInput } from "@fullcalendar/core";
import { remoteToFullCalendarEvent } from "../../../Calendar/store/utils"; import { remoteToFullCalendarEvent } from "../../../Calendar/store/utils";
import { TransportExceptionInterface } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; import { TransportExceptionInterface } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import { COLORS } from "../../../Calendar/const"; import { COLORS } from "../../../Calendar/const";
export interface CalendarRemotesState { export interface CalendarRemotesState {
remotes: EventInput[], remotes: EventInput[];
remotesLoaded: {start: number, end: number}[], remotesLoaded: { start: number; end: number }[];
remotesIndex: Set<string>, remotesIndex: Set<string>;
key: number key: number;
} }
type Context = ActionContext<CalendarRemotesState, State>; type Context = ActionContext<CalendarRemotesState, State>;
export default <Module<CalendarRemotesState, State>> { export default {
namespaced: true, namespaced: true,
state: (): CalendarRemotesState => ({ state: (): CalendarRemotesState => ({
remotes: [], remotes: [],
remotesLoaded: [], remotesLoaded: [],
remotesIndex: new Set<string>(), remotesIndex: new Set<string>(),
key: 0 key: 0,
}), }),
getters: { getters: {
isRemotesLoaded: (state: CalendarRemotesState) => ({start, end}: {start: Date, end: Date}): boolean => { isRemotesLoaded:
for (let range of state.remotesLoaded) { (state: CalendarRemotesState) =>
if (start.getTime() === range.start && end.getTime() === range.end) { ({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.remotesLoaded) {
if (
start.getTime() === range.start &&
end.getTime() === range.end
) {
return true; return true;
} }
} }
@ -37,11 +42,11 @@ export default <Module<CalendarRemotesState, State>> {
}, },
mutations: { mutations: {
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) { addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
console.log('addRemotes', ranges); console.log("addRemotes", ranges);
const toAdd = ranges const toAdd = ranges
.map(cr => remoteToFullCalendarEvent(cr)) .map((cr) => remoteToFullCalendarEvent(cr))
.filter(r => !state.remotesIndex.has(r.id)); .filter((r) => !state.remotesIndex.has(r.id));
toAdd.forEach((r) => { toAdd.forEach((r) => {
state.remotesIndex.add(r.id); state.remotesIndex.add(r.id);
@ -49,16 +54,25 @@ export default <Module<CalendarRemotesState, State>> {
}); });
state.key = state.key + toAdd.length; state.key = state.key + toAdd.length;
}, },
addLoaded(state: CalendarRemotesState, payload: {start: Date, end: Date}) { addLoaded(
state.remotesLoaded.push({start: payload.start.getTime(), end: payload.end.getTime()}); state: CalendarRemotesState,
payload: { start: Date; end: Date },
) {
state.remotesLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
}, },
}, },
actions: { actions: {
fetchRemotes(ctx: Context, payload: {start: Date, end: Date}): Promise<null> { fetchRemotes(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start; const start = payload.start;
const end = payload.end; const end = payload.end;
if (ctx.rootGetters['me/getMe'] === null) { if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(null); return Promise.resolve(null);
} }
@ -66,23 +80,30 @@ export default <Module<CalendarRemotesState, State>> {
return Promise.resolve(ctx.getters.getRangeSource); return Promise.resolve(ctx.getters.getRangeSource);
} }
ctx.commit('addLoaded', { ctx.commit("addLoaded", {
start: start, start: start,
end: end, end: end,
}); });
return fetchCalendarRemoteForUser( return fetchCalendarRemoteForUser(
ctx.rootGetters['me/getMe'], ctx.rootGetters["me/getMe"],
start, start,
end end,
) )
.then((remotes: CalendarRemote[]) => { .then((remotes: CalendarRemote[]) => {
// to be add when reactivity problem will be solve ? // to be add when reactivity problem will be solve ?
//ctx.commit('addRemotes', remotes); //ctx.commit('addRemotes', remotes);
const inputs = remotes const inputs = remotes
.map(cr => remoteToFullCalendarEvent(cr)) .map((cr) => remoteToFullCalendarEvent(cr))
.map(cr => ({...cr, backgroundColor: COLORS[0], textColor: 'black', editable: false})) .map((cr) => ({
ctx.commit('calendarRanges/addExternals', inputs, {root: true}); ...cr,
backgroundColor: COLORS[0],
textColor: "black",
editable: false,
}));
ctx.commit("calendarRanges/addExternals", inputs, {
root: true,
});
return Promise.resolve(null); return Promise.resolve(null);
}) })
.catch((e: TransportExceptionInterface) => { .catch((e: TransportExceptionInterface) => {
@ -90,6 +111,6 @@ export default <Module<CalendarRemotesState, State>> {
return Promise.resolve(null); return Promise.resolve(null);
}); });
} },
} },
}; } as Module<CalendarRemotesState, State>;

View File

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

View File

@ -1,6 +1,6 @@
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types"; import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
import {State} from '../index'; import { State } from "../index";
import {Module} from 'vuex'; import { Module } from "vuex";
import { getLocations } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/locations"; import { getLocations } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/locations";
import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user"; import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user";
@ -10,18 +10,20 @@ export interface LocationState {
currentLocation: Location | null; currentLocation: Location | null;
} }
export default <Module<LocationState, State>>{ export default {
namespaced: true, namespaced: true,
state: (): LocationState => { state: (): LocationState => {
return { return {
locations: [], locations: [],
locationPicked: null, locationPicked: null,
currentLocation: null, currentLocation: null,
} };
}, },
getters: { getters: {
getLocationById: (state) => (id: number): Location|undefined => { getLocationById:
return state.locations.find(l => l.id === id); (state) =>
(id: number): Location | undefined => {
return state.locations.find((l) => l.id === id);
}, },
}, },
mutations: { mutations: {
@ -34,7 +36,8 @@ export default <Module<LocationState, State>>{
return; return;
} }
state.locationPicked = state.locations.find(l => l.id === location.id) || null; state.locationPicked =
state.locations.find((l) => l.id === location.id) || null;
}, },
setCurrentLocation(state, location: Location | null): void { setCurrentLocation(state, location: Location | null): void {
if (null === location) { if (null === location) {
@ -42,21 +45,21 @@ export default <Module<LocationState, State>>{
return; return;
} }
state.currentLocation = state.locations.find(l => l.id === location.id) || null; state.currentLocation =
} state.locations.find((l) => l.id === location.id) || null;
},
}, },
actions: { actions: {
getLocations(ctx): Promise<void> { getLocations(ctx): Promise<void> {
return getLocations().then(locations => { return getLocations().then((locations) => {
ctx.commit('setLocations', locations); ctx.commit("setLocations", locations);
return Promise.resolve(); return Promise.resolve();
}); });
}, },
getCurrentLocation(ctx): Promise<void> { getCurrentLocation(ctx): Promise<void> {
return whereami().then(location => { return whereami().then((location) => {
ctx.commit('setCurrentLocation', location); ctx.commit("setCurrentLocation", location);
}) });
} },
} },
} } as Module<LocationState, State>;

View File

@ -1,9 +1,9 @@
import {State} from './../index'; import { State } from "./../index";
import {User} from '../../../../../../../ChillMainBundle/Resources/public/types'; import { User } from "../../../../../../../ChillMainBundle/Resources/public/types";
import {ActionContext} from 'vuex'; import { ActionContext } from "vuex";
export interface MeState { export interface MeState {
me: User|null, me: User | null;
} }
type Context = ActionContext<MeState, State>; type Context = ActionContext<MeState, State>;
@ -22,8 +22,5 @@ export default {
setWhoAmi(state: MeState, me: User) { setWhoAmi(state: MeState, me: User) {
state.me = me; state.me = me;
}, },
} },
}; };

View File

@ -6,20 +6,22 @@
const fetchCalendarRanges = () => { const fetchCalendarRanges = () => {
return Promise.resolve([]); return Promise.resolve([]);
const url = `/api/1.0/calendar/calendar-range-available.json`; const url = `/api/1.0/calendar/calendar-range-available.json`;
return fetch(url) return fetch(url).then((response) => {
.then(response => { if (response.ok) {
if (response.ok) { return response.json(); } return response.json();
throw Error('Error with request resource response'); }
throw Error("Error with request resource response");
}); });
}; };
const fetchCalendarRangesByUser = (userId) => { const fetchCalendarRangesByUser = (userId) => {
return Promise.resolve([]); return Promise.resolve([]);
const url = `/api/1.0/calendar/calendar-range-available.json?user=${userId}`; const url = `/api/1.0/calendar/calendar-range-available.json?user=${userId}`;
return fetch(url) return fetch(url).then((response) => {
.then(response => { if (response.ok) {
if (response.ok) { return response.json(); } return response.json();
throw Error('Error with request resource response'); }
throw Error("Error with request resource response");
}); });
}; };
@ -31,14 +33,14 @@ const fetchCalendarRangesByUser = (userId) => {
const fetchCalendar = (mainUserId) => { const fetchCalendar = (mainUserId) => {
return Promise.resolve([]); return Promise.resolve([]);
const url = `/api/1.0/calendar/calendar.json?main_user=${mainUserId}&item_per_page=1000`; const url = `/api/1.0/calendar/calendar.json?main_user=${mainUserId}&item_per_page=1000`;
return fetch(url) return fetch(url).then((response) => {
.then(response => { if (response.ok) {
if (response.ok) { return response.json(); } return response.json();
throw Error('Error with request resource response'); }
throw Error("Error with request resource response");
}); });
}; };
/* /*
* Endpoint chill_api_single_calendar_range__entity_create * Endpoint chill_api_single_calendar_range__entity_create
* method POST, post CalendarRange entity * method POST, post CalendarRange entity
@ -46,14 +48,16 @@ const fetchCalendar = (mainUserId) => {
const postCalendarRange = (body) => { const postCalendarRange = (body) => {
const url = `/api/1.0/calendar/calendar-range.json?`; const url = `/api/1.0/calendar/calendar-range.json?`;
return fetch(url, { return fetch(url, {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json;charset=utf-8' "Content-Type": "application/json;charset=utf-8",
}, },
body: JSON.stringify(body) body: JSON.stringify(body),
}).then(response => { }).then((response) => {
if (response.ok) { return response.json(); } if (response.ok) {
throw Error('Error with request resource response'); return response.json();
}
throw Error("Error with request resource response");
}); });
}; };
@ -62,17 +66,19 @@ const postCalendarRange = (body) => {
* method PATCH, patch CalendarRange entity * method PATCH, patch CalendarRange entity
*/ */
const patchCalendarRange = (id, body) => { const patchCalendarRange = (id, body) => {
console.log(body) console.log(body);
const url = `/api/1.0/calendar/calendar-range/${id}.json`; const url = `/api/1.0/calendar/calendar-range/${id}.json`;
return fetch(url, { return fetch(url, {
method: 'PATCH', method: "PATCH",
headers: { headers: {
'Content-Type': 'application/json;charset=utf-8' "Content-Type": "application/json;charset=utf-8",
}, },
body: JSON.stringify(body) body: JSON.stringify(body),
}).then(response => { }).then((response) => {
if (response.ok) { return response.json(); } if (response.ok) {
throw Error('Error with request resource response'); return response.json();
}
throw Error("Error with request resource response");
}); });
}; };
@ -83,13 +89,15 @@ const patchCalendarRange = (id, body) => {
const deleteCalendarRange = (id) => { const deleteCalendarRange = (id) => {
const url = `/api/1.0/calendar/calendar-range/${id}.json`; const url = `/api/1.0/calendar/calendar-range/${id}.json`;
return fetch(url, { return fetch(url, {
method: 'DELETE', method: "DELETE",
headers: { headers: {
'Content-Type': 'application/json;charset=utf-8' "Content-Type": "application/json;charset=utf-8",
}, },
}).then(response => { }).then((response) => {
if (response.ok) { return response.json(); } if (response.ok) {
throw Error('Error with request resource response'); return response.json();
}
throw Error("Error with request resource response");
}); });
}; };
@ -99,5 +107,5 @@ export {
fetchCalendarRangesByUser, fetchCalendarRangesByUser,
postCalendarRange, postCalendarRange,
patchCalendarRange, patchCalendarRange,
deleteCalendarRange deleteCalendarRange,
}; };

View File

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

View File

@ -1,17 +1,14 @@
import { multiSelectMessages } from 'ChillMainAssets/vuejs/_js/i18n' import { multiSelectMessages } from "ChillMainAssets/vuejs/_js/i18n";
const calendarUserSelectorMessages = { const calendarUserSelectorMessages = {
fr: { fr: {
choose_your_calendar_user: "Afficher les plages de disponibilités", choose_your_calendar_user: "Afficher les plages de disponibilités",
select_user: "Sélectionnez des calendriers", select_user: "Sélectionnez des calendriers",
show_my_calendar: "Afficher mon calendrier", show_my_calendar: "Afficher mon calendrier",
show_weekends: "Afficher les week-ends" show_weekends: "Afficher les week-ends",
} },
}; };
Object.assign(calendarUserSelectorMessages.fr, multiSelectMessages.fr); Object.assign(calendarUserSelectorMessages.fr, multiSelectMessages.fr);
export { export { calendarUserSelectorMessages };
calendarUserSelectorMessages
};

View File

@ -1,14 +1,25 @@
// this file loads all assets from the Chill calendar bundle // this file loads all assets from the Chill calendar bundle
module.exports = function (encore, entries) { module.exports = function (encore, entries) {
entries.push(__dirname + "/Resources/public/chill/chill.js");
entries.push(__dirname + '/Resources/public/chill/chill.js');
encore.addAliases({ encore.addAliases({
ChillCalendarAssets: __dirname + '/Resources/public' ChillCalendarAssets: __dirname + "/Resources/public",
}); });
encore.addEntry('vue_calendar', __dirname + '/Resources/public/vuejs/Calendar/index.js'); encore.addEntry(
encore.addEntry('vue_mycalendarrange', __dirname + '/Resources/public/vuejs/MyCalendarRange/index2.ts'); "vue_calendar",
encore.addEntry('page_calendar', __dirname + '/Resources/public/chill/index.js'); __dirname + "/Resources/public/vuejs/Calendar/index.js",
encore.addEntry('mod_answer', __dirname + '/Resources/public/module/Invite/answer.js'); );
encore.addEntry(
"vue_mycalendarrange",
__dirname + "/Resources/public/vuejs/MyCalendarRange/index2.ts",
);
encore.addEntry(
"page_calendar",
__dirname + "/Resources/public/chill/index.js",
);
encore.addEntry(
"mod_answer",
__dirname + "/Resources/public/module/Invite/answer.js",
);
}; };

View File

@ -3,8 +3,6 @@ import { fetchResults } from "ChillMainAssets/lib/api/apiMethods.ts";
const fetchTemplates = (entityClass) => { const fetchTemplates = (entityClass) => {
let fqdnEntityClass = encodeURI(entityClass); let fqdnEntityClass = encodeURI(entityClass);
return fetchResults(`/api/1.0/docgen/templates/by-entity/${fqdnEntityClass}`); return fetchResults(`/api/1.0/docgen/templates/by-entity/${fqdnEntityClass}`);
}
export {
fetchTemplates
}; };
export { fetchTemplates };

View File

@ -1,12 +1,11 @@
const buildLink = function (templateId, entityId, entityClass) { const buildLink = function (templateId, entityId, entityClass) {
const const entityIdEncoded = encodeURI(entityId),
entityIdEncoded = encodeURI(entityId), returnPath = encodeURIComponent(
returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash), window.location.pathname + window.location.search + window.location.hash,
),
entityClassEncoded = encodeURI(entityClass), entityClassEncoded = encodeURI(entityClass),
url = `/fr/doc/gen/generate/from/${templateId}/for/${entityClassEncoded}/${entityIdEncoded}?returnPath=${returnPath}` url = `/fr/doc/gen/generate/from/${templateId}/for/${entityClassEncoded}/${entityIdEncoded}?returnPath=${returnPath}`;
; console.log("computed Url");
console.log('computed Url');
return url; return url;
}; };

View File

@ -1,15 +1,15 @@
import {createApp} from 'vue'; import { createApp } from "vue";
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue'; import PickTemplate from "ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue";
import {fetchTemplates} from 'ChillDocGeneratorAssets/api/pickTemplate.js'; import { fetchTemplates } from "ChillDocGeneratorAssets/api/pickTemplate.js";
import {_createI18n} from 'ChillMainAssets/vuejs/_js/i18n'; import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
const i18n = _createI18n({}); const i18n = _createI18n({});
document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => { document.querySelectorAll("div[data-docgen-template-picker]").forEach((el) => {
fetchTemplates(el.dataset.entityClass).then(templates => { fetchTemplates(el.dataset.entityClass).then((templates) => {
let let picker = {
picker = { template:
template: '<pick-template :templates="this.templates" :entityId="this.entityId"></pick-template>', '<pick-template :templates="this.templates" :entityId="this.entityId"></pick-template>',
components: { components: {
PickTemplate, PickTemplate,
}, },
@ -17,11 +17,9 @@ document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => {
return { return {
templates: templates, templates: templates,
entityId: el.dataset.entityId, entityId: el.dataset.entityId,
} };
}, },
} };
;
createApp(picker).use(i18n).mount(el); createApp(picker).use(i18n).mount(el);
}) });
}); });

View File

@ -2,26 +2,44 @@
<div> <div>
<template v-if="templates.length > 0"> <template v-if="templates.length > 0">
<slot name="title"> <slot name="title">
<h2>{{ $t('generate_document')}}</h2> <h2>{{ $t("generate_document") }}</h2>
</slot> </slot>
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<slot name="label"> <slot name="label">
<label>{{ $t('select_a_template') }}</label> <label>{{ $t("select_a_template") }}</label>
</slot> </slot>
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
<div class="input-group mb-3"> <div class="input-group mb-3">
<select class="form-select" v-model="template"> <select class="form-select" v-model="template">
<option disabled selected value="">{{ $t('choose_a_template') }}</option> <option disabled selected value="">
{{ $t("choose_a_template") }}
</option>
<template v-for="t in templates" :key="t.id"> <template v-for="t in templates" :key="t.id">
<option :value="t.id" >{{ t.name.fr || 'Aucun nom défini' }}</option> <option :value="t.id">
{{ t.name.fr || "Aucun nom défini" }}
</option>
</template> </template>
</select> </select>
<a v-if="canGenerate" class="btn btn-update btn-sm change-icon" :href="buildUrlGenerate" @click.prevent="clickGenerate($event, buildUrlGenerate)"><i class="fa fa-fw fa-cog"></i></a> <a
<a v-else class="btn btn-update btn-sm change-icon" href="#" disabled ><i class="fa fa-fw fa-cog"></i></a> v-if="canGenerate"
class="btn btn-update btn-sm change-icon"
:href="buildUrlGenerate"
@click.prevent="
clickGenerate($event, buildUrlGenerate)
"
><i class="fa fa-fw fa-cog"
/></a>
<a
v-else
class="btn btn-update btn-sm change-icon"
href="#"
disabled
><i class="fa fa-fw fa-cog"
/></a>
</div> </div>
</div> </div>
</div> </div>
@ -30,16 +48,13 @@
<p>{{ getDescription }}</p> <p>{{ getDescription }}</p>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
</div> </div>
</template> </template>
<script> <script>
import { buildLink } from "ChillDocGeneratorAssets/lib/document-generator";
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
export default { export default {
name: "PickTemplate", name: "PickTemplate",
@ -57,13 +72,13 @@ export default {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}
}, },
emits: ['goToGenerateDocument'], },
emits: ["goToGenerateDocument"],
data() { data() {
return { return {
template: null, template: null,
} };
}, },
computed: { computed: {
canGenerate() { canGenerate() {
@ -78,21 +93,21 @@ export default {
}, },
getDescription() { getDescription() {
if (null === this.template) { if (null === this.template) {
return ''; return "";
} }
let desc = this.templates.find(t => t.id === this.template); let desc = this.templates.find((t) => t.id === this.template);
if (null === desc) { if (null === desc) {
return ''; return "";
} }
return desc.description || ''; return desc.description || "";
}, },
buildUrlGenerate() { buildUrlGenerate() {
if (null === this.template) { if (null === this.template) {
return '#'; return "#";
} }
return buildLink(this.template, this.entityId, this.entityClass); return buildLink(this.template, this.entityId, this.entityClass);
} },
}, },
methods: { methods: {
clickGenerate(event, link) { clickGenerate(event, link) {
@ -100,21 +115,23 @@ export default {
window.location.assign(link); window.location.assign(link);
} }
this.$emit('goToGenerateDocument', {event, link, template: this.template}); this.$emit("goToGenerateDocument", {
event,
link,
template: this.template,
});
}, },
}, },
i18n: { i18n: {
messages: { messages: {
fr: { fr: {
generate_document: 'Générer un document', generate_document: "Générer un document",
select_a_template: 'Choisir un modèle', select_a_template: "Choisir un modèle",
choose_a_template: 'Choisir', choose_a_template: "Choisir",
} },
} },
} },
} };
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -1,8 +1,11 @@
// this file loads all assets from the Chill DocGenerator bundle // this file loads all assets from the Chill DocGenerator bundle
module.exports = function (encore, entries) { module.exports = function (encore, entries) {
encore.addAliases({ encore.addAliases({
ChillDocGeneratorAssets: __dirname + '/Resources/public' ChillDocGeneratorAssets: __dirname + "/Resources/public",
}); });
encore.addEntry('mod_docgen_picktemplate', __dirname + '/Resources/public/module/PickTemplate/index.js'); encore.addEntry(
"mod_docgen_picktemplate",
__dirname + "/Resources/public/module/PickTemplate/index.js",
);
}; };

View File

@ -1,10 +1,9 @@
var mime = require('mime'); var mime = require("mime");
var algo = 'AES-CBC'; var algo = "AES-CBC";
var initializeButtons = (root) => { var initializeButtons = (root) => {
var var buttons = root.querySelectorAll("a[data-download-button]");
buttons = root.querySelectorAll('a[data-download-button]');
for (let i = 0; i < buttons.length; i++) { for (let i = 0; i < buttons.length; i++) {
initialize(buttons[i]); initialize(buttons[i]);
@ -12,18 +11,17 @@ var initializeButtons = (root) => {
}; };
var initialize = (button) => { var initialize = (button) => {
button.addEventListener('click', onClick); button.addEventListener("click", onClick);
}; };
var onClick = e => download(e.target); var onClick = (e) => download(e.target);
var download = (button) => { var download = (button) => {
var var keyData = JSON.parse(button.dataset.key),
keyData = JSON.parse(button.dataset.key),
ivData = JSON.parse(button.dataset.iv), ivData = JSON.parse(button.dataset.iv),
iv = new Uint8Array(ivData), iv = new Uint8Array(ivData),
urlGenerator = button.dataset.tempUrlGetGenerator, urlGenerator = button.dataset.tempUrlGetGenerator,
hasFilename = 'filename' in button.dataset, hasFilename = "filename" in button.dataset,
filename = button.dataset.filename, filename = button.dataset.filename,
labelPreparing = button.dataset.labelPreparing, labelPreparing = button.dataset.labelPreparing,
labelReady = button.dataset.labelReady, labelReady = button.dataset.labelReady,
@ -31,65 +29,70 @@ var download = (button) => {
extension = mime.getExtension(mimeType), extension = mime.getExtension(mimeType),
decryptError = "Error while decrypting file", decryptError = "Error while decrypting file",
fetchError = "Error while fetching file", fetchError = "Error while fetching file",
key, url key,
; url;
button.textContent = labelPreparing; button.textContent = labelPreparing;
window.fetch(urlGenerator) window
.fetch(urlGenerator)
.then((r) => { .then((r) => {
if (r.ok) { if (r.ok) {
return r.json(); return r.json();
} else { } else {
throw new Error("error while downloading url " + r.status + " " + r.statusText); throw new Error(
"error while downloading url " + r.status + " " + r.statusText,
);
} }
}) })
.then(data => { .then((data) => {
return window.fetch(data.url); return window.fetch(data.url);
}) })
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return response.arrayBuffer(); return response.arrayBuffer();
} }
throw new Error(response.status + response.statusText); throw new Error(response.status + response.statusText);
}) })
.then(buffer => { .then((buffer) => {
if (keyData.alg !== undefined) { if (keyData.alg !== undefined) {
return window.crypto.subtle return window.crypto.subtle
.importKey('jwk', keyData, { name: algo, iv: iv}, false, ['decrypt']) .importKey("jwk", keyData, { name: algo, iv: iv }, false, ["decrypt"])
.then(key => { .then((key) => {
return window.crypto.subtle.decrypt({ name: algo, iv: iv }, key, buffer); return window.crypto.subtle.decrypt(
{ name: algo, iv: iv },
key,
buffer,
);
}); });
} }
return Promise.resolve(buffer); return Promise.resolve(buffer);
}) })
.then(decrypted => { .then((decrypted) => {
var var blob = new Blob([decrypted], { type: mimeType }),
blob = new Blob([decrypted], { type: mimeType }), url = window.URL.createObjectURL(blob);
url = window.URL.createObjectURL(blob)
;
button.href = url; button.href = url;
button.target = '_blank'; button.target = "_blank";
button.type = mimeType; button.type = mimeType;
button.textContent = labelReady; button.textContent = labelReady;
if (hasFilename) { if (hasFilename) {
button.download = filename; button.download = filename;
if (extension !== false) { if (extension !== false) {
button.download = button.download + '.' + extension; button.download = button.download + "." + extension;
} }
} }
button.removeEventListener('click', onClick); button.removeEventListener("click", onClick);
button.click(); button.click();
}) })
.catch(error => { .catch((error) => {
button.textContent = ""; button.textContent = "";
button.appendChild(document.createTextNode("error while handling decrypted file")); button.appendChild(
}) document.createTextNode("error while handling decrypted file"),
; );
});
}; };
window.addEventListener('load', function(e) { window.addEventListener("load", function (e) {
initializeButtons(e.target); initializeButtons(e.target);
}); });

View File

@ -1,2 +1,2 @@
require('./uploader.js'); require("./uploader.js");
require('./downloader.js'); require("./downloader.js");

View File

@ -1,7 +1,6 @@
var algo = 'AES-CBC'; var algo = "AES-CBC";
import Dropzone from 'dropzone'; import Dropzone from "dropzone";
import { initializeButtons } from './downloader.js'; import { initializeButtons } from "./downloader.js";
/** /**
* *
@ -23,8 +22,8 @@ import { initializeButtons } from './downloader.js';
// load css // load css
//require('dropzone/dist/basic.css'); //require('dropzone/dist/basic.css');
require('dropzone/dist/dropzone.css'); require("dropzone/dist/dropzone.css");
require('./index.scss'); require("./index.scss");
// //
// disable dropzone autodiscover // disable dropzone autodiscover
@ -32,22 +31,21 @@ Dropzone.autoDiscover = false;
var keyDefinition = { var keyDefinition = {
name: algo, name: algo,
length: 256 length: 256,
}; };
var searchForZones = function (root) { var searchForZones = function (root) {
var zones = root.querySelectorAll('div[data-stored-object]'); var zones = root.querySelectorAll("div[data-stored-object]");
for (let i = 0; i < zones.length; i++) { for (let i = 0; i < zones.length; i++) {
initialize(zones[i]); initialize(zones[i]);
} }
}; };
var getUploadUrl = function (zoneData, files) { var getUploadUrl = function (zoneData, files) {
var var generateTempUrlPost = zoneData.zone.querySelector(
generateTempUrlPost = zoneData.zone.querySelector('input[data-async-file-upload]').dataset.generateTempUrlPost, "input[data-async-file-upload]",
oReq = new XMLHttpRequest() ).dataset.generateTempUrlPost,
; oReq = new XMLHttpRequest();
// arg, dropzone, you cannot handle async upload... // arg, dropzone, you cannot handle async upload...
oReq.open("GET", generateTempUrlPost, false); oReq.open("GET", generateTempUrlPost, false);
oReq.send(); oReq.send();
@ -62,32 +60,37 @@ var getUploadUrl = function(zoneData, files) {
}; };
var encryptFile = function (originalFile, zoneData, done) { var encryptFile = function (originalFile, zoneData, done) {
var var iv = crypto.getRandomValues(new Uint8Array(16)),
iv = crypto.getRandomValues(new Uint8Array(16)),
reader = new FileReader(), reader = new FileReader(),
jsKey, rawKey jsKey,
; rawKey;
zoneData.originalType = originalFile.type; zoneData.originalType = originalFile.type;
reader.onload = e => { reader.onload = (e) => {
window.crypto.subtle.generateKey(keyDefinition, true, [ "encrypt", "decrypt" ]) window.crypto.subtle
.then(key => { .generateKey(keyDefinition, true, ["encrypt", "decrypt"])
.then((key) => {
jsKey = key; jsKey = key;
// we register the key somwhere // we register the key somwhere
return window.crypto.subtle.exportKey('jwk', key); return window.crypto.subtle.exportKey("jwk", key);
}).then(exportedKey => { })
.then((exportedKey) => {
rawKey = exportedKey; rawKey = exportedKey;
// we start encryption // we start encryption
return window.crypto.subtle.encrypt({ name: algo, iv: iv}, jsKey, e.target.result); return window.crypto.subtle.encrypt(
{ name: algo, iv: iv },
jsKey,
e.target.result,
);
}) })
.then(encrypted => { .then((encrypted) => {
zoneData.crypto = { zoneData.crypto = {
jsKey: jsKey, jsKey: jsKey,
rawKey: rawKey, rawKey: rawKey,
iv: iv iv: iv,
}; };
done(new File([encrypted], zoneData.suffix)); done(new File([encrypted], zoneData.suffix));
@ -98,12 +101,11 @@ var encryptFile = function(originalFile, zoneData, done) {
}; };
var addBelowButton = (btn, zone, zoneData) => { var addBelowButton = (btn, zone, zoneData) => {
let let belowZone = zone.querySelector(".chill-dropzone__below-zone");
belowZone = zone.querySelector('.chill-dropzone__below-zone');
if (belowZone === null) { if (belowZone === null) {
belowZone = document.createElement('div'); belowZone = document.createElement("div");
belowZone.classList.add('chill-dropzone__below-zone'); belowZone.classList.add("chill-dropzone__below-zone");
zone.appendChild(belowZone); zone.appendChild(belowZone);
} }
@ -111,14 +113,13 @@ var addBelowButton = (btn, zone, zoneData) => {
}; };
var createZone = (zone, zoneData) => { var createZone = (zone, zoneData) => {
var var created = document.createElement("div"),
created = document.createElement('div'), initMessage = document.createElement("div"),
initMessage = document.createElement('div'),
initContent = zone.dataset.labelInitMessage, initContent = zone.dataset.labelInitMessage,
dropzoneI; dropzoneI;
created.classList.add('dropzone'); created.classList.add("dropzone");
initMessage.classList.add('dz-message'); initMessage.classList.add("dz-message");
initMessage.appendChild(document.createTextNode(initContent)); initMessage.appendChild(document.createTextNode(initContent));
console.log(Dropzone); console.log(Dropzone);
dropzoneI = new Dropzone(created, { dropzoneI = new Dropzone(created, {
@ -139,7 +140,7 @@ var createZone = (zone, zoneData) => {
}, },
renameFile: function (file) { renameFile: function (file) {
return zoneData.suffix; return zoneData.suffix;
} },
}); });
dropzoneI.on("sending", function (file, xhr, formData) { dropzoneI.on("sending", function (file, xhr, formData) {
@ -156,7 +157,7 @@ var createZone = (zone, zoneData) => {
}); });
dropzoneI.on("addedfile", function (file) { dropzoneI.on("addedfile", function (file) {
if (zoneData.hasOwnProperty('currentFile')) { if (zoneData.hasOwnProperty("currentFile")) {
dropzoneI.removeFile(zoneData.currentFile); dropzoneI.removeFile(zoneData.currentFile);
} }
}); });
@ -170,19 +171,20 @@ var createZone = (zone, zoneData) => {
let event = new CustomEvent("chill_dropzone_initialized", { let event = new CustomEvent("chill_dropzone_initialized", {
detail: { detail: {
dropzone: dropzoneI, dropzone: dropzoneI,
zoneData: zoneData zoneData: zoneData,
} },
}); });
window.dispatchEvent(event); window.dispatchEvent(event);
}; };
var initialize = function (zone) { var initialize = function (zone) {
var var allowRemove = zone.dataset.allowRemove,
allowRemove = zone.dataset.allowRemove, zoneData = {
zoneData = { zone: zone, suffix: createFilename(), allowRemove: allowRemove, old: null } zone: zone,
; suffix: createFilename(),
allowRemove: allowRemove,
old: null,
};
if (hasDataInForm(zone, zoneData)) { if (hasDataInForm(zone, zoneData)) {
insertRemoveButton(zone, zoneData); insertRemoveButton(zone, zoneData);
insertDownloadButton(zone, zoneData); insertDownloadButton(zone, zoneData);
@ -193,7 +195,8 @@ var initialize = function(zone) {
var createFilename = () => { var createFilename = () => {
var text = ""; var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length)); text += possible.charAt(Math.floor(Math.random() * possible.length));
@ -203,13 +206,10 @@ var createFilename = () => {
}; };
var storeDataInForm = (zone, zoneData) => { var storeDataInForm = (zone, zoneData) => {
var var inputKey = zone.querySelector("input[data-stored-object-key]"),
inputKey = zone.querySelector('input[data-stored-object-key]'), inputIv = zone.querySelector("input[data-stored-object-iv]"),
inputIv = zone.querySelector('input[data-stored-object-iv]'), inputObject = zone.querySelector("input[data-async-file-upload]"),
inputObject = zone.querySelector('input[data-async-file-upload]'), inputType = zone.querySelector("input[data-async-file-type]");
inputType = zone.querySelector('input[data-async-file-type]')
;
inputKey.value = JSON.stringify(zoneData.crypto.rawKey); inputKey.value = JSON.stringify(zoneData.crypto.rawKey);
inputIv.value = JSON.stringify(Array.from(zoneData.crypto.iv)); inputIv.value = JSON.stringify(Array.from(zoneData.crypto.iv));
inputType.value = zoneData.originalType; inputType.value = zoneData.originalType;
@ -219,15 +219,12 @@ var storeDataInForm = (zone, zoneData) => {
}; };
const restoreDataInForm = (zone, zoneData) => { const restoreDataInForm = (zone, zoneData) => {
var var inputKey = zone.querySelector("input[data-stored-object-key]"),
inputKey = zone.querySelector('input[data-stored-object-key]'), inputIv = zone.querySelector("input[data-stored-object-iv]"),
inputIv = zone.querySelector('input[data-stored-object-iv]'), inputObject = zone.querySelector("input[data-async-file-upload]"),
inputObject = zone.querySelector('input[data-async-file-upload]'), inputType = zone.querySelector("input[data-async-file-type]");
inputType = zone.querySelector('input[data-async-file-type]')
;
if (zoneData.old === null) { if (zoneData.old === null) {
console.log('should not have restored data'); console.log("should not have restored data");
return; return;
} }
@ -240,27 +237,21 @@ const restoreDataInForm = (zone, zoneData) => {
}; };
const hasDataInForm = (zone, zoneData) => { const hasDataInForm = (zone, zoneData) => {
var var inputObject = zone.querySelector("input[data-async-file-upload]");
inputObject = zone.querySelector('input[data-async-file-upload]')
;
return inputObject.value.length > 0; return inputObject.value.length > 0;
}; };
var removeDataInForm = (zone, zoneData) => { var removeDataInForm = (zone, zoneData) => {
var var inputKey = zone.querySelector("input[data-stored-object-key]"),
inputKey = zone.querySelector('input[data-stored-object-key]'), inputIv = zone.querySelector("input[data-stored-object-iv]"),
inputIv = zone.querySelector('input[data-stored-object-iv]'), inputObject = zone.querySelector("input[data-async-file-upload]"),
inputObject = zone.querySelector('input[data-async-file-upload]'), inputType = zone.querySelector("input[data-async-file-type]");
inputType = zone.querySelector('input[data-async-file-type]')
;
// store data for future usage // store data for future usage
zoneData.old = { zoneData.old = {
key: inputKey.value, key: inputKey.value,
iv: inputIv.value, iv: inputIv.value,
obj: inputObject.value, obj: inputObject.value,
type: inputType.value type: inputType.value,
}; };
// set blank values // set blank values
inputKey.value = ""; inputKey.value = "";
@ -272,30 +263,27 @@ var removeDataInForm = (zone, zoneData) => {
}; };
var insertRemoveButton = (zone, zoneData) => { var insertRemoveButton = (zone, zoneData) => {
var var removeButton = document.createElement("a"),
removeButton = document.createElement('a'), cancelButton = document.createElement("a"),
cancelButton = document.createElement('a'),
labelRemove = zone.dataset.dictRemove, labelRemove = zone.dataset.dictRemove,
labelCancel = 'Restaurer' labelCancel = "Restaurer";
; removeButton.classList.add("btn", "btn-delete");
removeButton.classList.add('btn', 'btn-delete');
removeButton.textContent = labelRemove; removeButton.textContent = labelRemove;
cancelButton.classList.add('btn', 'btn-cancel'); cancelButton.classList.add("btn", "btn-cancel");
cancelButton.textContent = labelCancel; cancelButton.textContent = labelCancel;
removeButton.addEventListener('click', (e) => { removeButton.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
if (zoneData.allowRemove === 'true') { if (zoneData.allowRemove === "true") {
removeDataInForm(zone, zoneData); removeDataInForm(zone, zoneData);
cancelButton.addEventListener('click', (e) => { cancelButton.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
restoreDataInForm(zone, zoneData); restoreDataInForm(zone, zoneData);
cancelButton.remove(); cancelButton.remove();
zone.querySelector('.dropzone').remove(); zone.querySelector(".dropzone").remove();
initialize(zone); initialize(zone);
}); });
@ -311,10 +299,7 @@ var insertRemoveButton = (zone, zoneData) => {
}; };
const removeDownloadButton = (zone, zoneData) => { const removeDownloadButton = (zone, zoneData) => {
var var existingButtons = zone.querySelectorAll("a[data-download-button]");
existingButtons = zone.querySelectorAll('a[data-download-button]')
;
// remove existing // remove existing
existingButtons.forEach(function (b) { existingButtons.forEach(function (b) {
b.remove(); b.remove();
@ -322,30 +307,27 @@ const removeDownloadButton = (zone, zoneData) => {
}; };
var insertDownloadButton = (zone, zoneData) => { var insertDownloadButton = (zone, zoneData) => {
var var existingButtons = zone.querySelectorAll("a[data-download-button]"),
existingButtons = zone.querySelectorAll('a[data-download-button]'), newButton = document.createElement("a"),
newButton = document.createElement('a'), inputKey = zone.querySelector("input[data-stored-object-key]"),
inputKey = zone.querySelector('input[data-stored-object-key]'), inputIv = zone.querySelector("input[data-stored-object-iv]"),
inputIv = zone.querySelector('input[data-stored-object-iv]'), inputObject = zone.querySelector("input[data-async-file-upload]"),
inputObject = zone.querySelector('input[data-async-file-upload]'), inputType = zone.querySelector("input[data-async-file-type]"),
inputType = zone.querySelector('input[data-async-file-type]'),
labelPreparing = zone.dataset.labelPreparing, labelPreparing = zone.dataset.labelPreparing,
labelQuietButton = zone.dataset.labelQuietButton, labelQuietButton = zone.dataset.labelQuietButton,
labelReady = zone.dataset.labelReady, labelReady = zone.dataset.labelReady,
tempUrlGenerator = zone.dataset.tempUrlGenerator, tempUrlGenerator = zone.dataset.tempUrlGenerator,
tempUrlGeneratorParams = new URLSearchParams() tempUrlGeneratorParams = new URLSearchParams();
;
// remove existing // remove existing
existingButtons.forEach(function (b) { existingButtons.forEach(function (b) {
b.remove(); b.remove();
}); });
if (inputObject.value === '') { if (inputObject.value === "") {
return; return;
} }
tempUrlGeneratorParams.append('object_name', inputObject.value); tempUrlGeneratorParams.append("object_name", inputObject.value);
newButton.dataset.downloadButton = true; newButton.dataset.downloadButton = true;
newButton.dataset.key = inputKey.value; newButton.dataset.key = inputKey.value;
@ -353,8 +335,9 @@ var insertDownloadButton = (zone, zoneData) => {
newButton.dataset.mimeType = inputType.value; newButton.dataset.mimeType = inputType.value;
newButton.dataset.labelPreparing = labelPreparing; newButton.dataset.labelPreparing = labelPreparing;
newButton.dataset.labelReady = labelReady; newButton.dataset.labelReady = labelReady;
newButton.dataset.tempUrlGetGenerator = tempUrlGenerator + '?' + tempUrlGeneratorParams.toString(); newButton.dataset.tempUrlGetGenerator =
newButton.classList.add('btn', 'btn-download', 'dz-bt-below-dropzone'); tempUrlGenerator + "?" + tempUrlGeneratorParams.toString();
newButton.classList.add("btn", "btn-download", "dz-bt-below-dropzone");
newButton.textContent = labelQuietButton; newButton.textContent = labelQuietButton;
addBelowButton(newButton, zone, zoneData); addBelowButton(newButton, zone, zoneData);
@ -362,11 +345,11 @@ var insertDownloadButton = (zone, zoneData) => {
initializeButtons(zone); initializeButtons(zone);
}; };
window.addEventListener('load', function(e) { window.addEventListener("load", function (e) {
searchForZones(document); searchForZones(document);
}); });
window.addEventListener('collection-add-entry', function(e) { window.addEventListener("collection-add-entry", function (e) {
searchForZones(e.detail.entry); searchForZones(e.detail.entry);
}); });

View File

@ -1,23 +1,58 @@
<template> <template>
<div v-if="isButtonGroupDisplayable" class="btn-group"> <div v-if="isButtonGroupDisplayable" class="btn-group">
<button :class="Object.assign({'btn': true, 'btn-outline-primary': true, 'dropdown-toggle': true, 'btn-sm': props.small})" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <button
:class="
Object.assign({
btn: true,
'btn-outline-primary': true,
'dropdown-toggle': true,
'btn-sm': props.small,
})
"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Actions Actions
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li v-if="isEditableOnline"> <li v-if="isEditableOnline">
<wopi-edit-button :stored-object="props.storedObject" :classes="{'dropdown-item': true}" :execute-before-leave="props.executeBeforeLeave"></wopi-edit-button> <wopi-edit-button
:stored-object="props.storedObject"
:classes="{ 'dropdown-item': true }"
:execute-before-leave="props.executeBeforeLeave"
></wopi-edit-button>
</li> </li>
<li v-if="isEditableOnDesktop"> <li v-if="isEditableOnDesktop">
<desktop-edit-button :classes="{'dropdown-item': true}" :edit-link="props.davLink" :expiration-link="props.davLinkExpiration"></desktop-edit-button> <desktop-edit-button
:classes="{ 'dropdown-item': true }"
:edit-link="props.davLink"
:expiration-link="props.davLinkExpiration"
></desktop-edit-button>
</li> </li>
<li v-if="isConvertibleToPdf"> <li v-if="isConvertibleToPdf">
<convert-button :stored-object="props.storedObject" :filename="filename" :classes="{'dropdown-item': true}"></convert-button> <convert-button
:stored-object="props.storedObject"
:filename="filename"
:classes="{ 'dropdown-item': true }"
></convert-button>
</li> </li>
<li v-if="isDownloadable"> <li v-if="isDownloadable">
<download-button :stored-object="props.storedObject" :at-version="props.storedObject.currentVersion" :filename="filename" :classes="{'dropdown-item': true}" :display-action-string-in-button="true"></download-button> <download-button
:stored-object="props.storedObject"
:at-version="props.storedObject.currentVersion"
:filename="filename"
:classes="{ 'dropdown-item': true }"
:display-action-string-in-button="true"
></download-button>
</li> </li>
<li v-if="isHistoryViewable"> <li v-if="isHistoryViewable">
<history-button :stored-object="props.storedObject" :can-edit="canEdit && props.storedObject._permissions.canEdit"></history-button> <history-button
:stored-object="props.storedObject"
:can-edit="
canEdit && props.storedObject._permissions.canEdit
"
></history-button>
</li> </li>
</ul> </ul>
</div> </div>
@ -27,31 +62,34 @@
<div v-else-if="'failure' === props.storedObject.status"> <div v-else-if="'failure' === props.storedObject.status">
<div class="btn btn-outline-danger">La génération a échoué</div> <div class="btn btn-outline-danger">La génération a échoué</div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted } from "vue"; import { computed, onMounted } from "vue";
import ConvertButton from "./StoredObjectButton/ConvertButton.vue"; import ConvertButton from "./StoredObjectButton/ConvertButton.vue";
import DownloadButton from "./StoredObjectButton/DownloadButton.vue"; import DownloadButton from "./StoredObjectButton/DownloadButton.vue";
import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue"; import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue";
import {is_extension_editable, is_extension_viewable, is_object_ready} from "./StoredObjectButton/helpers"; import {
is_extension_editable,
is_extension_viewable,
is_object_ready,
} from "./StoredObjectButton/helpers";
import { import {
StoredObject, StoredObject,
StoredObjectStatusChange, StoredObjectVersion, StoredObjectStatusChange,
WopiEditButtonExecutableBeforeLeaveFunction StoredObjectVersion,
WopiEditButtonExecutableBeforeLeaveFunction,
} from "../types"; } from "../types";
import DesktopEditButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DesktopEditButton.vue"; import DesktopEditButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DesktopEditButton.vue";
import HistoryButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton.vue"; import HistoryButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton.vue";
interface DocumentActionButtonsGroupConfig { interface DocumentActionButtonsGroupConfig {
storedObject: StoredObject, storedObject: StoredObject;
small?: boolean, small?: boolean;
canEdit?: boolean, canEdit?: boolean;
canDownload?: boolean, canDownload?: boolean;
canConvertPdf?: boolean, canConvertPdf?: boolean;
returnPath?: string, returnPath?: string;
/** /**
* Will be the filename displayed to the user when he·she download the document * Will be the filename displayed to the user when he·she download the document
@ -59,34 +97,41 @@ interface DocumentActionButtonsGroupConfig {
* *
* If not set, 'document' will be used. * If not set, 'document' will be used.
*/ */
filename?: string, filename?: string;
/** /**
* If set, will execute this function before leaving to the editor * If set, will execute this function before leaving to the editor
*/ */
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction, executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
/** /**
* a link to download and edit file using webdav * a link to download and edit file using webdav
*/ */
davLink?: string, davLink?: string;
/** /**
* the expiration date of the download, as a unix timestamp * the expiration date of the download, as a unix timestamp
*/ */
davLinkExpiration?: number, davLinkExpiration?: number;
} }
const emit = defineEmits<{ const emit =
(e: 'onStoredObjectStatusChange', newStatus: StoredObjectStatusChange): void defineEmits<
}>(); (
e: "onStoredObjectStatusChange",
newStatus: StoredObjectStatusChange,
) => void
>();
const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), { const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
small: false, small: false,
canEdit: true, canEdit: true,
canDownload: true, canDownload: true,
canConvertPdf: true, canConvertPdf: true,
returnPath: window.location.pathname + window.location.search + window.location.hash returnPath:
window.location.pathname +
window.location.search +
window.location.hash,
}); });
/** /**
@ -100,22 +145,32 @@ let tryiesForReady = 0;
const maxTryiesForReady = 120; const maxTryiesForReady = 120;
const isButtonGroupDisplayable = computed<boolean>(() => { const isButtonGroupDisplayable = computed<boolean>(() => {
return isDownloadable.value || isEditableOnline.value || isEditableOnDesktop.value || isConvertibleToPdf.value; return (
isDownloadable.value ||
isEditableOnline.value ||
isEditableOnDesktop.value ||
isConvertibleToPdf.value
);
}); });
const isDownloadable = computed<boolean>(() => { const isDownloadable = computed<boolean>(() => {
return props.storedObject.status === 'ready' return (
props.storedObject.status === "ready" ||
// happens when the stored object version is just added, but not persisted // happens when the stored object version is just added, but not persisted
|| (props.storedObject.currentVersion !== null && props.storedObject.status === 'empty') (props.storedObject.currentVersion !== null &&
props.storedObject.status === "empty")
);
}); });
const isEditableOnline = computed<boolean>(() => { const isEditableOnline = computed<boolean>(() => {
return props.storedObject.status === 'ready' return (
&& props.storedObject._permissions.canEdit props.storedObject.status === "ready" &&
&& props.canEdit props.storedObject._permissions.canEdit &&
&& props.storedObject.currentVersion !== null props.canEdit &&
&& is_extension_editable(props.storedObject.currentVersion.type) props.storedObject.currentVersion !== null &&
&& props.storedObject.currentVersion.persisted !== false; is_extension_editable(props.storedObject.currentVersion.type) &&
props.storedObject.currentVersion.persisted !== false
);
}); });
const isEditableOnDesktop = computed<boolean>(() => { const isEditableOnDesktop = computed<boolean>(() => {
@ -123,26 +178,28 @@ const isEditableOnDesktop = computed<boolean>(() => {
}); });
const isConvertibleToPdf = computed<boolean>(() => { const isConvertibleToPdf = computed<boolean>(() => {
return props.storedObject.status === 'ready' return (
&& props.storedObject._permissions.canSee props.storedObject.status === "ready" &&
&& props.canConvertPdf props.storedObject._permissions.canSee &&
&& props.storedObject.currentVersion !== null props.canConvertPdf &&
&& is_extension_viewable(props.storedObject.currentVersion.type) props.storedObject.currentVersion !== null &&
&& props.storedObject.currentVersion.type !== 'application/pdf' is_extension_viewable(props.storedObject.currentVersion.type) &&
&& props.storedObject.currentVersion.persisted !== false; props.storedObject.currentVersion.type !== "application/pdf" &&
props.storedObject.currentVersion.persisted !== false
);
}); });
const isHistoryViewable = computed<boolean>(() => { const isHistoryViewable = computed<boolean>(() => {
return props.storedObject.status === 'ready'; return props.storedObject.status === "ready";
}); });
const checkForReady = function (): void { const checkForReady = function (): void {
if ( if (
'ready' === props.storedObject.status "ready" === props.storedObject.status ||
|| 'empty' === props.storedObject.status "empty" === props.storedObject.status ||
|| 'failure' === props.storedObject.status "failure" === props.storedObject.status ||
// stop reloading if the page stays opened for a long time // stop reloading if the page stays opened for a long time
|| tryiesForReady > maxTryiesForReady tryiesForReady > maxTryiesForReady
) { ) {
return; return;
} }
@ -153,20 +210,19 @@ const checkForReady = function(): void {
}; };
const onObjectNewStatusCallback = async function (): Promise<void> { const onObjectNewStatusCallback = async function (): Promise<void> {
if (props.storedObject.status === "stored_object_created") {
if (props.storedObject.status === 'stored_object_created') {
return Promise.resolve(); return Promise.resolve();
} }
const new_status = await is_object_ready(props.storedObject); const new_status = await is_object_ready(props.storedObject);
if (props.storedObject.status !== new_status.status) { if (props.storedObject.status !== new_status.status) {
emit('onStoredObjectStatusChange', new_status); emit("onStoredObjectStatusChange", new_status);
return Promise.resolve(); return Promise.resolve();
} else if ('failure' === new_status.status) { } else if ("failure" === new_status.status) {
return Promise.resolve(); return Promise.resolve();
} }
if ('ready' !== new_status.status) { if ("ready" !== new_status.status) {
// we check for new status, unless it is ready // we check for new status, unless it is ready
checkForReady(); checkForReady();
} }
@ -176,9 +232,7 @@ const onObjectNewStatusCallback = async function(): Promise<void> {
onMounted(() => { onMounted(() => {
checkForReady(); checkForReady();
}) });
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -27,7 +27,9 @@
</modal> </modal>
</teleport> </teleport>
<div class="col-12 m-auto sticky-top"> <div class="col-12 m-auto sticky-top">
<div class="row justify-content-center border-bottom pdf-tools d-md-none"> <div
class="row justify-content-center border-bottom pdf-tools d-md-none"
>
<div class="col-5 text-center turn-page"> <div class="col-5 text-center turn-page">
<select <select
class="form-select form-select-sm" class="form-select form-select-sm"
@ -89,7 +91,10 @@
class="col-5 p-0 text-center turnSignature" class="col-5 p-0 text-center turnSignature"
> >
<button <button
:disabled="userSignatureZone === null || userSignatureZone?.index < 1" :disabled="
userSignatureZone === null ||
userSignatureZone?.index < 1
"
class="btn btn-light btn-sm" class="btn btn-light btn-sm"
@click="turnSignature(-1)" @click="turnSignature(-1)"
> >
@ -97,7 +102,9 @@
</button> </button>
<span>|</span> <span>|</span>
<button <button
:disabled="userSignatureZone?.index >= signature.zones.length - 1" :disabled="
userSignatureZone?.index >= signature.zones.length - 1
"
class="btn btn-light btn-sm" class="btn btn-light btn-sm"
@click="turnSignature(1)" @click="turnSignature(1)"
> >
@ -135,7 +142,10 @@
:title="$t('add_sign_zone')" :title="$t('add_sign_zone')"
> >
<template v-if="canvasEvent === 'add'"> <template v-if="canvasEvent === 'add'">
<div class="spinner-border spinner-border-sm" role="status"> <div
class="spinner-border spinner-border-sm"
role="status"
>
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">Loading...</span>
</div> </div>
</template> </template>
@ -190,7 +200,10 @@
class="col-4 d-xl-none text-center turnSignature p-0" class="col-4 d-xl-none text-center turnSignature p-0"
> >
<button <button
:disabled="userSignatureZone === null || userSignatureZone?.index < 1" :disabled="
userSignatureZone === null ||
userSignatureZone?.index < 1
"
class="btn btn-light btn-sm" class="btn btn-light btn-sm"
@click="turnSignature(-1)" @click="turnSignature(-1)"
> >
@ -198,7 +211,9 @@
</button> </button>
<span>|</span> <span>|</span>
<button <button
:disabled="userSignatureZone?.index >= signature.zones.length - 1" :disabled="
userSignatureZone?.index >= signature.zones.length - 1
"
class="btn btn-light btn-sm" class="btn btn-light btn-sm"
@click="turnSignature(1)" @click="turnSignature(1)"
> >
@ -210,7 +225,10 @@
class="col-4 d-none d-xl-flex p-0 text-center turnSignature" class="col-4 d-none d-xl-flex p-0 text-center turnSignature"
> >
<button <button
:disabled="userSignatureZone === null || userSignatureZone?.index < 1" :disabled="
userSignatureZone === null ||
userSignatureZone?.index < 1
"
class="btn btn-light btn-sm" class="btn btn-light btn-sm"
@click="turnSignature(-1)" @click="turnSignature(-1)"
> >
@ -218,7 +236,9 @@
</button> </button>
<span>|</span> <span>|</span>
<button <button
:disabled="userSignatureZone?.index >= signature.zones.length - 1" :disabled="
userSignatureZone?.index >= signature.zones.length - 1
"
class="btn btn-light btn-sm" class="btn btn-light btn-sm"
@click="turnSignature(1)" @click="turnSignature(1)"
> >
@ -259,7 +279,10 @@
</template> </template>
<template v-else> <template v-else>
{{ $t("click_on_document") }} {{ $t("click_on_document") }}
<div class="spinner-border spinner-border-sm" role="status"> <div
class="spinner-border spinner-border-sm"
role="status"
>
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">Loading...</span>
</div> </div>
</template> </template>
@ -336,7 +359,10 @@ console.log(PdfWorker); // incredible but this is needed
// pdfjsLib.GlobalWorkerOptions.workerSrc = PdfWorker; // pdfjsLib.GlobalWorkerOptions.workerSrc = PdfWorker;
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue"; import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import {download_and_decrypt_doc, 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"; pdfjsLib.GlobalWorkerOptions.workerSrc = "pdfjs-dist/build/pdf.worker.mjs";
@ -479,12 +505,18 @@ const addCanvasEvents = () => {
canvas.addEventListener( canvas.addEventListener(
"pointerup", "pointerup",
(e) => canvasClick(e, canvas), (e) => canvasClick(e, canvas),
false false,
); );
}); });
} else { } else {
const canvas = document.querySelectorAll("canvas")[0] as HTMLCanvasElement; const canvas = document.querySelectorAll(
canvas.addEventListener("pointerup", (e) => canvasClick(e, canvas), false); "canvas",
)[0] as HTMLCanvasElement;
canvas.addEventListener(
"pointerup",
(e) => canvasClick(e, canvas),
false,
);
} }
}; };
@ -511,7 +543,7 @@ const scaleYToCanvas = (h: number, canvasHeight: number, PDFHeight: number) =>
const hitSignature = ( const hitSignature = (
zone: SignatureZone, zone: SignatureZone,
xy: number[], xy: number[],
canvas: HTMLCanvasElement canvas: HTMLCanvasElement,
) => ) =>
scaleXToCanvas(zone.x, canvas.width, zone.PDFPage.width) < xy[0] && scaleXToCanvas(zone.x, canvas.width, zone.PDFPage.width) < xy[0] &&
xy[0] < xy[0] <
@ -520,7 +552,11 @@ const hitSignature = (
scaleYToCanvas(zone.y, canvas.height, zone.PDFPage.height) < scaleYToCanvas(zone.y, canvas.height, zone.PDFPage.height) <
xy[1] && xy[1] &&
xy[1] < xy[1] <
scaleYToCanvas(zone.height - zone.y, canvas.height, zone.PDFPage.height) + scaleYToCanvas(
zone.height - zone.y,
canvas.height,
zone.PDFPage.height,
) +
zone.PDFPage.height * zoom.value; zone.PDFPage.height * zoom.value;
const selectZone = async (z: SignatureZone, canvas: HTMLCanvasElement) => { const selectZone = async (z: SignatureZone, canvas: HTMLCanvasElement) => {
@ -536,8 +572,9 @@ const selectZoneEvent = (e: PointerEvent, canvas: HTMLCanvasElement) =>
signature.zones signature.zones
.filter( .filter(
(z) => (z) =>
(z.PDFPage.index + 1 === getCanvasId(canvas) && multiPage.value) || (z.PDFPage.index + 1 === getCanvasId(canvas) &&
(z.PDFPage.index + 1 === page.value && !multiPage.value) multiPage.value) ||
(z.PDFPage.index + 1 === page.value && !multiPage.value),
) )
.map((z) => { .map((z) => {
if (hitSignature(z, [e.offsetX, e.offsetY], canvas)) { if (hitSignature(z, [e.offsetX, e.offsetY], canvas)) {
@ -590,7 +627,7 @@ const drawZone = (
zone: SignatureZone, zone: SignatureZone,
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
canvasWidth: number, canvasWidth: number,
canvasHeight: number canvasHeight: number,
) => { ) => {
const unselectedBlue = "#007bff"; const unselectedBlue = "#007bff";
const selectedBlue = "#034286"; const selectedBlue = "#034286";
@ -605,7 +642,7 @@ const drawZone = (
zone.PDFPage.height * zoom.value - zone.PDFPage.height * zoom.value -
scaleYToCanvas(zone.y, canvasHeight, zone.PDFPage.height), scaleYToCanvas(zone.y, canvasHeight, zone.PDFPage.height),
scaleXToCanvas(zone.width, canvasWidth, zone.PDFPage.width), scaleXToCanvas(zone.width, canvasWidth, zone.PDFPage.width),
scaleYToCanvas(zone.height, canvasHeight, zone.PDFPage.height) scaleYToCanvas(zone.height, canvasHeight, zone.PDFPage.height),
); );
ctx.font = `bold ${16 * zoom.value}px serif`; ctx.font = `bold ${16 * zoom.value}px serif`;
ctx.textAlign = "center"; ctx.textAlign = "center";
@ -633,7 +670,7 @@ const drawAllZones = () => {
.filter( .filter(
(z) => (z) =>
multiPage.value || multiPage.value ||
(z.PDFPage.index + 1 === page.value && !multiPage.value) (z.PDFPage.index + 1 === page.value && !multiPage.value),
) )
.map((z) => { .map((z) => {
const canvas = getCanvas(z.PDFPage.index + 1); const canvas = getCanvas(z.PDFPage.index + 1);
@ -663,7 +700,7 @@ const checkSignature = () => {
signedState.value = "error"; signedState.value = "error";
console.log("Error while checking the signature", error); console.log("Error while checking the signature", error);
$toast.error( $toast.error(
`Erreur lors de la vérification de la signature: ${error.txt}` `Erreur lors de la vérification de la signature: ${error.txt}`,
); );
}); });
}; };
@ -682,7 +719,7 @@ const checkForReady = () => {
tryForReady = 0; tryForReady = 0;
console.log("Reached the maximum number of tentative to try signing"); console.log("Reached the maximum number of tentative to try signing");
$toast.error( $toast.error(
"Le nombre maximum de tentatives pour essayer de signer est atteint" "Le nombre maximum de tentatives pour essayer de signer est atteint",
); );
} }
if (signedState.value === "rejected") { if (signedState.value === "rejected") {
@ -724,7 +761,7 @@ const confirmSign = () => {
console.log("Error while posting the signature", error); console.log("Error while posting the signature", error);
stopTrySigning(); stopTrySigning();
$toast.error( $toast.error(
`Erreur lors de la soumission de la signature: ${error.txt}` `Erreur lors de la soumission de la signature: ${error.txt}`,
); );
}); });
}; };
@ -780,8 +817,8 @@ const addZoneEvent = async (e: PointerEvent, canvas: HTMLCanvasElement) => {
const getReturnPath = () => const getReturnPath = () =>
window.location.search window.location.search
? window.location.search.split("?returnPath=")[1] ?? ? (window.location.search.split("?returnPath=")[1] ??
window.location.pathname window.location.pathname)
: window.location.pathname; : window.location.pathname;
init(); init();
@ -842,4 +879,3 @@ div.signature-modal-body {
height: 8rem; height: 8rem;
} }
</style> </style>

View File

@ -1,19 +1,31 @@
<script setup lang="ts"> <script setup lang="ts">
import { StoredObject, StoredObjectVersionCreated } from "../../types"; import { StoredObject, StoredObjectVersionCreated } from "../../types";
import {encryptFile, fetchNewStoredObject, uploadVersion} from "../../js/async-upload/uploader"; import {
encryptFile,
fetchNewStoredObject,
uploadVersion,
} from "../../js/async-upload/uploader";
import { computed, ref, Ref } from "vue"; import { computed, ref, Ref } from "vue";
import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue"; import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
interface DropFileConfig { interface DropFileConfig {
existingDoc?: StoredObject, existingDoc?: StoredObject;
} }
const props = withDefaults(defineProps<DropFileConfig>(), {existingDoc: null}); const props = withDefaults(defineProps<DropFileConfig>(), {
existingDoc: null,
});
const emit = defineEmits<{ const emit =
(e: 'addDocument', {stored_object_version: StoredObjectVersionCreated, stored_object: StoredObject}): void, defineEmits<
}>(); (
e: "addDocument",
{
stored_object_version: StoredObjectVersionCreated,
stored_object: StoredObject,
},
) => void
>();
const is_dragging: Ref<boolean> = ref(false); const is_dragging: Ref<boolean> = ref(false);
const uploading: Ref<boolean> = ref(false); const uploading: Ref<boolean> = ref(false);
@ -27,13 +39,13 @@ const onDragOver = (e: Event) => {
e.preventDefault(); e.preventDefault();
is_dragging.value = true; is_dragging.value = true;
} };
const onDragLeave = (e: Event) => { const onDragLeave = (e: Event) => {
e.preventDefault(); e.preventDefault();
is_dragging.value = false; is_dragging.value = false;
} };
const onDrop = (e: DragEvent) => { const onDrop = (e: DragEvent) => {
e.preventDefault(); e.preventDefault();
@ -49,8 +61,8 @@ const onDrop = (e: DragEvent) => {
return; return;
} }
handleFile(files[0]) handleFile(files[0]);
} };
const onZoneClick = (e: Event) => { const onZoneClick = (e: Event) => {
e.stopPropagation(); e.stopPropagation();
@ -61,21 +73,21 @@ const onZoneClick = (e: Event) => {
input.addEventListener("change", onFileChange); input.addEventListener("change", onFileChange);
input.click(); input.click();
} };
const onFileChange = async (event: Event): Promise<void> => { const onFileChange = async (event: Event): Promise<void> => {
const input = event.target as HTMLInputElement; const input = event.target as HTMLInputElement;
if (input.files && input.files[0]) { if (input.files && input.files[0]) {
console.log('file added', input.files[0]); console.log("file added", input.files[0]);
const file = input.files[0]; const file = input.files[0];
await handleFile(file); await handleFile(file);
return Promise.resolve(); return Promise.resolve();
} }
throw 'No file given'; throw "No file given";
} };
const handleFile = async (file: File): Promise<void> => { const handleFile = async (file: File): Promise<void> => {
uploading.value = true; uploading.value = true;
@ -100,25 +112,39 @@ const handleFile = async (file: File): Promise<void> => {
keyInfos: jsonWebKey, keyInfos: jsonWebKey,
type: type, type: type,
persisted: false, persisted: false,
} };
emit('addDocument', {stored_object, stored_object_version}); emit("addDocument", { stored_object, stored_object_version });
uploading.value = false; uploading.value = false;
} };
</script> </script>
<template> <template>
<div class="drop-file"> <div class="drop-file">
<div v-if="!uploading" :class="{ area: true, dragging: is_dragging}" @click="onZoneClick" @dragover="onDragOver" @dragleave="onDragLeave" @drop="onDrop"> <div
v-if="!uploading"
:class="{ area: true, dragging: is_dragging }"
@click="onZoneClick"
@dragover="onDragOver"
@dragleave="onDragLeave"
@drop="onDrop"
>
<p v-if="has_existing_doc" class="file-icon"> <p v-if="has_existing_doc" class="file-icon">
<file-icon :type="props.existingDoc?.type"></file-icon> <file-icon :type="props.existingDoc?.type"></file-icon>
</p> </p>
<p v-if="display_filename !== null" class="display-filename">{{ display_filename }}</p> <p v-if="display_filename !== null" class="display-filename">
{{ display_filename }}
</p>
<!-- todo i18n --> <!-- todo i18n -->
<p v-if="has_existing_doc">Déposez un document ou cliquez ici pour remplacer le document existant</p> <p v-if="has_existing_doc">
<p v-else>Déposez un document ou cliquez ici pour ouvrir le navigateur de fichier</p> Déposez un document ou cliquez ici pour remplacer le document
existant
</p>
<p v-else>
Déposez un document ou cliquez ici pour ouvrir le navigateur de
fichier
</p>
</div> </div>
<div v-else class="waiting"> <div v-else class="waiting">
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i> <i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
@ -140,7 +166,8 @@ const handleFile = async (file: File): Promise<void> => {
font-weight: 200; font-weight: 200;
} }
& > .area, & > .waiting { & > .area,
& > .waiting {
width: 100%; width: 100%;
height: 10rem; height: 10rem;
@ -163,5 +190,4 @@ const handleFile = async (file: File): Promise<void> => {
} }
} }
} }
</style> </style>

View File

@ -1,14 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue"; import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import { StoredObject, StoredObjectVersion } from "../../types"; import { StoredObject, StoredObjectVersion } from "../../types";
import DropFileWidget from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileWidget.vue"; import DropFileWidget from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileWidget.vue";
import { computed, reactive } from "vue"; import { computed, reactive } from "vue";
import {useToast} from 'vue-toast-notification'; import { useToast } from "vue-toast-notification";
interface DropFileConfig { interface DropFileConfig {
allowRemove: boolean, allowRemove: boolean;
existingDoc?: StoredObject, existingDoc?: StoredObject;
} }
const props = withDefaults(defineProps<DropFileConfig>(), { const props = withDefaults(defineProps<DropFileConfig>(), {
@ -16,8 +15,14 @@ const props = withDefaults(defineProps<DropFileConfig>(), {
}); });
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'addDocument', {stored_object: StoredObject, stored_object_version: StoredObjectVersion}): void, (
(e: 'removeDocument'): void e: "addDocument",
{
stored_object: StoredObject,
stored_object_version: StoredObjectVersion,
},
): void;
(e: "removeDocument"): void;
}>(); }>();
const $toast = useToast(); const $toast = useToast();
@ -26,23 +31,30 @@ const state = reactive({showModal: false});
const modalClasses = { "modal-dialog-centered": true, "modal-md": true }; const modalClasses = { "modal-dialog-centered": true, "modal-md": true };
const buttonState = computed<'add'|'replace'>(() => { const buttonState = computed<"add" | "replace">(() => {
if (props.existingDoc === undefined || props.existingDoc === null) { if (props.existingDoc === undefined || props.existingDoc === null) {
return 'add'; return "add";
} }
return 'replace'; return "replace";
}) });
function onAddDocument({stored_object, stored_object_version}: {stored_object: StoredObject, stored_object_version: StoredObjectVersion}): void { function onAddDocument({
const message = buttonState.value === 'add' ? "Document ajouté" : "Document remplacé"; stored_object,
stored_object_version,
}: {
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
}): void {
const message =
buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
$toast.success(message); $toast.success(message);
emit('addDocument', {stored_object_version, stored_object}); emit("addDocument", { stored_object_version, stored_object });
state.showModal = false; state.showModal = false;
} }
function onRemoveDocument(): void { function onRemoveDocument(): void {
emit('removeDocument'); emit("removeDocument");
} }
function openModal(): void { function openModal(): void {
@ -55,15 +67,30 @@ function closeModal(): void {
</script> </script>
<template> <template>
<button v-if="buttonState === 'add'" @click="openModal" class="btn btn-create">Ajouter un document</button> <button
<button v-else @click="openModal" class="btn btn-edit">Remplacer le document</button> v-if="buttonState === 'add'"
<modal v-if="state.showModal" :modal-dialog-class="modalClasses" @close="closeModal"> @click="openModal"
class="btn btn-create"
>
Ajouter un document
</button>
<button v-else @click="openModal" class="btn btn-edit">
Remplacer le document
</button>
<modal
v-if="state.showModal"
:modal-dialog-class="modalClasses"
@close="closeModal"
>
<template v-slot:body> <template v-slot:body>
<drop-file-widget :existing-doc="existingDoc" :allow-remove="allowRemove" @add-document="onAddDocument" @remove-document="onRemoveDocument" ></drop-file-widget> <drop-file-widget
:existing-doc="existingDoc"
:allow-remove="allowRemove"
@add-document="onAddDocument"
@remove-document="onRemoveDocument"
></drop-file-widget>
</template> </template>
</modal> </modal>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -1,13 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { StoredObject, StoredObjectVersion } from "../../types"; import { StoredObject, StoredObjectVersion } from "../../types";
import { computed, ref, Ref } from "vue"; import { computed, ref, Ref } from "vue";
import DropFile from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFile.vue"; import DropFile from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFile.vue";
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue"; import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
interface DropFileConfig { interface DropFileConfig {
allowRemove: boolean, allowRemove: boolean;
existingDoc?: StoredObject, existingDoc?: StoredObject;
} }
const props = withDefaults(defineProps<DropFileConfig>(), { const props = withDefaults(defineProps<DropFileConfig>(), {
@ -15,8 +14,14 @@ const props = withDefaults(defineProps<DropFileConfig>(), {
}); });
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'addDocument', {stored_object: StoredObject, stored_object_version: StoredObjectVersion}): void, (
(e: 'removeDocument'): void e: "addDocument",
{
stored_object: StoredObject,
stored_object_version: StoredObjectVersion,
},
): void;
(e: "removeDocument"): void;
}>(); }>();
const has_existing_doc = computed<boolean>(() => { const has_existing_doc = computed<boolean>(() => {
@ -27,7 +32,7 @@ const dav_link_expiration = computed<number|undefined>(() => {
if (props.existingDoc === undefined || props.existingDoc === null) { if (props.existingDoc === undefined || props.existingDoc === null) {
return undefined; return undefined;
} }
if (props.existingDoc.status !== 'ready') { if (props.existingDoc.status !== "ready") {
return undefined; return undefined;
} }
@ -38,28 +43,36 @@ const dav_link_href = computed<string|undefined>(() => {
if (props.existingDoc === undefined || props.existingDoc === null) { if (props.existingDoc === undefined || props.existingDoc === null) {
return undefined; return undefined;
} }
if (props.existingDoc.status !== 'ready') { if (props.existingDoc.status !== "ready") {
return undefined; return undefined;
} }
return props.existingDoc._links?.dav_link?.href; return props.existingDoc._links?.dav_link?.href;
}) });
const onAddDocument = ({stored_object, stored_object_version}: {stored_object: StoredObject, stored_object_version: StoredObjectVersion}): void => { const onAddDocument = ({
emit('addDocument', {stored_object, stored_object_version}); stored_object,
} stored_object_version,
}: {
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
}): void => {
emit("addDocument", { stored_object, stored_object_version });
};
const onRemoveDocument = (e: Event): void => { const onRemoveDocument = (e: Event): void => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
emit('removeDocument'); emit("removeDocument");
} };
</script> </script>
<template> <template>
<div> <div>
<drop-file :existingDoc="props.existingDoc" @addDocument="onAddDocument"></drop-file> <drop-file
:existingDoc="props.existingDoc"
@addDocument="onAddDocument"
></drop-file>
<ul class="record_actions"> <ul class="record_actions">
<li v-if="has_existing_doc"> <li v-if="has_existing_doc">
@ -72,12 +85,14 @@ const onRemoveDocument = (e: Event): void => {
/> />
</li> </li>
<li> <li>
<button v-if="allowRemove" class="btn btn-delete" @click="onRemoveDocument($event)" ></button> <button
v-if="allowRemove"
class="btn btn-delete"
@click="onRemoveDocument($event)"
></button>
</li> </li>
</ul> </ul>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -4,22 +4,43 @@ interface FileIconConfig {
} }
const props = defineProps<FileIconConfig>(); const props = defineProps<FileIconConfig>();
</script> </script>
<template> <template>
<i class="fa fa-file-pdf-o" v-if="props.type === 'application/pdf'"></i> <i class="fa fa-file-pdf-o" v-if="props.type === 'application/pdf'"></i>
<i class="fa fa-file-word-o" v-else-if="props.type === 'application/vnd.oasis.opendocument.text'"></i> <i
<i class="fa fa-file-word-o" v-else-if="props.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'"></i> class="fa fa-file-word-o"
<i class="fa fa-file-word-o" v-else-if="props.type === 'application/msword'"></i> v-else-if="props.type === 'application/vnd.oasis.opendocument.text'"
<i class="fa fa-file-excel-o" v-else-if="props.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'"></i> ></i>
<i class="fa fa-file-excel-o" v-else-if="props.type === 'application/vnd.ms-excel'"></i> <i
class="fa fa-file-word-o"
v-else-if="
props.type ===
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
"
></i>
<i
class="fa fa-file-word-o"
v-else-if="props.type === 'application/msword'"
></i>
<i
class="fa fa-file-excel-o"
v-else-if="
props.type ===
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
"
></i>
<i
class="fa fa-file-excel-o"
v-else-if="props.type === 'application/vnd.ms-excel'"
></i>
<i class="fa fa-file-image-o" v-else-if="props.type === 'image/jpeg'"></i> <i class="fa fa-file-image-o" v-else-if="props.type === 'image/jpeg'"></i>
<i class="fa fa-file-image-o" v-else-if="props.type === 'image/png'"></i> <i class="fa fa-file-image-o" v-else-if="props.type === 'image/png'"></i>
<i class="fa fa-file-archive-o" v-else-if="props.type === 'application/x-zip-compressed'"></i> <i
class="fa fa-file-archive-o"
v-else-if="props.type === 'application/x-zip-compressed'"
></i>
<i class="fa fa-file-code-o" v-else></i> <i class="fa fa-file-code-o" v-else></i>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -6,20 +6,23 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {
import {build_convert_link, download_and_decrypt_doc, download_doc} from "./helpers"; build_convert_link,
download_and_decrypt_doc,
download_doc,
} from "./helpers";
import mime from "mime"; import mime from "mime";
import { reactive, ref } from "vue"; import { reactive, ref } from "vue";
import { StoredObject } from "../../types"; import { StoredObject } from "../../types";
interface ConvertButtonConfig { interface ConvertButtonConfig {
storedObject: StoredObject, storedObject: StoredObject;
classes: { [key: string]: boolean}, classes: Record<string, boolean>;
filename?: string, filename?: string;
}; }
interface DownloadButtonState { interface DownloadButtonState {
content: null|string content: null | string;
} }
const props = defineProps<ConvertButtonConfig>(); const props = defineProps<ConvertButtonConfig>();
@ -32,13 +35,15 @@ async function download_and_open(event: Event): Promise<void> {
if (null === state.content) { if (null === state.content) {
event.preventDefault(); event.preventDefault();
const raw = await download_doc(build_convert_link(props.storedObject.uuid)); const raw = await download_doc(
build_convert_link(props.storedObject.uuid),
);
state.content = window.URL.createObjectURL(raw); state.content = window.URL.createObjectURL(raw);
button.href = window.URL.createObjectURL(raw); button.href = window.URL.createObjectURL(raw);
button.type = 'application/pdf'; button.type = "application/pdf";
button.download = (props.filename + '.pdf') || 'document.pdf'; button.download = props.filename + ".pdf" || "document.pdf";
} }
button.click(); button.click();
@ -47,11 +52,10 @@ async function download_and_open(event: Event): Promise<void> {
function reset_state(): void { function reset_state(): void {
state.content = null; state.content = null;
btn.value?.removeAttribute('download'); btn.value?.removeAttribute("download");
btn.value?.removeAttribute('href'); btn.value?.removeAttribute("href");
btn.value?.removeAttribute('type'); btn.value?.removeAttribute("type");
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -1,23 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue"; import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import { computed, reactive } from "vue"; import { computed, reactive } from "vue";
export interface DesktopEditButtonConfig { export interface DesktopEditButtonConfig {
editLink: null, editLink: null;
classes: { [k: string]: boolean }, classes: Record<string, boolean>;
expirationLink: number|Date, expirationLink: number | Date;
} }
interface DesktopEditButtonState { interface DesktopEditButtonState {
modalOpened: boolean modalOpened: boolean;
}; }
const state: DesktopEditButtonState = reactive({ modalOpened: false }); const state: DesktopEditButtonState = reactive({ modalOpened: false });
const props = defineProps<DesktopEditButtonConfig>(); const props = defineProps<DesktopEditButtonConfig>();
const buildCommand = computed<string>(() => 'vnd.libreoffice.command:ofe|u|' + props.editLink); const buildCommand = computed<string>(
() => "vnd.libreoffice.command:ofe|u|" + props.editLink,
);
const editionUntilFormatted = computed<string>(() => { const editionUntilFormatted = computed<string>(() => {
let d; let d;
@ -29,9 +30,11 @@ const editionUntilFormatted = computed<string>(() => {
} }
console.log(props.expirationLink); console.log(props.expirationLink);
return (new Intl.DateTimeFormat(undefined, {'dateStyle': 'long', 'timeStyle': 'medium'})).format(d); return new Intl.DateTimeFormat(undefined, {
dateStyle: "long",
timeStyle: "medium",
}).format(d);
}); });
</script> </script>
<template> <template>
@ -39,16 +42,40 @@ const editionUntilFormatted = computed<string>(() => {
<modal v-if="state.modalOpened" @close="state.modalOpened = false"> <modal v-if="state.modalOpened" @close="state.modalOpened = false">
<template v-slot:body> <template v-slot:body>
<div class="desktop-edit"> <div class="desktop-edit">
<p class="center">Veuillez enregistrer vos modifications avant le</p> <p class="center">
<p><strong>{{ editionUntilFormatted }}</strong></p> Veuillez enregistrer vos modifications avant le
</p>
<p>
<strong>{{ editionUntilFormatted }}</strong>
</p>
<p><a class="btn btn-primary" :href="buildCommand">Ouvrir le document pour édition</a></p> <p>
<a class="btn btn-primary" :href="buildCommand"
>Ouvrir le document pour édition</a
>
</p>
<p><small>Le document peut être édité uniquement en utilisant Libre Office.</small></p> <p>
<small
>Le document peut être édité uniquement en utilisant
Libre Office.</small
>
</p>
<p><small>En cas d'échec lors de l'enregistrement, sauver le document sur le poste de travail avant de le déposer à nouveau ici.</small></p> <p>
<small
>En cas d'échec lors de l'enregistrement, sauver le
document sur le poste de travail avant de le déposer
à nouveau ici.</small
>
</p>
<p><small>Vous pouvez naviguez sur d'autres pages pendant l'édition.</small></p> <p>
<small
>Vous pouvez naviguez sur d'autres pages pendant
l'édition.</small
>
</p>
</div> </div>
</template> </template>
</modal> </modal>

View File

@ -1,9 +1,23 @@
<template> <template>
<a v-if="!state.is_ready" :class="props.classes" @click="download_and_open()" title="T&#233;l&#233;charger"> <a
v-if="!state.is_ready"
:class="props.classes"
@click="download_and_open()"
title="T&#233;l&#233;charger"
>
<i class="fa fa-download"></i> <i class="fa fa-download"></i>
<template v-if="displayActionStringInButton">Télécharger</template> <template v-if="displayActionStringInButton">Télécharger</template>
</a> </a>
<a v-else :class="props.classes" target="_blank" :type="props.atVersion.type" :download="buildDocumentName()" :href="state.href_url" ref="open_button" title="Ouvrir"> <a
v-else
:class="props.classes"
target="_blank"
:type="props.atVersion.type"
:download="buildDocumentName()"
:href="state.href_url"
ref="open_button"
title="Ouvrir"
>
<i class="fa fa-external-link"></i> <i class="fa fa-external-link"></i>
<template v-if="displayActionStringInButton">Ouvrir</template> <template v-if="displayActionStringInButton">Ouvrir</template>
</a> </a>
@ -16,43 +30,50 @@ import mime from "mime";
import { StoredObject, StoredObjectVersion } from "../../types"; import { StoredObject, StoredObjectVersion } from "../../types";
interface DownloadButtonConfig { interface DownloadButtonConfig {
storedObject: StoredObject, storedObject: StoredObject;
atVersion: StoredObjectVersion, atVersion: StoredObjectVersion;
classes: { [k: string]: boolean }, classes: Record<string, boolean>;
filename?: string, filename?: string;
/** /**
* if true, display the action string into the button. If false, displays only * if true, display the action string into the button. If false, displays only
* the icon * the icon
*/ */
displayActionStringInButton?: boolean, displayActionStringInButton?: boolean;
/** /**
* if true, will download directly the file on load * if true, will download directly the file on load
*/ */
directDownload?: boolean, directDownload?: boolean;
} }
interface DownloadButtonState { interface DownloadButtonState {
is_ready: boolean, is_ready: boolean;
is_running: boolean, is_running: boolean;
href_url: string, href_url: string;
} }
const props = withDefaults(defineProps<DownloadButtonConfig>(), {displayActionStringInButton: true, directDownload: false}); const props = withDefaults(defineProps<DownloadButtonConfig>(), {
const state: DownloadButtonState = reactive({is_ready: false, is_running: false, href_url: "#"}); displayActionStringInButton: true,
directDownload: false,
});
const state: DownloadButtonState = reactive({
is_ready: false,
is_running: false,
href_url: "#",
});
const open_button = ref<HTMLAnchorElement | null>(null); const open_button = ref<HTMLAnchorElement | null>(null);
function buildDocumentName(): string { function buildDocumentName(): string {
let document_name = props.filename ?? props.storedObject.title; let document_name = props.filename ?? props.storedObject.title;
if ('' === document_name) { if ("" === document_name) {
document_name = 'document'; document_name = "document";
} }
const ext = mime.getExtension(props.atVersion.type); const ext = mime.getExtension(props.atVersion.type);
if (null !== ext) { if (null !== ext) {
return document_name + '.' + ext; return document_name + "." + ext;
} }
return document_name; return document_name;
@ -60,21 +81,24 @@ function buildDocumentName(): string {
async function download_and_open(): Promise<void> { async function download_and_open(): Promise<void> {
if (state.is_running) { if (state.is_running) {
console.log('state is running, aborting'); console.log("state is running, aborting");
return; return;
} }
state.is_running = true; state.is_running = true;
if (state.is_ready) { if (state.is_ready) {
console.log('state is ready. This should not happens'); console.log("state is ready. This should not happens");
return; return;
} }
let raw; let raw;
try { try {
raw = await download_and_decrypt_doc(props.storedObject, props.atVersion); raw = await download_and_decrypt_doc(
props.storedObject,
props.atVersion,
);
} catch (e) { } catch (e) {
console.error("error while downloading and decrypting document"); console.error("error while downloading and decrypting document");
console.error(e); console.error(e);
@ -89,13 +113,13 @@ async function download_and_open(): Promise<void> {
await nextTick(); await nextTick();
open_button.value?.click(); open_button.value?.click();
console.log('open button should have been clicked'); console.log("open button should have been clicked");
setTimeout(reset_state, 45000); setTimeout(reset_state, 45000);
} }
} }
function reset_state(): void { function reset_state(): void {
state.href_url = '#'; state.href_url = "#";
state.is_ready = false; state.is_ready = false;
state.is_running = false; state.is_running = false;
} }

View File

@ -1,7 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import HistoryButtonModal from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonModal.vue"; import HistoryButtonModal from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonModal.vue";
import {StoredObject, StoredObjectVersionWithPointInTime} from "./../../types"; import {
StoredObject,
StoredObjectVersionWithPointInTime,
} from "./../../types";
import { computed, reactive, ref, useTemplateRef } from "vue"; import { computed, reactive, ref, useTemplateRef } from "vue";
import { get_versions } from "./HistoryButton/api"; import { get_versions } from "./HistoryButton/api";
@ -17,7 +19,7 @@ interface HistoryButtonState {
const props = defineProps<HistoryButtonConfig>(); const props = defineProps<HistoryButtonConfig>();
const state = reactive<HistoryButtonState>({ versions: [], loaded: false }); const state = reactive<HistoryButtonState>({ versions: [], loaded: false });
const modal = useTemplateRef<typeof HistoryButtonModal>('modal'); const modal = useTemplateRef<typeof HistoryButtonModal>("modal");
const download_version_and_open_modal = async function (): Promise<void> { const download_version_and_open_modal = async function (): Promise<void> {
if (null !== modal.value) { if (null !== modal.value) {
@ -34,17 +36,26 @@ const download_version_and_open_modal = async function (): Promise<void> {
} }
state.loaded = true; state.loaded = true;
} }
} };
const onRestoreVersion = ({newVersion}: {newVersion: StoredObjectVersionWithPointInTime}) => { const onRestoreVersion = ({
newVersion,
}: {
newVersion: StoredObjectVersionWithPointInTime;
}) => {
state.versions.unshift(newVersion); state.versions.unshift(newVersion);
} };
</script> </script>
<template> <template>
<a @click="download_version_and_open_modal" class="dropdown-item"> <a @click="download_version_and_open_modal" class="dropdown-item">
<history-button-modal ref="modal" :versions="state.versions" :stored-object="storedObject" :can-edit="canEdit" @restore-version="onRestoreVersion"></history-button-modal> <history-button-modal
ref="modal"
:versions="state.versions"
:stored-object="storedObject"
:can-edit="canEdit"
@restore-version="onRestoreVersion"
></history-button-modal>
<i class="fa fa-history"></i> <i class="fa fa-history"></i>
Historique Historique
</a> </a>

View File

@ -1,5 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import {StoredObject, StoredObjectVersionWithPointInTime} from "./../../../types"; import {
StoredObject,
StoredObjectVersionWithPointInTime,
} from "./../../../types";
import HistoryButtonListItem from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonListItem.vue"; import HistoryButtonListItem from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonListItem.vue";
import { computed, reactive } from "vue"; import { computed, reactive } from "vue";
@ -10,8 +13,8 @@ interface HistoryButtonListConfig {
} }
const emit = defineEmits<{ const emit = defineEmits<{
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime] restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
}>() }>();
interface HistoryButtonListState { interface HistoryButtonListState {
/** /**
@ -22,12 +25,14 @@ interface HistoryButtonListState {
const props = defineProps<HistoryButtonListConfig>(); const props = defineProps<HistoryButtonListConfig>();
const state = reactive<HistoryButtonListState>({restored: -1}) const state = reactive<HistoryButtonListState>({ restored: -1 });
const higher_version = computed<number>(() => props.versions.reduce( const higher_version = computed<number>(() =>
(accumulator: number, version: StoredObjectVersionWithPointInTime) => Math.max(accumulator, version.version), props.versions.reduce(
-1 (accumulator: number, version: StoredObjectVersionWithPointInTime) =>
) Math.max(accumulator, version.version),
-1,
),
); );
/** /**
@ -35,11 +40,14 @@ const higher_version = computed<number>(() => props.versions.reduce(
* *
* internally, keep track of the newly restored version * internally, keep track of the newly restored version
*/ */
const onRestored = ({newVersion}: {newVersion: StoredObjectVersionWithPointInTime}) => { const onRestored = ({
newVersion,
}: {
newVersion: StoredObjectVersionWithPointInTime;
}) => {
state.restored = newVersion.version; state.restored = newVersion.version;
emit('restoreVersion', {newVersion}); emit("restoreVersion", { newVersion });
} };
</script> </script>
<template> <template>
@ -59,9 +67,6 @@ const onRestored = ({newVersion}: {newVersion: StoredObjectVersionWithPointInTim
<template v-else> <template v-else>
<p>Chargement des versions</p> <p>Chargement des versions</p>
</template> </template>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -1,5 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import {StoredObject, StoredObjectPointInTime, StoredObjectVersionWithPointInTime} from "./../../../types"; import {
StoredObject,
StoredObjectPointInTime,
StoredObjectVersionWithPointInTime,
} from "./../../../types";
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue"; import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue";
import { ISOToDatetime } from "./../../../../../../ChillMainBundle/Resources/public/chill/js/date"; import { ISOToDatetime } from "./../../../../../../ChillMainBundle/Resources/public/chill/js/date";
import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue"; import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
@ -15,47 +19,120 @@ interface HistoryButtonListItemConfig {
} }
const emit = defineEmits<{ const emit = defineEmits<{
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime] restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
}>() }>();
const props = defineProps<HistoryButtonListItemConfig>(); const props = defineProps<HistoryButtonListItemConfig>();
const onRestore = ({newVersion}: {newVersion: StoredObjectVersionWithPointInTime}) => { const onRestore = ({
emit('restoreVersion', {newVersion}); newVersion,
} }: {
newVersion: StoredObjectVersionWithPointInTime;
}) => {
emit("restoreVersion", { newVersion });
};
const isKeptBeforeConversion = computed<boolean>(() => props.version["point-in-times"].reduce( const isKeptBeforeConversion = computed<boolean>(() =>
(accumulator: boolean, pit: StoredObjectPointInTime) => accumulator || "keep-before-conversion" === pit.reason, props.version["point-in-times"].reduce(
false (accumulator: boolean, pit: StoredObjectPointInTime) =>
accumulator || "keep-before-conversion" === pit.reason,
false,
), ),
); );
const isRestored = computed<boolean>(() => props.version.version > 0 && null !== props.version["from-restored"]); const isRestored = computed<boolean>(
() => props.version.version > 0 && null !== props.version["from-restored"],
);
const isDuplicated = computed<boolean>(() => props.version.version === 0 && null !== props.version["from-restored"]); const isDuplicated = computed<boolean>(
() =>
const classes = computed<{row: true, 'row-hover': true, 'blinking-1': boolean, 'blinking-2': boolean}>(() => ({row: true, 'row-hover': true, 'blinking-1': props.isRestored && 0 === props.version.version % 2, 'blinking-2': props.isRestored && 1 === props.version.version % 2})); props.version.version === 0 && null !== props.version["from-restored"],
);
const classes = computed<{
row: true;
"row-hover": true;
"blinking-1": boolean;
"blinking-2": boolean;
}>(() => ({
row: true,
"row-hover": true,
"blinking-1": props.isRestored && 0 === props.version.version % 2,
"blinking-2": props.isRestored && 1 === props.version.version % 2,
}));
</script> </script>
<template> <template>
<div :class="classes"> <div :class="classes">
<div class="col-12 tags" v-if="isCurrent || isKeptBeforeConversion || isRestored || isDuplicated"> <div
<span class="badge bg-success" v-if="isCurrent">Version actuelle</span> class="col-12 tags"
<span class="badge bg-info" v-if="isKeptBeforeConversion">Conservée avant conversion dans un autre format</span> v-if="
<span class="badge bg-info" v-if="isRestored">Restaurée depuis la version {{ version["from-restored"]?.version + 1 }}</span> isCurrent ||
<span class="badge bg-info" v-if="isDuplicated">Dupliqué depuis un autre document</span> isKeptBeforeConversion ||
isRestored ||
isDuplicated
"
>
<span class="badge bg-success" v-if="isCurrent"
>Version actuelle</span
>
<span class="badge bg-info" v-if="isKeptBeforeConversion"
>Conservée avant conversion dans un autre format</span
>
<span class="badge bg-info" v-if="isRestored"
>Restaurée depuis la version
{{ version["from-restored"]?.version + 1 }}</span
>
<span class="badge bg-info" v-if="isDuplicated"
>Dupliqué depuis un autre document</span
>
</div> </div>
<div class="col-12"> <div class="col-12">
<file-icon :type="version.type"></file-icon> <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-else>modifié par</strong> <span class="badge-user"><UserRenderBoxBadge :user="version.createdBy"></UserRenderBoxBadge></span> <strong>à</strong> {{ $d(ISOToDatetime(version.createdAt.datetime8601), 'long') }}</template><template v-if="version.createdBy === null && version.createdAt !== null"><strong v-if="version.version == 0">Créé le</strong><strong v-else>modifié le</strong> {{ $d(ISOToDatetime(version.createdAt.datetime8601), 'long') }}</template> <file-icon :type="version.type"></file-icon>
<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-else>modifié par</strong>
<span class="badge-user"
><UserRenderBoxBadge
:user="version.createdBy"
></UserRenderBoxBadge
></span>
<strong>à</strong>
{{
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
}}</template
><template
v-if="version.createdBy === null && version.createdAt !== null"
><strong v-if="version.version == 0">Créé le</strong
><strong v-else>modifié le</strong>
{{
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
}}</template
>
</div> </div>
<div class="col-12"> <div class="col-12">
<ul class="record_actions small slim on-version-actions"> <ul class="record_actions small slim on-version-actions">
<li v-if="canEdit && !isCurrent"> <li v-if="canEdit && !isCurrent">
<restore-version-button :stored-object-version="props.version" @restore-version="onRestore"></restore-version-button> <restore-version-button
:stored-object-version="props.version"
@restore-version="onRestore"
></restore-version-button>
</li> </li>
<li> <li>
<download-button :stored-object="storedObject" :at-version="version" :classes="{btn: true, 'btn-outline-primary': true, 'btn-sm': true}" :display-action-string-in-button="false"></download-button> <download-button
:stored-object="storedObject"
:at-version="version"
:classes="{
btn: true,
'btn-outline-primary': true,
'btn-sm': true,
}"
:display-action-string-in-button="false"
></download-button>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -2,7 +2,10 @@
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue"; import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import { reactive } from "vue"; import { reactive } from "vue";
import HistoryButtonList from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue"; import HistoryButtonList from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue";
import {StoredObject, StoredObjectVersionWithPointInTime} from "./../../../types"; import {
StoredObject,
StoredObjectVersionWithPointInTime,
} from "./../../../types";
interface HistoryButtonListConfig { interface HistoryButtonListConfig {
versions: StoredObjectVersionWithPointInTime[]; versions: StoredObjectVersionWithPointInTime[];
@ -11,8 +14,8 @@ interface HistoryButtonListConfig {
} }
const emit = defineEmits<{ const emit = defineEmits<{
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime] restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
}>() }>();
interface HistoryButtonModalState { interface HistoryButtonModalState {
opened: boolean; opened: boolean;
@ -23,10 +26,9 @@ const state = reactive<HistoryButtonModalState>({opened: false});
const open = () => { const open = () => {
state.opened = true; state.opened = true;
} };
defineExpose({ open }); defineExpose({ open });
</script> </script>
<template> <template>
<Teleport to="body"> <Teleport to="body">
@ -36,12 +38,17 @@ defineExpose({open});
</template> </template>
<template v-slot:body> <template v-slot:body>
<p>Les versions sont conservées pendant 90 jours.</p> <p>Les versions sont conservées pendant 90 jours.</p>
<history-button-list :versions="props.versions" :can-edit="canEdit" :stored-object="storedObject" @restore-version="(payload) => emit('restoreVersion', payload)"></history-button-list> <history-button-list
:versions="props.versions"
:can-edit="canEdit"
:stored-object="storedObject"
@restore-version="
(payload) => emit('restoreVersion', payload)
"
></history-button-list>
</template> </template>
</modal> </modal>
</Teleport> </Teleport>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -1,17 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import {StoredObjectVersionPersisted, StoredObjectVersionWithPointInTime} from "../../../types"; import {
StoredObjectVersionPersisted,
StoredObjectVersionWithPointInTime,
} from "../../../types";
import { useToast } from "vue-toast-notification"; import { useToast } from "vue-toast-notification";
import { restore_version } from "./api"; import { restore_version } from "./api";
interface RestoreVersionButtonProps { interface RestoreVersionButtonProps {
storedObjectVersion: StoredObjectVersionPersisted, storedObjectVersion: StoredObjectVersionPersisted;
} }
const emit = defineEmits<{ const emit = defineEmits<{
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime] restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
}>() }>();
const props = defineProps<RestoreVersionButtonProps>() const props = defineProps<RestoreVersionButtonProps>();
const $toast = useToast(); const $toast = useToast();
@ -19,14 +22,18 @@ const restore_version_fn = async () => {
const newVersion = await restore_version(props.storedObjectVersion); const newVersion = await restore_version(props.storedObjectVersion);
$toast.success("Version restaurée"); $toast.success("Version restaurée");
emit('restoreVersion', {newVersion}); emit("restoreVersion", { newVersion });
} };
</script> </script>
<template> <template>
<button class="btn btn-outline-action" @click="restore_version_fn" title="Restaurer"><i class="fa fa-rotate-left"></i> Restaurer</button> <button
class="btn btn-outline-action"
@click="restore_version_fn"
title="Restaurer"
>
<i class="fa fa-rotate-left"></i> Restaurer
</button>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -1,5 +1,11 @@
<template> <template>
<a :class="Object.assign(props.classes, {'btn': true})" @click="beforeLeave($event)" :href="build_wopi_editor_link(props.storedObject.uuid, props.returnPath)"> <a
:class="Object.assign(props.classes, { btn: true })"
@click="beforeLeave($event)"
:href="
build_wopi_editor_link(props.storedObject.uuid, props.returnPath)
"
>
<i class="fa fa-paragraph"></i> <i class="fa fa-paragraph"></i>
Editer en ligne Editer en ligne
</a> </a>
@ -8,13 +14,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import WopiEditButton from "./WopiEditButton.vue"; import WopiEditButton from "./WopiEditButton.vue";
import { build_wopi_editor_link } from "./helpers"; import { build_wopi_editor_link } from "./helpers";
import {StoredObject, WopiEditButtonExecutableBeforeLeaveFunction} from "../../types"; import {
StoredObject,
WopiEditButtonExecutableBeforeLeaveFunction,
} from "../../types";
interface WopiEditButtonConfig { interface WopiEditButtonConfig {
storedObject: StoredObject, storedObject: StoredObject;
returnPath?: string, returnPath?: string;
classes: {[k: string] : boolean}, classes: Record<string, boolean>;
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction, executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
} }
const props = defineProps<WopiEditButtonConfig>(); const props = defineProps<WopiEditButtonConfig>();
@ -43,4 +52,3 @@ i.fa::before {
color: var(--bs-dropdown-link-hover-color); color: var(--bs-dropdown-link-hover-color);
} }
</style> </style>

View File

@ -1 +1 @@
require('./chillevent.scss'); require("./chillevent.scss");

View File

@ -1,4 +1,3 @@
module.exports = function(encore, entries) module.exports = function (encore, entries) {
{ entries.push(__dirname + "/Resources/public/chill/index.js");
entries.push(__dirname + '/Resources/public/chill/index.js');
}; };

View File

@ -1 +1 @@
require('./footer_bandeau.jpg'); require("./footer_bandeau.jpg");

View File

@ -1,43 +1,37 @@
import { ShowHide } from 'ShowHide/show_hide.js'; import { ShowHide } from "ShowHide/show_hide.js";
// listen to adding of formation and register a show hide // listen to adding of formation and register a show hide
var make_show_hide = function (entry) { var make_show_hide = function (entry) {
let let obtained = entry.querySelector("[data-diploma-obtained]"),
obtained = entry.querySelector('[data-diploma-obtained]'), reconnue = entry.querySelector("[data-diploma-reconnue]");
reconnue = entry.querySelector('[data-diploma-reconnue]')
;
var a = new ShowHide({ var a = new ShowHide({
load_event: null, load_event: null,
froms: [obtained], froms: [obtained],
container: [reconnue], container: [reconnue],
test: function (froms, event) { test: function (froms, event) {
for (let f of froms.values()) { for (let f of froms.values()) {
for (let input of f.querySelectorAll('input').values()) { for (let input of f.querySelectorAll("input").values()) {
if (input.value === 'non-fr') { if (input.value === "non-fr") {
return input.checked; return input.checked;
} }
} }
} }
return false; return false;
} },
}); });
}; };
window.addEventListener('collection-add-entry', function(e) { window.addEventListener("collection-add-entry", function (e) {
if (e.detail.collection.dataset.collectionName === 'formations') { if (e.detail.collection.dataset.collectionName === "formations") {
make_show_hide(e.detail.entry); make_show_hide(e.detail.entry);
} }
}); });
// starting the formation on load // starting the formation on load
window.addEventListener('load', function(_e) { window.addEventListener("load", function (_e) {
let let formations = document.querySelectorAll("[data-formation-entry]");
formations = document.querySelectorAll('[data-formation-entry]')
;
for (let f of formations.values()) { for (let f of formations.values()) {
make_show_hide(f); make_show_hide(f);
} }
}); });

View File

@ -1,37 +1,36 @@
import { ShowHide } from 'ShowHide/show_hide.js'; import { ShowHide } from "ShowHide/show_hide.js";
var var div_accompagnement = document.getElementById("form_accompagnement"),
div_accompagnement = document.getElementById("form_accompagnement"),
div_accompagnement_rqth = document.getElementById("form_accompagnement_rqth"), div_accompagnement_rqth = document.getElementById("form_accompagnement_rqth"),
div_accompagnement_comment = document.getElementById("form_accompagnement_comment"), div_accompagnement_comment = document.getElementById(
"form_accompagnement_comment",
),
div_caf_id = document.getElementById("cafId"), div_caf_id = document.getElementById("cafId"),
div_caf_inscription_date = document.getElementById("cafInscriptionDate"), div_caf_inscription_date = document.getElementById("cafInscriptionDate"),
div_neet_eligibilite = document.getElementById("neetEligibilite"), div_neet_eligibilite = document.getElementById("neetEligibilite"),
div_neet_commission_date = document.getElementById("neetCommissionDate") div_neet_commission_date = document.getElementById("neetCommissionDate");
;
// faire apparaitre / disparaitre RQTH si RQTH coché // faire apparaitre / disparaitre RQTH si RQTH coché
new ShowHide({ new ShowHide({
"froms": [div_accompagnement], froms: [div_accompagnement],
"test": function(froms, event) { test: function (froms, event) {
for (let el of froms.values()) { for (let el of froms.values()) {
for (let input of el.querySelectorAll('input').values()) { for (let input of el.querySelectorAll("input").values()) {
if (input.value === 'rqth') { if (input.value === "rqth") {
return input.checked; return input.checked;
} }
} }
} }
}, },
"container": [div_accompagnement_rqth] container: [div_accompagnement_rqth],
}); });
// faire apparaitre / disparaitre commetnaire si coché // faire apparaitre / disparaitre commetnaire si coché
new ShowHide({ new ShowHide({
"froms": [div_accompagnement], froms: [div_accompagnement],
"test": function(froms, event) { test: function (froms, event) {
for (let el of froms.values()) { for (let el of froms.values()) {
for (let input of el.querySelectorAll('input').values()) { for (let input of el.querySelectorAll("input").values()) {
if (input.value === 'autre') { if (input.value === "autre") {
return input.checked; return input.checked;
} }
} }
@ -39,7 +38,7 @@ new ShowHide({
return false; return false;
}, },
"container": [div_accompagnement_comment] container: [div_accompagnement_comment],
}); });
// faire apparaitre cafInscriptionDate seulement si cafID est rempli // faire apparaitre cafInscriptionDate seulement si cafID est rempli
@ -51,7 +50,7 @@ new ShowHide({
} }
}, },
container: [div_caf_inscription_date], container: [div_caf_inscription_date],
event_name: 'input' event_name: "input",
}); });
// faire apparaitre date de commission neet seulement si eligible // faire apparaitre date de commission neet seulement si eligible
@ -59,7 +58,7 @@ new ShowHide({
froms: [div_neet_eligibilite], froms: [div_neet_eligibilite],
test: function (froms, event) { test: function (froms, event) {
for (let el of froms.values()) { for (let el of froms.values()) {
for (let input of el.querySelectorAll('input').values()) { for (let input of el.querySelectorAll("input").values()) {
if (input.value === "oui") { if (input.value === "oui") {
return input.checked; return input.checked;
} }
@ -68,5 +67,5 @@ new ShowHide({
return false; return false;
}, },
container: [ div_neet_commission_date ] container: [div_neet_commission_date],
}); });

View File

@ -1,20 +1,17 @@
import { ShowHide } from 'ShowHide/show_hide.js'; import { ShowHide } from "ShowHide/show_hide.js";
var
div_objectifs = document.getElementById("objectifs"),
div_objectifs_autre = document.getElementById("objectifsAutre")
;
var div_objectifs = document.getElementById("objectifs"),
div_objectifs_autre = document.getElementById("objectifsAutre");
new ShowHide({ new ShowHide({
froms: [div_objectifs], froms: [div_objectifs],
container: [div_objectifs_autre], container: [div_objectifs_autre],
test: function (froms, event) { test: function (froms, event) {
for (let el of froms.values()) { for (let el of froms.values()) {
for (let input of el.querySelectorAll('input').values()) { for (let input of el.querySelectorAll("input").values()) {
if (input.value === 'autre') { if (input.value === "autre") {
return input.checked; return input.checked;
} }
} }
} }
} },
}); });

View File

@ -1,28 +1,27 @@
import { ShowHide } from 'ShowHide/show_hide.js'; import { ShowHide } from "ShowHide/show_hide.js";
var var ressources = document.getElementById("ressources"),
ressources = document.getElementById("ressources"),
ressources_comment = document.getElementById("ressourcesComment"), ressources_comment = document.getElementById("ressourcesComment"),
handicap_is = document.getElementById('handicap_is'), handicap_is = document.getElementById("handicap_is"),
handicap_if = document.getElementById('handicap_if'), handicap_if = document.getElementById("handicap_if"),
situation_prof = document.getElementById('situation_prof'), situation_prof = document.getElementById("situation_prof"),
type_contrat = document.getElementById('type_contrat'), type_contrat = document.getElementById("type_contrat"),
type_contrat_aide = document.getElementById('type_contrat_aide'), type_contrat_aide = document.getElementById("type_contrat_aide"),
situation_logement = document.getElementById('situation_logement'), situation_logement = document.getElementById("situation_logement"),
situation_logement_precision = document.getElementById('situation_logement_precision') situation_logement_precision = document.getElementById(
; "situation_logement_precision",
);
new ShowHide({ new ShowHide({
froms: [ressources], froms: [ressources],
container: [ressources_comment], container: [ressources_comment],
test: function (froms) { test: function (froms) {
for (let f of froms.values()) { for (let f of froms.values()) {
for (let input of f.querySelectorAll('input').values()) { for (let input of f.querySelectorAll("input").values()) {
if (input.value === 'autre') { if (input.value === "autre") {
return input.checked; return input.checked;
} }
} }
} }
} },
}); });
new ShowHide({ new ShowHide({
@ -30,15 +29,15 @@ new ShowHide({
container: [handicap_if], container: [handicap_if],
test: function (froms) { test: function (froms) {
for (let f of froms.values()) { for (let f of froms.values()) {
for (let input of f.querySelectorAll('input').values()) { for (let input of f.querySelectorAll("input").values()) {
if (input.value === '1') { if (input.value === "1") {
return input.checked; return input.checked;
} }
} }
} }
return false; return false;
} },
}); });
var show_hide_contrat_aide = new ShowHide({ var show_hide_contrat_aide = new ShowHide({
@ -46,42 +45,42 @@ var show_hide_contrat_aide = new ShowHide({
container: [type_contrat_aide], container: [type_contrat_aide],
test: function (froms) { test: function (froms) {
for (let f of froms.values()) { for (let f of froms.values()) {
for (let input of f.querySelectorAll('input').values()) { for (let input of f.querySelectorAll("input").values()) {
if (input.value === 'contrat_aide') { if (input.value === "contrat_aide") {
return input.checked; return input.checked;
} }
} }
} }
return false; return false;
} },
}); });
new ShowHide({ new ShowHide({
id: 'situation_prof_type_contrat', id: "situation_prof_type_contrat",
froms: [situation_prof], froms: [situation_prof],
container: [type_contrat], container: [type_contrat],
test: function (froms) { test: function (froms) {
for (let f of froms.values()) { for (let f of froms.values()) {
for (let input of f.querySelectorAll('input').values()) { for (let input of f.querySelectorAll("input").values()) {
if (input.value === 'en_activite') { if (input.value === "en_activite") {
return input.checked; return input.checked;
} }
} }
} }
return false; return false;
} },
}); });
window.addEventListener('show-hide-hide', function (e) { window.addEventListener("show-hide-hide", function (e) {
if (e.detail.id = 'situation_prof_type_contrat') { if ((e.detail.id = "situation_prof_type_contrat")) {
show_hide_contrat_aide.forceHide(); show_hide_contrat_aide.forceHide();
} }
}); });
window.addEventListener('show-hide-show', function (e) { window.addEventListener("show-hide-show", function (e) {
if (e.detail.id = 'situation_prof_type_contrat') { if ((e.detail.id = "situation_prof_type_contrat")) {
show_hide_contrat_aide.forceCompute(); show_hide_contrat_aide.forceCompute();
} }
}); });
@ -91,13 +90,13 @@ new ShowHide({
container: [situation_logement_precision], container: [situation_logement_precision],
test: function (froms) { test: function (froms) {
for (let f of froms.values()) { for (let f of froms.values()) {
for (let input of f.querySelectorAll('input')) { for (let input of f.querySelectorAll("input")) {
if (input.value === 'heberge_chez_tiers') { if (input.value === "heberge_chez_tiers") {
return input.checked; return input.checked;
} }
} }
} }
return false; return false;
} },
}); });

View File

@ -4,47 +4,44 @@
*/ */
/// import jQuery /// import jQuery
const $ = require('jquery'); const $ = require("jquery");
global.$ = global.jQuery = $; global.$ = global.jQuery = $;
/// import select2 /// import select2
const select2 = require('select2'); const select2 = require("select2");
global.select2 = select2; global.select2 = select2;
require('select2/dist/css/select2.css'); require("select2/dist/css/select2.css");
require('select2-bootstrap-theme/dist/select2-bootstrap.css'); require("select2-bootstrap-theme/dist/select2-bootstrap.css");
/* /*
* Load Chill themes assets * Load Chill themes assets
*/ */
require('./chillmain.scss'); require("./chillmain.scss");
import { chill } from './js/chill.js'; import { chill } from "./js/chill.js";
global.chill = chill; global.chill = chill;
require('./js/date'); require("./js/date");
require('./js/counter.js'); require("./js/counter.js");
/// Load fonts /// Load fonts
require('./fonts/OpenSans/OpenSans.scss') require("./fonts/OpenSans/OpenSans.scss");
/// Load images /// Load images
require('./img/favicon.ico'); require("./img/favicon.ico");
require('./img/logo-chill-sans-slogan_white.png'); require("./img/logo-chill-sans-slogan_white.png");
require('./img/logo-chill-outil-accompagnement_white.png'); require("./img/logo-chill-outil-accompagnement_white.png");
/* /*
* Load local libs * Load local libs
* Some libs are only used in a few pages, they are loaded on a case by case basis * Some libs are only used in a few pages, they are loaded on a case by case basis
*/ */
require('../lib/breadcrumb/index.js'); require("../lib/breadcrumb/index.js");
require('../lib/download-report/index.js'); require("../lib/download-report/index.js");
require('../lib/select_interactive_loading/index.js'); require("../lib/select_interactive_loading/index.js");
//require('../lib/show_hide/index.js'); //require('../lib/show_hide/index.js');
//require('../lib/tabs/index.js'); //require('../lib/tabs/index.js');

View File

@ -1,10 +1,9 @@
/* jslint vars: true */ /* jslint vars: true */
/*jslint indent: 4 */ /*jslint indent: 4 */
/* global moment, $, window */ /* global moment, $, window */
'use strict'; "use strict";
var chill = function() {
var chill = (function () {
/** /**
* Display an alert message when the user wants to leave a page containing a given form * Display an alert message when the user wants to leave a page containing a given form
* in a given state. * in a given state.
@ -19,7 +18,11 @@ var chill = function() {
* contains some modified fields otherwise always display the alert when leaving * contains some modified fields otherwise always display the alert when leaving
* @return nothing * @return nothing
*/ */
function _generalDisplayAlertWhenLeavingForm(form_id, alert_message, check_unsaved_data) { function _generalDisplayAlertWhenLeavingForm(
form_id,
alert_message,
check_unsaved_data,
) {
var form_submitted = false; var form_submitted = false;
var unsaved_data = false; var unsaved_data = false;
@ -27,19 +30,18 @@ var chill = function() {
.submit(function () { .submit(function () {
form_submitted = true; form_submitted = true;
}) })
.on('reset', function() { .on("reset", function () {
unsaved_data = false; unsaved_data = false;
}) });
;
$.each($(form_id).find(':input'), function(i,e) { $.each($(form_id).find(":input"), function (i, e) {
$(e).change(function () { $(e).change(function () {
unsaved_data = true; unsaved_data = true;
}); });
}); });
$(window).bind('beforeunload', function(){ $(window).bind("beforeunload", function () {
if((!form_submitted) && (unsaved_data || !check_unsaved_data)) { if (!form_submitted && (unsaved_data || !check_unsaved_data)) {
return alert_message; return alert_message;
} }
}); });
@ -93,10 +95,12 @@ var chill = function() {
of an other field, its checkbox is checked. of an other field, its checkbox is checked.
*/ */
function checkOtherValueOnChange() { function checkOtherValueOnChange() {
$('.input-text-other-value').each(function() { $(".input-text-other-value").each(function () {
$(this).change(function () { $(this).change(function () {
var checkbox = $(this).parent().find('input[type=checkbox][value=_other]')[0]; var checkbox = $(this)
$(checkbox).prop('checked', ($(this).val() !== '')); .parent()
.find("input[type=checkbox][value=_other]")[0];
$(checkbox).prop("checked", $(this).val() !== "");
}); });
}); });
} }
@ -139,38 +143,45 @@ var chill = function() {
- quand choix - quand choix
*/ */
function categoryLinkParentChildSelect() { function categoryLinkParentChildSelect() {
var forms_to_link = $('form:has(select.chill-category-link-parent)'); var forms_to_link = $("form:has(select.chill-category-link-parent)");
forms_to_link.each(function (i, form_selector) { forms_to_link.each(function (i, form_selector) {
var form = $(form_selector), parent_multiple; var form = $(form_selector),
parent_multiple;
form.old_category = null; form.old_category = null;
form.link_parent = $(form).find('.chill-category-link-parent'); form.link_parent = $(form).find(".chill-category-link-parent");
form.link_child = $(form).find('.chill-category-link-child'); form.link_child = $(form).find(".chill-category-link-child");
// check if the parent allow multiple or single results // check if the parent allow multiple or single results
parent_multiple = $(form).find('.chill-category-link-parent').get(0).multiple; parent_multiple = $(form)
.find(".chill-category-link-parent")
.get(0).multiple;
// if we use select2, parent_multiple will be `undefined` // if we use select2, parent_multiple will be `undefined`
if (typeof parent_multiple == 'undefined') { if (typeof parent_multiple == "undefined") {
// currently, I do not know how to check if multiple using select2. // currently, I do not know how to check if multiple using select2.
// we suppose that multiple is false (old behaviour) // we suppose that multiple is false (old behaviour)
parent_multiple = false parent_multiple = false;
} }
$(form.link_parent).addClass('select2'); $(form.link_parent).addClass("select2");
$(form.link_parant).select2({ allowClear: true }); // it is weird: when I fix the typo here, the whole stuff does not work anymore... $(form.link_parant).select2({ allowClear: true }); // it is weird: when I fix the typo here, the whole stuff does not work anymore...
if (parent_multiple == false) { if (parent_multiple == false) {
form.old_category = null; form.old_category = null;
if($(form.link_parent).select2('data') !== null) { if ($(form.link_parent).select2("data") !== null) {
form.old_category = ($(form.link_parent).select2('data').element[0].dataset.linkCategory); form.old_category = $(form.link_parent).select2(
"data",
).element[0].dataset.linkCategory;
} }
$(form.link_child).find('option') $(form.link_child)
.find("option")
.each(function (i, e) { .each(function (i, e) {
if ( if (
((!$(e).data('link-category')) || $(e).data('link-category') == form.old_category) && (!$(e).data("link-category") ||
((!$(e).data('link-categories')) || form.old_category in $(e).data('link-categories').split(',')) $(e).data("link-category") == form.old_category) &&
(!$(e).data("link-categories") ||
form.old_category in $(e).data("link-categories").split(","))
) { ) {
$(e).show(); $(e).show();
// here, we should handle the optgroup // here, we should handle the optgroup
@ -181,13 +192,17 @@ var chill = function() {
}); });
form.link_parent.change(function () { form.link_parent.change(function () {
var new_category = ($(form.link_parent).select2('data').element[0].dataset.linkCategory); var new_category = $(form.link_parent).select2("data").element[0]
.dataset.linkCategory;
if (new_category != form.old_category) { if (new_category != form.old_category) {
$(form.link_child).find('option') $(form.link_child)
.find("option")
.each(function (i, e) { .each(function (i, e) {
if ( if (
((!$(e).data('link-category')) || $(e).data('link-category') == new_category) && (!$(e).data("link-category") ||
((!$(e).data('link-categories')) || new_category in $(e).data('link-categories').split(',')) $(e).data("link-category") == new_category) &&
(!$(e).data("link-categories") ||
new_category in $(e).data("link-categories").split(","))
) { ) {
$(e).show(); $(e).show();
// here, we should handle the optgroup // here, we should handle the optgroup
@ -196,20 +211,21 @@ var chill = function() {
// here, we should handle the opgroup // here, we should handle the opgroup
} }
}); });
$(form.link_child).find('option')[0].selected = true; $(form.link_child).find("option")[0].selected = true;
form.old_category = new_category; form.old_category = new_category;
} }
}); });
} else { } else {
var i = 0, var i = 0,
selected_items = $(form.link_parent).find(':selected'); selected_items = $(form.link_parent).find(":selected");
form.old_categories = []; form.old_categories = [];
for (i = 0; i < selected_items.length; i++) { for (i = 0; i < selected_items.length; i++) {
form.old_categories.push(selected_items[i].value); form.old_categories.push(selected_items[i].value);
} }
$(form.link_child).find('option') $(form.link_child)
.find("option")
.each(function (i, e) { .each(function (i, e) {
var visible; var visible;
if (form.old_categories.indexOf(e.dataset.linkCategory) != -1) { if (form.old_categories.indexOf(e.dataset.linkCategory) != -1) {
@ -221,9 +237,13 @@ var chill = function() {
$(e).hide(); $(e).hide();
if (e.parentNode instanceof HTMLOptGroupElement) { if (e.parentNode instanceof HTMLOptGroupElement) {
// we check if the other options are visible. // we check if the other options are visible.
visible = false visible = false;
for (var l = 0; l < e.parentNode.children.length; l++) { for (var l = 0; l < e.parentNode.children.length; l++) {
if (window.getComputedStyle(e.parentNode.children[l]).getPropertyValue('display') != 'none') { if (
window
.getComputedStyle(e.parentNode.children[l])
.getPropertyValue("display") != "none"
) {
visible = true; visible = true;
} }
} }
@ -237,14 +257,15 @@ var chill = function() {
form.link_parent.change(function () { form.link_parent.change(function () {
var new_categories = [], var new_categories = [],
selected_items = $(form.link_parent).find(':selected'), selected_items = $(form.link_parent).find(":selected"),
visible; visible;
for (i = 0; i < selected_items.length; i++) { for (i = 0; i < selected_items.length; i++) {
new_categories.push(selected_items[i].value); new_categories.push(selected_items[i].value);
} }
if (new_categories != form.old_categories) { if (new_categories != form.old_categories) {
$(form.link_child).find('option') $(form.link_child)
.find("option")
.each(function (i, e) { .each(function (i, e) {
if (new_categories.indexOf(e.dataset.linkCategory) != -1) { if (new_categories.indexOf(e.dataset.linkCategory) != -1) {
$(e).show(); $(e).show();
@ -257,9 +278,13 @@ var chill = function() {
// we check if the parent is an optgroup // we check if the parent is an optgroup
if (e.parentNode instanceof HTMLOptGroupElement) { if (e.parentNode instanceof HTMLOptGroupElement) {
// we check if other options are visible // we check if other options are visible
visible = false visible = false;
for (var l = 0; l < e.parentNode.children.length; l++) { for (var l = 0; l < e.parentNode.children.length; l++) {
if (window.getComputedStyle(e.parentNode.children[l]).getPropertyValue('display') != 'none') { if (
window
.getComputedStyle(e.parentNode.children[l])
.getPropertyValue("display") != "none"
) {
visible = true; visible = true;
} }
} }
@ -282,7 +307,9 @@ var chill = function() {
var target = checkbox.dataset.displayTarget, var target = checkbox.dataset.displayTarget,
hideableElements; hideableElements;
hideableElements = document.querySelectorAll('[data-display-show-hide="' + target + '"]'); hideableElements = document.querySelectorAll(
'[data-display-show-hide="' + target + '"]',
);
if (checkbox.checked) { if (checkbox.checked) {
for (let i = 0; i < hideableElements.length; i = i + 1) { for (let i = 0; i < hideableElements.length; i = i + 1) {
@ -293,7 +320,6 @@ var chill = function() {
hideableElements[i].style.display = "none"; hideableElements[i].style.display = "none";
} }
} }
} }
/** /**
@ -335,11 +361,12 @@ var chill = function() {
return { return {
checkOtherValueOnChange: checkOtherValueOnChange, checkOtherValueOnChange: checkOtherValueOnChange,
displayAlertWhenLeavingModifiedForm: displayAlertWhenLeavingModifiedForm, displayAlertWhenLeavingModifiedForm: displayAlertWhenLeavingModifiedForm,
displayAlertWhenLeavingUnsubmittedForm: displayAlertWhenLeavingUnsubmittedForm, displayAlertWhenLeavingUnsubmittedForm:
displayAlertWhenLeavingUnsubmittedForm,
checkNullValuesInChoices: checkNullValuesInChoices, checkNullValuesInChoices: checkNullValuesInChoices,
categoryLinkParentChildSelect: categoryLinkParentChildSelect, categoryLinkParentChildSelect: categoryLinkParentChildSelect,
listenerDisplayCheckbox: listenerDisplayCheckbox, listenerDisplayCheckbox: listenerDisplayCheckbox,
}; };
} (); })();
export { chill }; export { chill };

View File

@ -9,26 +9,23 @@
const isNum = (v) => !isNaN(v); const isNum = (v) => !isNaN(v);
const parseCounter = () => { const parseCounter = () => {
document.querySelectorAll('span.counter') document.querySelectorAll("span.counter").forEach((el) => {
.forEach(el => {
let r = []; let r = [];
el.innerText el.innerText
.trim() .trim()
.split(' ') .split(" ")
.forEach(w => { .forEach((w) => {
if (isNum(w)) { if (isNum(w)) {
r.push(`<span>${w}</span>`); r.push(`<span>${w}</span>`);
} else { } else {
r.push(w); r.push(w);
} }
}) });
; el.innerHTML = r.join(" ");
el.innerHTML = r.join(' '); });
})
;
}; };
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener("DOMContentLoaded", function (e) {
parseCounter(); parseCounter();
}); });

View File

@ -19,9 +19,9 @@ export const dateToISO = (date: Date|null): string|null => {
return [ return [
date.getFullYear(), date.getFullYear(),
(date.getMonth() + 1).toString().padStart(2, '0'), (date.getMonth() + 1).toString().padStart(2, "0"),
date.getDate().toString().padStart(2, '0') date.getDate().toString().padStart(2, "0"),
].join('-'); ].join("-");
}; };
/** /**
@ -37,11 +37,10 @@ export const ISOToDate = (str: string|null): Date|null => {
return null; return null;
} }
let const [year, month, day] = str.split("-").map((p) => parseInt(p));
[year, month, day] = str.split('-').map(p => parseInt(p));
return new Date(year, month - 1, day, 0, 0, 0, 0); return new Date(year, month - 1, day, 0, 0, 0, 0);
} };
/** /**
* Return a date object from iso string formatted as YYYY-mm-dd:HH:MM:ss+01:00 * Return a date object from iso string formatted as YYYY-mm-dd:HH:MM:ss+01:00
@ -52,19 +51,18 @@ export const ISOToDatetime = (str: string|null): Date|null => {
return null; return null;
} }
let const [cal, times] = str.split("T"),
[cal, times] = str.split('T'), [year, month, date] = cal.split("-").map((s) => parseInt(s)),
[year, month, date] = cal.split('-').map(s => parseInt(s)),
[time, timezone] = times.split(times.charAt(8)), [time, timezone] = times.split(times.charAt(8)),
[hours, minutes, seconds] = time.split(':').map(s => parseInt(s)); [hours, minutes, seconds] = time.split(":").map((s) => parseInt(s));
; if ("0000" === timezone) {
return new Date(
if ('0000' === timezone) { Date.UTC(year, month - 1, date, hours, minutes, seconds),
return new Date(Date.UTC(year, month-1, date, hours, minutes, seconds)); );
} }
return new Date(year, month - 1, date, hours, minutes, seconds); return new Date(year, month - 1, date, hours, minutes, seconds);
} };
/** /**
* Convert a date to ISO8601, valid for usage in api * Convert a date to ISO8601, valid for usage in api
@ -74,39 +72,43 @@ export const datetimeToISO = (date: Date): string => {
let cal, time, offset; let cal, time, offset;
cal = [ cal = [
date.getFullYear(), date.getFullYear(),
(date.getMonth() + 1).toString().padStart(2, '0'), (date.getMonth() + 1).toString().padStart(2, "0"),
date.getDate().toString().padStart(2, '0') date.getDate().toString().padStart(2, "0"),
].join('-'); ].join("-");
time = [ time = [
date.getHours().toString().padStart(2, '0'), date.getHours().toString().padStart(2, "0"),
date.getMinutes().toString().padStart(2, '0'), date.getMinutes().toString().padStart(2, "0"),
date.getSeconds().toString().padStart(2, '0') date.getSeconds().toString().padStart(2, "0"),
].join(':'); ].join(":");
offset = [ offset = [
date.getTimezoneOffset() <= 0 ? '+' : '-', date.getTimezoneOffset() <= 0 ? "+" : "-",
Math.abs(Math.floor(date.getTimezoneOffset() / 60)).toString().padStart(2, '0'), Math.abs(Math.floor(date.getTimezoneOffset() / 60))
':', .toString()
Math.abs(date.getTimezoneOffset() % 60).toString().padStart(2, '0'), .padStart(2, "0"),
].join(''); ":",
Math.abs(date.getTimezoneOffset() % 60)
.toString()
.padStart(2, "0"),
].join("");
let x = cal + 'T' + time + offset; const x = cal + "T" + time + offset;
return x; return x;
}; };
export const intervalDaysToISO = (days: number | string | null): string => { export const intervalDaysToISO = (days: number | string | null): string => {
if (null === days) { if (null === days) {
return 'P0D'; return "P0D";
} }
return `P${days}D`; return `P${days}D`;
} };
export const intervalISOToDays = (str: string | null): number | null => { export const intervalISOToDays = (str: string | null): number | null => {
if (null === str) { if (null === str) {
return null return null;
} }
if ("" === str.trim()) { if ("" === str.trim()) {
@ -121,40 +123,42 @@ export const intervalISOToDays = (str: string|null): number|null => {
continue; continue;
} }
switch (str.charAt(i)) { switch (str.charAt(i)) {
case 'P': case "P":
isDate = true; isDate = true;
break; break;
case 'T': case "T":
isDate = false; isDate = false;
break; break;
case '0': case "0":
case '1': case "1":
case '2': case "2":
case '3': case "3":
case '4': case "4":
case '5': case "5":
case '6': case "6":
case '7': case "7":
case '8': case "8":
case '9': case "9":
vstring = vstring + str.charAt(i); vstring = vstring + str.charAt(i);
break; break;
case 'Y': case "Y":
days = days + Number.parseInt(vstring) * 365; days = days + Number.parseInt(vstring) * 365;
vstring = ""; vstring = "";
break; break;
case 'M': case "M":
days = days + Number.parseInt(vstring) * 30; days = days + Number.parseInt(vstring) * 30;
vstring = ""; vstring = "";
break; break;
case 'D': case "D":
days = days + Number.parseInt(vstring); days = days + Number.parseInt(vstring);
vstring = ""; vstring = "";
break; break;
default: default:
throw Error("this character should not appears: " + str.charAt(i)); throw Error(
"this character should not appears: " + str.charAt(i),
);
} }
} }
return days; return days;
} };

View File

@ -1,8 +1,11 @@
import {Address, GeographicalUnitLayer, SimpleGeographicalUnit} from "../../types"; import {
Address,
GeographicalUnitLayer,
SimpleGeographicalUnit,
} from "../../types";
import { fetchResults, makeFetch } from "./apiMethods"; import { fetchResults, makeFetch } from "./apiMethods";
export const getAddressById = async (address_id: number): Promise<Address> => export const getAddressById = async (address_id: number): Promise<Address> => {
{
const url = `/api/1.0/main/address/${address_id}.json`; const url = `/api/1.0/main/address/${address_id}.json`;
const response = await fetch(url); const response = await fetch(url);
@ -11,25 +14,48 @@ export const getAddressById = async (address_id: number): Promise<Address> =>
return response.json(); return response.json();
} }
throw Error('Error with request resource response'); throw Error("Error with request resource response");
}; };
export const getGeographicalUnitsByAddress = async (address: Address): Promise<SimpleGeographicalUnit[]> => { export const getGeographicalUnitsByAddress = async (
return fetchResults<SimpleGeographicalUnit>(`/api/1.0/main/geographical-unit/by-address/${address.address_id}.json`); address: Address,
} ): Promise<SimpleGeographicalUnit[]> => {
return fetchResults<SimpleGeographicalUnit>(
`/api/1.0/main/geographical-unit/by-address/${address.address_id}.json`,
);
};
export const getAllGeographicalUnitLayers = async (): Promise<GeographicalUnitLayer[]> => { export const getAllGeographicalUnitLayers = async (): Promise<
return fetchResults<GeographicalUnitLayer>(`/api/1.0/main/geographical-unit-layer.json`); GeographicalUnitLayer[]
} > => {
return fetchResults<GeographicalUnitLayer>(
`/api/1.0/main/geographical-unit-layer.json`,
);
};
export const syncAddressWithReference = async (address: Address): Promise<Address> => { export const syncAddressWithReference = async (
return makeFetch<null, Address>("POST", `/api/1.0/main/address/reference-match/${address.address_id}/sync-with-reference`); address: Address,
} ): Promise<Address> => {
return makeFetch<null, Address>(
"POST",
`/api/1.0/main/address/reference-match/${address.address_id}/sync-with-reference`,
);
};
export const markAddressReviewed = async (address: Address): Promise<Address> => { export const markAddressReviewed = async (
return makeFetch<null, Address>("POST", `/api/1.0/main/address/reference-match/${address.address_id}/set/reviewed`); address: Address,
} ): Promise<Address> => {
return makeFetch<null, Address>(
"POST",
`/api/1.0/main/address/reference-match/${address.address_id}/set/reviewed`,
);
};
export const markAddressToReview = async (address: Address): Promise<Address> => { export const markAddressToReview = async (
return makeFetch<null, Address>("POST", `/api/1.0/main/address/reference-match/${address.address_id}/set/to_review`); address: Address,
} ): Promise<Address> => {
return makeFetch<null, Address>(
"POST",
`/api/1.0/main/address/reference-match/${address.address_id}/set/to_review`,
);
};

View File

@ -1,11 +1,9 @@
import {Scope} from '../../types'; import { Scope } from "../../types";
export type body = {[key: string]: boolean|string|number|null}; export type body = Record<string, boolean | string | number | null>;
export type fetchOption = {[key: string]: boolean|string|number|null}; export type fetchOption = Record<string, boolean | string | number | null>;
export interface Params { export type Params = Record<string, number | string>;
[key: string]: number|string
}
export interface PaginationResponse<T> { export interface PaginationResponse<T> {
pagination: { pagination: {
@ -16,16 +14,15 @@ export interface PaginationResponse<T> {
count: number; count: number;
} }
export interface FetchParams { export type FetchParams = Record<string, string | number | null>;
[K: string]: string|number|null;
};
export interface TransportExceptionInterface { export interface TransportExceptionInterface {
name: string; name: string;
} }
export interface ValidationExceptionInterface extends TransportExceptionInterface { export interface ValidationExceptionInterface
name: 'ValidationException'; extends TransportExceptionInterface {
name: "ValidationException";
error: object; error: object;
violations: string[]; violations: string[];
titles: string[]; titles: string[];
@ -40,23 +37,25 @@ export interface ValidationErrorResponse extends TransportExceptionInterface {
} }
export interface AccessExceptionInterface extends TransportExceptionInterface { export interface AccessExceptionInterface extends TransportExceptionInterface {
name: 'AccessException'; name: "AccessException";
violations: string[]; violations: string[];
} }
export interface NotFoundExceptionInterface extends TransportExceptionInterface { export interface NotFoundExceptionInterface
name: 'NotFoundException'; extends TransportExceptionInterface {
name: "NotFoundException";
} }
export interface ServerExceptionInterface extends TransportExceptionInterface { export interface ServerExceptionInterface extends TransportExceptionInterface {
name: 'ServerException'; name: "ServerException";
message: string; message: string;
code: number; code: number;
body: string; body: string;
} }
export interface ConflictHttpExceptionInterface extends TransportExceptionInterface { export interface ConflictHttpExceptionInterface
name: 'ConflictHttpException'; extends TransportExceptionInterface {
name: "ConflictHttpException";
violations: string[]; violations: string[];
} }
@ -64,35 +63,33 @@ export interface ConflictHttpExceptionInterface extends TransportExceptionInterf
* Generic api method that can be adapted to any fetch request * Generic api method that can be adapted to any fetch request
*/ */
export const makeFetch = <Input, Output>( export const makeFetch = <Input, Output>(
method: 'POST'|'GET'|'PUT'|'PATCH'|'DELETE', method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE",
url: string, body?: body | Input | null, url: string,
options?: FetchParams body?: body | Input | null,
options?: FetchParams,
): Promise<Output> => { ): Promise<Output> => {
let opts = { let opts = {
method: method, method: method,
headers: { headers: {
'Content-Type': 'application/json;charset=utf-8' "Content-Type": "application/json;charset=utf-8",
}, },
}; };
if (body !== null && typeof body !== "undefined") {
if (body !== null && typeof body !== 'undefined') { Object.assign(opts, { body: JSON.stringify(body) });
Object.assign(opts, {body: JSON.stringify(body)})
} }
if (typeof options !== 'undefined') { if (typeof options !== "undefined") {
opts = Object.assign(opts, options); opts = Object.assign(opts, options);
} }
return fetch(url, opts) return fetch(url, opts).then((response) => {
.then(response => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
if (response.status === 422) { if (response.status === 422) {
return response.json().then(response => { return response.json().then((response) => {
throw ValidationException(response) throw ValidationException(response);
}); });
} }
@ -105,55 +102,62 @@ export const makeFetch = <Input, Output>(
} }
throw { throw {
name: 'Exception', name: "Exception",
sta: response.status, sta: response.status,
txt: response.statusText, txt: response.statusText,
err: new Error(), err: new Error(),
violations: response.body violations: response.body,
}; };
}); });
} };
/** /**
* Fetch results with certain parameters * Fetch results with certain parameters
*/ */
function _fetchAction<T>(page: number, uri: string, params?: FetchParams): Promise<PaginationResponse<T>> { function _fetchAction<T>(
const item_per_page: number = 50; page: number,
uri: string,
params?: FetchParams,
): Promise<PaginationResponse<T>> {
const item_per_page = 50;
let searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
searchParams.append('item_per_page', item_per_page.toString()); searchParams.append("item_per_page", item_per_page.toString());
searchParams.append('page', page.toString()); searchParams.append("page", page.toString());
if (params !== undefined) { if (params !== undefined) {
Object.keys(params).forEach(key => { Object.keys(params).forEach((key) => {
let v = params[key]; const v = params[key];
if (typeof v === 'string') { if (typeof v === "string") {
searchParams.append(key, v); searchParams.append(key, v);
} else if (typeof v === 'number') { } else if (typeof v === "number") {
searchParams.append(key, v.toString()); searchParams.append(key, v.toString());
} else if (v === null) { } else if (v === null) {
searchParams.append(key, ''); searchParams.append(key, "");
} }
}); });
} }
let url = uri + '?' + searchParams.toString(); const url = uri + "?" + searchParams.toString();
return fetch(url, { return fetch(url, {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json;charset=utf-8' "Content-Type": "application/json;charset=utf-8",
}, },
}).then((response) => { })
if (response.ok) { return response.json(); } .then((response) => {
if (response.ok) {
return response.json();
}
if (response.status === 404) { if (response.status === 404) {
throw NotFoundException(response); throw NotFoundException(response);
} }
if (response.status === 422) { if (response.status === 422) {
return response.json().then(response => { return response.json().then((response) => {
throw ValidationException(response) throw ValidationException(response);
}); });
} }
@ -162,22 +166,30 @@ function _fetchAction<T>(page: number, uri: string, params?: FetchParams): Promi
} }
if (response.status >= 500) { if (response.status >= 500) {
return response.text().then(body => { return response.text().then((body) => {
throw ServerException(response.status, body); throw ServerException(response.status, body);
}); });
} }
throw new Error("other network error"); throw new Error("other network error");
}).catch((reason: any) => { })
.catch((reason: any) => {
console.error(reason); console.error(reason);
throw new Error(reason); throw new Error(reason);
}); });
}; }
export const fetchResults = async<T> (uri: string, params?: FetchParams): Promise<T[]> => { export const fetchResults = async <T>(
uri: string,
params?: FetchParams,
): Promise<T[]> => {
let promises: Promise<T[]>[] = [], let promises: Promise<T[]>[] = [],
page = 1; page = 1;
let firstData: PaginationResponse<T> = await _fetchAction(page, uri, params) as PaginationResponse<T>; const firstData: PaginationResponse<T> = (await _fetchAction(
page,
uri,
params,
)) as PaginationResponse<T>;
promises.push(Promise.resolve(firstData.results)); promises.push(Promise.resolve(firstData.results));
@ -185,61 +197,74 @@ export const fetchResults = async<T> (uri: string, params?: FetchParams): Promis
do { do {
page = ++page; page = ++page;
promises.push( promises.push(
_fetchAction<T>(page, uri, params) _fetchAction<T>(page, uri, params).then((r) =>
.then(r => Promise.resolve(r.results)) Promise.resolve(r.results),
),
); );
} while (page * firstData.pagination.items_per_page < firstData.count) } while (page * firstData.pagination.items_per_page < firstData.count);
} }
return Promise.all(promises).then((values) => values.flat()); return Promise.all(promises).then((values) => values.flat());
}; };
export const fetchScopes = (): Promise<Scope[]> => { export const fetchScopes = (): Promise<Scope[]> => {
return fetchResults('/api/1.0/main/scope.json'); return fetchResults("/api/1.0/main/scope.json");
}; };
/** /**
* Error objects to be thrown * Error objects to be thrown
*/ */
const ValidationException = (response: ValidationErrorResponse): ValidationExceptionInterface => { const ValidationException = (
response: ValidationErrorResponse,
): ValidationExceptionInterface => {
const error = {} as ValidationExceptionInterface; const error = {} as ValidationExceptionInterface;
error.name = 'ValidationException'; error.name = "ValidationException";
error.violations = response.violations.map((violation) => `${violation.title}: ${violation.propertyPath}`); error.violations = response.violations.map(
(violation) => `${violation.title}: ${violation.propertyPath}`,
);
error.titles = response.violations.map((violation) => violation.title); error.titles = response.violations.map((violation) => violation.title);
error.propertyPaths = response.violations.map((violation) => violation.propertyPath); error.propertyPaths = response.violations.map(
(violation) => violation.propertyPath,
);
return error; return error;
} };
const AccessException = (response: Response): AccessExceptionInterface => { const AccessException = (response: Response): AccessExceptionInterface => {
const error = {} as AccessExceptionInterface; const error = {} as AccessExceptionInterface;
error.name = 'AccessException'; error.name = "AccessException";
error.violations = ['You are not allowed to perform this action']; error.violations = ["You are not allowed to perform this action"];
return error; return error;
} };
const NotFoundException = (response: Response): NotFoundExceptionInterface => { const NotFoundException = (response: Response): NotFoundExceptionInterface => {
const error = {} as NotFoundExceptionInterface; const error = {} as NotFoundExceptionInterface;
error.name = 'NotFoundException'; error.name = "NotFoundException";
return error; return error;
} };
const ServerException = (code: number, body: string): ServerExceptionInterface => { const ServerException = (
code: number,
body: string,
): ServerExceptionInterface => {
const error = {} as ServerExceptionInterface; const error = {} as ServerExceptionInterface;
error.name = 'ServerException'; error.name = "ServerException";
error.code = code; error.code = code;
error.body = body; error.body = body;
return error; return error;
} };
const ConflictHttpException = (response: Response): ConflictHttpExceptionInterface => { const ConflictHttpException = (
response: Response,
): ConflictHttpExceptionInterface => {
const error = {} as ConflictHttpExceptionInterface; const error = {} as ConflictHttpExceptionInterface;
error.name = 'ConflictHttpException'; error.name = "ConflictHttpException";
error.violations = ['Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again'] error.violations = [
"Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again",
];
return error; return error;
} };

View File

@ -1,4 +1,3 @@
// const _fetchAction = (page, uri, params) => { // const _fetchAction = (page, uri, params) => {
// const item_per_page = 50; // const item_per_page = 50;
// if (params === undefined) { // if (params === undefined) {

View File

@ -1,6 +1,8 @@
import { fetchResults } from "./apiMethods"; import { fetchResults } from "./apiMethods";
import { Location, LocationType } from "../../types"; import { Location, LocationType } from "../../types";
export const getLocations = (): Promise<Location[]> => fetchResults('/api/1.0/main/location.json'); export const getLocations = (): Promise<Location[]> =>
fetchResults("/api/1.0/main/location.json");
export const getLocationTypes = (): Promise<LocationType[]> => fetchResults('/api/1.0/main/location-type.json'); export const getLocationTypes = (): Promise<LocationType[]> =>
fetchResults("/api/1.0/main/location-type.json");

View File

@ -3,17 +3,16 @@ import {makeFetch} from "./apiMethods";
export const whoami = (): Promise<User> => { export const whoami = (): Promise<User> => {
const url = `/api/1.0/main/whoami.json`; const url = `/api/1.0/main/whoami.json`;
return fetch(url) return fetch(url).then((response) => {
.then(response => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
throw { throw {
msg: 'Error while getting whoami.', msg: "Error while getting whoami.",
sta: response.status, sta: response.status,
txt: response.statusText, txt: response.statusText,
err: new Error(), err: new Error(),
body: response.body body: response.body,
}; };
}); });
}; };
@ -22,4 +21,4 @@ export const whereami = (): Promise<Location | null> => {
const url = `/api/1.0/main/user-current-location.json`; const url = `/api/1.0/main/user-current-location.json`;
return makeFetch<null, Location | null>("GET", url); return makeFetch<null, Location | null>("GET", url);
} };

View File

@ -1,3 +1 @@
require("./layout.scss"); require("./layout.scss");

View File

@ -15,40 +15,44 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import mime from 'mime'; import mime from "mime";
export const download_report = (url, container) => { export const download_report = (url, container) => {
var download_text = container.dataset.downloadText, var download_text = container.dataset.downloadText,
alias = container.dataset.alias; alias = container.dataset.alias;
window.fetch(url, { credentials: 'same-origin' }) window
.then(response => { .fetch(url, { credentials: "same-origin" })
.then((response) => {
if (!response.ok) { if (!response.ok) {
throw Error(response.statusText); throw Error(response.statusText);
} }
return response.blob(); return response.blob();
}).then(blob => { })
.then((blob) => {
var content = URL.createObjectURL(blob), var content = URL.createObjectURL(blob),
link = document.createElement("a"), link = document.createElement("a"),
type = blob.type, type = blob.type,
hasForcedType = 'mimeType' in container.dataset, hasForcedType = "mimeType" in container.dataset,
extension; extension;
if (hasForcedType) { if (hasForcedType) {
// force a type // force a type
type = container.dataset.mimeType; type = container.dataset.mimeType;
blob = new Blob([ blob ], { 'type': type }); blob = new Blob([blob], { type: type });
content = URL.createObjectURL(blob); content = URL.createObjectURL(blob);
} }
const extensions = new Map(); const extensions = new Map();
extensions.set('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlsx'); extensions.set(
extensions.set('application/vnd.oasis.opendocument.spreadsheet', 'ods'); "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
extensions.set('application/vnd.ms-excel', 'xlsx'); "xlsx",
extensions.set('text/csv', 'csv'); );
extensions.set('text/csv; charset=utf-8', 'csv'); extensions.set("application/vnd.oasis.opendocument.spreadsheet", "ods");
extensions.set("application/vnd.ms-excel", "xlsx");
extensions.set("text/csv", "csv");
extensions.set("text/csv; charset=utf-8", "csv");
extension = extensions.get(type); extension = extensions.get(type);
@ -57,16 +61,15 @@ export const download_report = (url, container) => {
link.href = content; link.href = content;
link.download = alias; link.download = alias;
if (extension !== false) { if (extension !== false) {
link.download = link.download + '.' + extension; link.download = link.download + "." + extension;
} }
container.innerHTML = ""; container.innerHTML = "";
container.appendChild(link); container.appendChild(link);
}).catch(function(error) { })
.catch(function (error) {
console.error(error); console.error(error);
var problem_text = var problem_text = document.createTextNode("Problem during download");
document.createTextNode("Problem during download");
container container.replaceChild(problem_text, container.firstChild);
.replaceChild(problem_text, container.firstChild);
}); });
}; };

View File

@ -1,20 +1,22 @@
const buildLinkCreate = function (relatedEntityClass: string, relatedEntityId: number, to: number | null, returnPath: string | null): string const buildLinkCreate = function (
{ relatedEntityClass: string,
relatedEntityId: number,
to: number | null,
returnPath: string | null,
): string {
const params = new URLSearchParams(); const params = new URLSearchParams();
params.append('entityClass', relatedEntityClass); params.append("entityClass", relatedEntityClass);
params.append('entityId', relatedEntityId.toString()); params.append("entityId", relatedEntityId.toString());
if (null !== to) { if (null !== to) {
params.append('tos[0]', to.toString()); params.append("tos[0]", to.toString());
} }
if (null !== returnPath) { if (null !== returnPath) {
params.append('returnPath', returnPath); params.append("returnPath", returnPath);
} }
return `/fr/notification/create?${params.toString()}`; return `/fr/notification/create?${params.toString()}`;
} };
export { export { buildLinkCreate };
buildLinkCreate,
}

View File

@ -1,18 +1,12 @@
window.addEventListener("load", function (e) {
window.addEventListener('load', function (e) { var postalCodes = document.querySelectorAll(
var "[data-select-interactive-loading]",
postalCodes = document.querySelectorAll('[data-select-interactive-loading]') );
;
for (let i = 0; i < postalCodes.length; i++) { for (let i = 0; i < postalCodes.length; i++) {
let let searchUrl = postalCodes[i].dataset.searchUrl,
searchUrl = postalCodes[i].dataset.searchUrl,
noResultsLabel = postalCodes[i].dataset.noResultsLabel, noResultsLabel = postalCodes[i].dataset.noResultsLabel,
errorLoadLabel = postalCodes[i].dataset.errorLoadLabel, errorLoadLabel = postalCodes[i].dataset.errorLoadLabel,
searchingLabel = postalCodes[i].dataset.searchingLabel searchingLabel = postalCodes[i].dataset.searchingLabel;
;
$(postalCodes[i]).select2({ $(postalCodes[i]).select2({
allowClear: true, allowClear: true,
language: { language: {
@ -24,13 +18,13 @@ window.addEventListener('load', function (e) {
}, },
searching: function () { searching: function () {
return searchingLabel; return searchingLabel;
} },
}, },
ajax: { ajax: {
url: searchUrl, url: searchUrl,
dataType: 'json', dataType: "json",
delay: 250 delay: 250,
} },
}); });
} }
}); });

View File

@ -1,5 +1,5 @@
//require("./show_hide.js"); //require("./show_hide.js");
import { ShowHide } from './show_hide.js' import { ShowHide } from "./show_hide.js";
export { ShowHide } export { ShowHide };

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