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:
- /vendor/
- .cache
- node_modules/
# 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.
@ -103,6 +104,32 @@ rector_tests:
paths:
- 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:
stage: Tests
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>
Embeddable comments <embeddable-comments.rst>
Run tests <run-tests.rst>
ESLint <es-lint.rst>
Useful snippets <useful-snippets.rst>
manual/index.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
div_accompagnement = document.getElementById("form_accompagnement"),
div_accompagnement_comment = document.getElementById("form_accompagnement_comment"),
var div_accompagnement = document.getElementById("form_accompagnement"),
div_accompagnement_comment = document.getElementById(
"form_accompagnement_comment",
),
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
new ShowHide({
"froms": [div_accompagnement],
"test": function(froms, event) {
froms: [div_accompagnement],
test: function (froms, event) {
for (let el of froms.values()) {
for (let input of el.querySelectorAll('input').values()) {
if (input.value === 'autre') {
for (let input of el.querySelectorAll("input").values()) {
if (input.value === "autre") {
return input.checked;
}
}
@ -21,7 +20,7 @@ new ShowHide({
return false;
},
"container": [div_accompagnement_comment]
container: [div_accompagnement_comment],
});
// let show the date input only if the the id is filled
@ -34,6 +33,5 @@ new ShowHide({
},
container: [div_caf_inscription_date],
// 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-theme-lark": "^41.4.2",
"@ckeditor/ckeditor5-vue": "^5.1.0",
"@eslint/js": "^9.14.0",
"@luminateone/eslint-baseline": "^1.0.9",
"@symfony/webpack-encore": "^4.1.0",
"@tsconfig/node14": "^1.0.1",
"@types/dompurify": "^3.0.5",
"@types/eslint__js": "^8.42.3",
"@typescript-eslint/parser": "^8.12.2",
"bindings": "^1.5.0",
"bootstrap": "5.2.3",
"chokidar": "^3.5.1",
"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",
"jquery": "^3.6.0",
"marked": "^12.0.1",
"node-sass": "^8.0.0",
"popper.js": "^1.16.1",
"postcss-loader": "^7.0.2",
"prettier": "^3.3.3",
"raw-loader": "^4.0.2",
"sass-loader": "^14.0.0",
"select2": "^4.0.13",
"select2-bootstrap-theme": "0.1.0-beta.10",
"style-loader": "^3.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",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
@ -68,6 +78,7 @@
"scripts": {
"dev-server": "encore dev-server",
"dev": "encore dev",
"prettier": "prettier --write \"**/*.{js,ts,vue}\"",
"watch": "encore dev --watch",
"build": "encore production --progress"
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,21 +1,21 @@
import 'es6-promise/auto';
import { createStore } from 'vuex';
import { postLocation } from './api';
import prepareLocations from './store.locations.js';
import "es6-promise/auto";
import { createStore } from "vuex";
import { postLocation } from "./api";
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);
const addIdToValue = (string, id) => {
let array = string ? string.split(',') : [];
let array = string ? string.split(",") : [];
array.push(id.toString());
let str = array.join();
return str;
};
const removeIdFromValue = (string, id) => {
let array = string.split(',');
array = array.filter(el => el !== id.toString());
let array = string.split(",");
array = array.filter((el) => el !== id.toString());
let str = array.join();
return str;
};
@ -30,7 +30,10 @@ const store = createStore({
},
getters: {
suggestedEntities(state) {
if (typeof state.activity.accompanyingPeriod === "undefined" || state.activity.accompanyingPeriod === null) {
if (
typeof state.activity.accompanyingPeriod === "undefined" ||
state.activity.accompanyingPeriod === null
) {
return [];
}
const allEntities = [
@ -44,7 +47,7 @@ const store = createStore({
];
return Array.from(
uniqueIds,
(id) => allEntities.filter((r) => `${r.type}-${r.id}` === id)[0]
(id) => allEntities.filter((r) => `${r.type}-${r.id}` === id)[0],
);
},
suggestedPersons(state) {
@ -62,7 +65,7 @@ const store = createStore({
}
const existingPersonIds = state.activity.persons.map((p) => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map(
(p) => p.id
(p) => p.id,
);
return [state.activity.accompanyingPeriod.requestor].filter(
@ -72,7 +75,7 @@ const store = createStore({
state.activity.activityType.personsVisible !== 0) ||
(r.type === "thirdparty" &&
!existingThirdPartyIds.includes(r.id) &&
state.activity.activityType.thirdPartiesVisible !== 0)
state.activity.activityType.thirdPartiesVisible !== 0),
);
},
suggestedUser(state) {
@ -80,14 +83,14 @@ const store = createStore({
return state.activity.activityType.usersVisible === 0
? []
: [state.activity.accompanyingPeriod.user].filter(
(u) => u !== null && !existingUserIds.includes(u.id)
(u) => u !== null && !existingUserIds.includes(u.id),
);
},
suggestedResources(state) {
const resources = state.activity.accompanyingPeriod.resources;
const existingPersonIds = state.activity.persons.map((p) => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map(
(p) => p.id
(p) => p.id,
);
return state.activity.accompanyingPeriod.resources
.map((r) => r.resource)
@ -98,11 +101,13 @@ const store = createStore({
state.activity.activityType.personsVisible !== 0) ||
(r.type === "thirdparty" &&
!existingThirdPartyIds.includes(r.id) &&
state.activity.activityType.thirdPartiesVisible !== 0)
state.activity.activityType.thirdPartiesVisible !== 0),
);
},
socialActionsListSorted(state) {
return [ ...state.socialActionsList].sort((a, b) => a.ordering - b.ordering);
return [...state.socialActionsList].sort(
(a, b) => a.ordering - b.ordering,
);
},
},
mutations: {
@ -126,7 +131,7 @@ const store = createStore({
removeIssueInOther(state, issue) {
//console.log('remove issue other', issue.id);
state.socialIssuesOther = state.socialIssuesOther.filter(
(i) => i.id !== issue.id
(i) => i.id !== issue.id,
);
},
resetActionsList(state) {
@ -145,18 +150,15 @@ const store = createStore({
// remove duplicates entries
list = list.filter(
(value, index) =>
list.findIndex((array) => array.id === value.id) ===
index
list.findIndex((array) => array.id === value.id) === index,
);
// alpha sort
list.sort((a, b) =>
a.text > b.text ? 1 : b.text > a.text ? -1 : 0
);
list.sort((a, b) => (a.text > b.text ? 1 : b.text > a.text ? -1 : 0));
return list;
};
if (list === "issues") {
state.activity.accompanyingPeriod.socialIssues = filterList(
state.activity.accompanyingPeriod.socialIssues
state.activity.accompanyingPeriod.socialIssues,
);
}
if (list === "actions") {
@ -184,18 +186,17 @@ const store = createStore({
switch (payload.type) {
case "person":
state.activity.persons = state.activity.persons.filter(
(person) => person !== payload
(person) => person !== payload,
);
break;
case "thirdparty":
state.activity.thirdParties =
state.activity.thirdParties.filter(
(thirdparty) => thirdparty !== payload
state.activity.thirdParties = state.activity.thirdParties.filter(
(thirdparty) => thirdparty !== payload,
);
break;
case "user":
state.activity.users = state.activity.users.filter(
(user) => user !== payload
(user) => user !== payload,
);
break;
}
@ -206,39 +207,33 @@ const store = createStore({
},
addAvailableLocationGroup(state, group) {
state.availableLocations.push(group);
}
},
},
actions: {
addIssueSelected({ commit }, issue) {
let aSocialIssues = document.getElementById(
"chill_activitybundle_activity_socialIssues"
"chill_activitybundle_activity_socialIssues",
);
aSocialIssues.value = addIdToValue(aSocialIssues.value, issue.id);
commit("addIssueSelected", issue);
},
updateIssuesSelected({ commit }, payload) {
let aSocialIssues = document.getElementById(
"chill_activitybundle_activity_socialIssues"
"chill_activitybundle_activity_socialIssues",
);
aSocialIssues.value = "";
payload.forEach((item) => {
aSocialIssues.value = addIdToValue(
aSocialIssues.value,
item.id
);
aSocialIssues.value = addIdToValue(aSocialIssues.value, item.id);
});
commit("updateIssuesSelected", payload);
},
updateActionsSelected({ commit }, payload) {
let aSocialActions = document.getElementById(
"chill_activitybundle_activity_socialActions"
"chill_activitybundle_activity_socialActions",
);
aSocialActions.value = "";
payload.forEach((item) => {
aSocialActions.value = addIdToValue(
aSocialActions.value,
item.id
);
aSocialActions.value = addIdToValue(aSocialActions.value, item.id);
});
commit("updateActionsSelected", payload);
},
@ -250,30 +245,24 @@ const store = createStore({
switch (payload.result.type) {
case "person":
let aPersons = document.getElementById(
"chill_activitybundle_activity_persons"
);
aPersons.value = addIdToValue(
aPersons.value,
payload.result.id
"chill_activitybundle_activity_persons",
);
aPersons.value = addIdToValue(aPersons.value, payload.result.id);
break;
case "thirdparty":
let aThirdParties = document.getElementById(
"chill_activitybundle_activity_thirdParties"
"chill_activitybundle_activity_thirdParties",
);
aThirdParties.value = addIdToValue(
aThirdParties.value,
payload.result.id
payload.result.id,
);
break;
case "user":
let aUsers = document.getElementById(
"chill_activitybundle_activity_users"
);
aUsers.value = addIdToValue(
aUsers.value,
payload.result.id
"chill_activitybundle_activity_users",
);
aUsers.value = addIdToValue(aUsers.value, payload.result.id);
break;
}
commit("addPersonsInvolved", payload);
@ -283,25 +272,22 @@ const store = createStore({
switch (payload.type) {
case "person":
let aPersons = document.getElementById(
"chill_activitybundle_activity_persons"
);
aPersons.value = removeIdFromValue(
aPersons.value,
payload.id
"chill_activitybundle_activity_persons",
);
aPersons.value = removeIdFromValue(aPersons.value, payload.id);
break;
case "thirdparty":
let aThirdParties = document.getElementById(
"chill_activitybundle_activity_thirdParties"
"chill_activitybundle_activity_thirdParties",
);
aThirdParties.value = removeIdFromValue(
aThirdParties.value,
payload.id
payload.id,
);
break;
case "user":
let aUsers = document.getElementById(
"chill_activitybundle_activity_users"
"chill_activitybundle_activity_users",
);
aUsers.value = removeIdFromValue(aUsers.value, payload.id);
break;
@ -311,32 +297,30 @@ const store = createStore({
updateLocation({ commit }, value) {
console.log("### action: updateLocation", value);
let hiddenLocation = document.getElementById(
"chill_activitybundle_activity_location"
"chill_activitybundle_activity_location",
);
if (value.onthefly) {
const body = {
"type": "location",
"name": value.name === '__AccompanyingCourseLocation__' ? null : value.name,
"locationType": {
"id": value.locationType.id,
"type": "location-type"
}
type: "location",
name:
value.name === "__AccompanyingCourseLocation__" ? null : value.name,
locationType: {
id: value.locationType.id,
type: "location-type",
},
};
if (value.address.id) {
Object.assign(body, {
"address": {
"id": value.address.id
address: {
id: value.address.id,
},
})
});
}
postLocation(body)
.then(
location => hiddenLocation.value = location.id
).catch(
err => {
.then((location) => (hiddenLocation.value = location.id))
.catch((err) => {
console.log(err.message);
}
);
});
} else {
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) => {
let locations = [];
store.getters.suggestedEntities.forEach(
(e) => {
if (e.type === 'person' && e.current_household_address !== null){
store.getters.suggestedEntities.forEach((e) => {
if (e.type === "person" && e.current_household_address !== null) {
locations.push({
type: 'location',
type: "location",
id: -store.getters.suggestedEntities.indexOf(e) * 10,
onthefly: true,
name: e.text,
address: {
id: e.current_household_address.address_id,
},
locationType: locationType
locationType: locationType,
});
}
}
)
});
return locations;
};
const makeConcernedThirdPartiesLocation = (locationType, store) => {
let locations = [];
store.getters.suggestedEntities.forEach(
(e) => {
if (e.type === 'thirdparty' && e.address !== null){
store.getters.suggestedEntities.forEach((e) => {
if (e.type === "thirdparty" && e.address !== null) {
locations.push({
type: 'location',
type: "location",
id: -store.getters.suggestedEntities.indexOf(e) * 10,
onthefly: true,
name: e.text,
address: { id: e.address.address_id },
locationType: locationType
locationType: locationType,
});
}
}
)
});
return locations;
};
const makeAccompanyingPeriodLocation = (locationType, store) => {
@ -44,78 +44,86 @@ const makeAccompanyingPeriodLocation = (locationType, store) => {
}
const accPeriodLocation = store.state.activity.accompanyingPeriod.location;
return {
type: 'location',
type: "location",
id: -1,
onthefly: true,
name: '__AccompanyingCourseLocation__',
name: "__AccompanyingCourseLocation__",
address: {
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) {
// find the locations
let allLocations = getLocations().then(
(results) => {
store.commit('addAvailableLocationGroup', {
locationGroup: 'Autres localisations',
locations: results
let allLocations = getLocations().then((results) => {
store.commit("addAvailableLocationGroup", {
locationGroup: "Autres localisations",
locations: results,
});
});
}
);
let currentLocation = getUserCurrentLocation().then(
userCurrentLocation => {
let currentLocation = getUserCurrentLocation().then((userCurrentLocation) => {
if (null !== userCurrentLocation) {
store.commit('addAvailableLocationGroup', {
locationGroup: 'Ma localisation',
locations: [userCurrentLocation]
store.commit("addAvailableLocationGroup", {
locationGroup: "Ma localisation",
locations: [userCurrentLocation],
});
}
}
);
});
let partiesLocations = [], partyPromise;
['person', 'thirdparty'].forEach(kind => {
let partiesLocations = [],
partyPromise;
["person", "thirdparty"].forEach((kind) => {
partyPromise = getLocationTypeByDefaultFor(kind).then(
(kindLocationType) => {
if (kindLocationType) {
let concernedKindLocations;
if (kind === 'person') {
concernedKindLocations = makeConcernedPersonsLocation(kindLocationType, store);
if (kind === "person") {
concernedKindLocations = makeConcernedPersonsLocation(
kindLocationType,
store,
);
// add location for the parcours into suggestions
const personLocation = makeAccompanyingPeriodLocation(kindLocationType, store);
store.commit('addAvailableLocationGroup', {
locationGroup: 'Localisation du parcours',
locations: [personLocation]
const personLocation = makeAccompanyingPeriodLocation(
kindLocationType,
store,
);
store.commit("addAvailableLocationGroup", {
locationGroup: "Localisation du parcours",
locations: [personLocation],
});
} else {
concernedKindLocations = makeConcernedThirdPartiesLocation(kindLocationType, store);
concernedKindLocations = makeConcernedThirdPartiesLocation(
kindLocationType,
store,
);
}
store.commit('addAvailableLocationGroup', {
locationGroup: kind === 'person' ? 'Usagers concernés' : 'Tiers concernés',
store.commit("addAvailableLocationGroup", {
locationGroup:
kind === "person" ? "Usagers concernés" : "Tiers concernés",
locations: concernedKindLocations,
});
}
}
},
);
partiesLocations.push(partyPromise);
});
// when all location are loaded
Promise.all([allLocations, currentLocation, ...partiesLocations]).then(() => {
console.log('current location in activity', store.state.activity.location);
console.log('default loation id', window.default_location_id);
console.log("current location in activity", store.state.activity.location);
console.log("default loation id", window.default_location_id);
if (window.default_location_id) {
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) {
store.dispatch('updateLocation', location);
store.dispatch("updateLocation", location);
break;
}
}

View File

@ -1,13 +1,18 @@
// this file loads all assets from the Chill person bundle
module.exports = function(encore, entries)
{
entries.push(__dirname + '/Resources/public/chill/index.js');
module.exports = function (encore, entries) {
entries.push(__dirname + "/Resources/public/chill/index.js");
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
module.exports = function(encore, entries)
{
module.exports = function (encore, entries) {
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/calendar-list.scss';
import "./scss/badge.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 Answer from 'ChillCalendarAssets/vuejs/Invite/Answer';
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
import { createApp } from "vue";
import Answer from "ChillCalendarAssets/vuejs/Invite/Answer";
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
const i18n = _createI18n({});
document.addEventListener('DOMContentLoaded', function (e) {
console.log('dom loaded answer');
document.querySelectorAll('div[invite-answer]').forEach(function (el) {
console.log('element found', el);
document.addEventListener("DOMContentLoaded", function (e) {
console.log("dom loaded answer");
document.querySelectorAll("div[invite-answer]").forEach(function (el) {
console.log("element found", el);
const app = createApp({
components: {
@ -18,14 +17,15 @@ document.addEventListener('DOMContentLoaded', function (e) {
return {
status: el.dataset.status,
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: {
onStatusChanged: function (newStatus) {
this.$data.status = newStatus;
},
}
},
});
app.use(i18n).mount(el);

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import {fetchResults} from '../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods';
import {datetimeToISO} from '../../../../../ChillMainBundle/Resources/public/chill/js/date';
import {User} from '../../../../../ChillMainBundle/Resources/public/types';
import {CalendarLight, CalendarRange, CalendarRemote} from '../../types';
import { fetchResults } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import { datetimeToISO } from "../../../../../ChillMainBundle/Resources/public/chill/js/date";
import { User } from "../../../../../ChillMainBundle/Resources/public/types";
import { CalendarLight, CalendarRange, CalendarRemote } from "../../types";
// re-export whoami
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
* @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 dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
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 dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
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 dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
}
};

View File

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

View File

@ -1,6 +1,6 @@
import {personMessages} from 'ChillPersonAssets/vuejs/_js/i18n'
import {calendarUserSelectorMessages} from '../_components/CalendarUserSelector/js/i18n';
import {activityMessages} from 'ChillActivityAssets/vuejs/Activity/i18n';
import { personMessages } from "ChillPersonAssets/vuejs/_js/i18n";
import { calendarUserSelectorMessages } from "../_components/CalendarUserSelector/js/i18n";
import { activityMessages } from "ChillActivityAssets/vuejs/Activity/i18n";
const appMessages = {
fr: {
@ -13,20 +13,22 @@ const appMessages = {
bloc_thirdparty: "Tiers professionnels",
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 ?",
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 ?",
main_user_is_mandatory: "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é',
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 ?",
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 ?",
main_user_is_mandatory:
"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",
}
}
},
};
Object.assign(appMessages.fr, personMessages.fr);
Object.assign(appMessages.fr, calendarUserSelectorMessages.fr);
Object.assign(appMessages.fr, activityMessages.fr);
export {
appMessages
};
export { appMessages };

View File

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

View File

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

View File

@ -18,7 +18,7 @@ export default {
if (null === state.activity.start) {
return new Date();
}
throw 'transform date to object ?';
throw "transform date to object ?";
},
/**
* Compute the event sources to show on the FullCalendar
@ -33,7 +33,7 @@ export default {
// current calendar
if (state.activity.startDate !== null && state.activity.endDate !== null) {
const s = {
id: 'current',
id: "current",
events: [
{
title: "Rendez-vous",
@ -41,8 +41,8 @@ export default {
end: state.activity.endDate,
allDay: false,
is: "current",
classNames: ['iscurrent'],
}
classNames: ["iscurrent"],
},
],
editable: state.activity.calendarRange === null,
};
@ -52,7 +52,7 @@ export default {
for (const [userId, kinds] of state.currentView.users.entries()) {
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;
}
@ -61,11 +61,16 @@ export default {
if (kinds.ranges && userData.calendarRanges.length > 0) {
const s = {
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,
classNames: ['isrange'],
backgroundColor: 'white',
textColor: 'black',
classNames: ["isrange"],
backgroundColor: "white",
textColor: "black",
editable: false,
};
@ -74,10 +79,10 @@ export default {
if (kinds.remotes && userData.remotes.length > 0) {
const s = {
'id': `remote_${userId}`,
id: `remote_${userId}`,
events: userData.remotes,
color: userData.mainColor,
textColor: 'black',
textColor: "black",
editable: false,
};
@ -87,10 +92,12 @@ export default {
// if remotes is checked, we display also the locals calendars
if (kinds.remotes && userData.locals.length > 0) {
const s = {
'id': `local_${userId}`,
events: userData.locals.filter(l => l.originId !== state.activity.id),
id: `local_${userId}`,
events: userData.locals.filter(
(l) => l.originId !== state.activity.id,
),
color: userData.mainColor,
textColor: 'black',
textColor: "black",
editable: false,
};
@ -104,7 +111,7 @@ export default {
return state.activity.startDate;
},
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
@ -138,7 +145,9 @@ export default {
* @param getters
* @returns {(function({user: *, start: *, end: *}): (boolean))|*}
*/
isCalendarRangeLoadedForUser: (state, getters) => ({user, start, end}) => {
isCalendarRangeLoadedForUser:
(state, getters) =>
({ user, start, end }) => {
if (!getters.hasUserData(user)) {
return false;
}
@ -159,7 +168,9 @@ export default {
* @param getters
* @returns {(function({user: *, start: *, end: *}): (boolean))|*}
*/
isCalendarRemoteLoadedForUser: (state, getters) => ({user, start, end}) => {
isCalendarRemoteLoadedForUser:
(state, getters) =>
({ user, start, end }) => {
if (!getters.hasUserData(user)) {
return false;
}
@ -180,8 +191,10 @@ export default {
*/
isRangeShownOnCalendarForUser: (state) => (user) => {
const k = state.currentView.users.get(user.id);
if (typeof k === 'undefined') {
console.error('try to determinate if calendar range is shown and user is not in currentView');
if (typeof k === "undefined") {
console.error(
"try to determinate if calendar range is shown and user is not in currentView",
);
return false;
}
@ -195,8 +208,10 @@ export default {
*/
isRemoteShownOnCalendarForUser: (state) => (user) => {
const k = state.currentView.users.get(user.id);
if (typeof k === 'undefined') {
console.error('try to determinate if calendar range is shown and user is not in currentView');
if (typeof k === "undefined") {
console.error(
"try to determinate if calendar range is shown and user is not in currentView",
);
return false;
}
@ -205,8 +220,8 @@ export default {
getLocationById: (state) => (id) => {
for (let group of state.availableLocations) {
console.log('group', group);
const found = group.locations.find(l => l.id === id);
console.log("group", group);
const found = group.locations.find((l) => l.id === id);
if (typeof found !== "undefined") {
return found;
}
@ -216,57 +231,60 @@ export default {
},
suggestedEntities(state, getters) {
if (typeof (state.activity.accompanyingPeriod) === 'undefined') {
if (typeof state.activity.accompanyingPeriod === "undefined") {
return [];
}
const allEntities = [
...getters.suggestedPersons,
...getters.suggestedRequestor,
...getters.suggestedUser,
...getters.suggestedResources
...getters.suggestedResources,
];
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]);
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],
);
},
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
.filter(p => p.endDate === null)
.map(p => p.person)
.filter(p => !existingPersonIds.includes(p.id))
.filter((p) => p.endDate === null)
.map((p) => p.person)
.filter((p) => !existingPersonIds.includes(p.id));
},
suggestedRequestor(state) {
if (state.activity.accompanyingPeriod.requestor === null) {
return [];
}
const existingPersonIds = state.activity.persons.map(p => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map(p => p.id);
return [state.activity.accompanyingPeriod.requestor]
.filter(r =>
(r.type === 'person' && !existingPersonIds.includes(r.id)) ||
(r.type === 'thirdparty' && !existingThirdPartyIds.includes(r.id))
const existingPersonIds = state.activity.persons.map((p) => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map((p) => p.id);
return [state.activity.accompanyingPeriod.requestor].filter(
(r) =>
(r.type === "person" && !existingPersonIds.includes(r.id)) ||
(r.type === "thirdparty" && !existingThirdPartyIds.includes(r.id)),
);
},
suggestedUser(state) {
if (null === state.activity.users) {
return [];
}
const existingUserIds = state.activity.users.map(p => p.id);
return [state.activity.accompanyingPeriod.user]
.filter(
u => u !== null && !existingUserIds.includes(u.id)
const existingUserIds = state.activity.users.map((p) => p.id);
return [state.activity.accompanyingPeriod.user].filter(
(u) => u !== null && !existingUserIds.includes(u.id),
);
},
suggestedResources(state) {
const resources = state.activity.accompanyingPeriod.resources;
const existingPersonIds = state.activity.persons.map(p => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map(p => p.id);
const existingPersonIds = state.activity.persons.map((p) => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map((p) => p.id);
return state.activity.accompanyingPeriod.resources
.map(r => r.resource)
.filter(r =>
(r.type === 'person' && !existingPersonIds.includes(r.id)) ||
(r.type === 'thirdparty' && !existingThirdPartyIds.includes(r.id))
.map((r) => r.resource)
.filter(
(r) =>
(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 { createStore } from 'vuex';
import { postLocation } from 'ChillActivityAssets/vuejs/Activity/api';
import getters from './getters';
import actions from './actions';
import mutations from './mutations';
import { mapEntity } from './utils';
import { whoami } from '../api';
import "es6-promise/auto";
import { createStore } from "vuex";
import { postLocation } from "ChillActivityAssets/vuejs/Activity/api";
import getters from "./getters";
import actions from "./actions";
import mutations from "./mutations";
import { mapEntity } from "./utils";
import { whoami } from "../api";
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({
strict: debug,
@ -42,16 +42,24 @@ const store = createStore({
actions,
});
whoami().then(me => {
store.commit('setWhoAmiI', me);
whoami().then((me) => {
store.commit("setWhoAmiI", me);
});
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) {
store.commit('showUserOnCalendar', {ranges: false, remotes: false, user: u});
store.commit("showUserOnCalendar", {
ranges: false,
remotes: false,
user: u,
});
}
prepareLocations(store);

View File

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

View File

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

View File

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

View File

@ -23,14 +23,22 @@
<label class="input-group-text" for="slotDuration"
>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:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option>
</select>
<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="01:00:00">1h</option>
<option value="02:00:00">2h</option>
@ -46,7 +54,11 @@
<option value="12:00:00">12h</option>
</select>
<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="13:00:00">13h</option>
<option value="14:00:00">14h</option>
@ -74,7 +86,9 @@
v-model="showWeekends"
/>
</span>
<label for="showHideWE" class="form-check-label input-group-text"
<label
for="showHideWE"
class="form-check-label input-group-text"
>Week-ends</label
>
</div>
@ -88,7 +102,8 @@
arg.event.title
}}</b>
<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'">{{
arg.event.title
@ -106,29 +121,45 @@
<div id="copy-widget">
<div class="container mt-2 mb-2">
<div class="row justify-content-between align-items-center mb-4">
<div class="col-xs-12 col-sm-3 col-md-2">
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
</div>
<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="week">{{ $t("from_week_to_week") }}</option>
<option value="week">
{{ $t("from_week_to_week") }}
</option>
</select>
</div>
<template v-if="dayOrWeek === 'day'">
<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 class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
<i class="fa fa-angle-double-right"></i>
</div>
<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 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") }}
</button>
</div>
@ -140,7 +171,11 @@
id="copyFromWeek"
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 }}
</option>
</select>
@ -149,21 +184,31 @@
<i class="fa fa-angle-double-right"></i>
</div>
<div class="col-xs-12 col-sm-3 col-md-3">
<select v-model="copyToWeek" id="copyToWeek" class="form-select">
<option v-for="w in nextWeeks" :value="w.value" :key="w.value">
<select
v-model="copyToWeek"
id="copyToWeek"
class="form-select"
>
<option
v-for="w in nextWeeks"
:value="w.value"
:key="w.value"
>
{{ w.text }}
</option>
</select>
</div>
<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") }}
</button>
</div>
</template>
</div>
</div>
</div>
<!-- not directly seen, but include in a modal -->
@ -224,7 +269,7 @@ interface Weeks {
const getMonday = (week: number): Date => {
const lastMonday = new Date();
lastMonday.setDate(
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
);
return lastMonday;
};
@ -243,7 +288,7 @@ const lastWeeks = computed((): Weeks[] =>
value: dateToISO(lastMonday),
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
};
})
}),
);
const nextWeeks = computed((): Weeks[] =>
@ -253,7 +298,7 @@ const nextWeeks = computed((): Weeks[] =>
value: dateToISO(nextMonday),
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
};
})
}),
);
const baseOptions = ref<CalendarOptions>({
@ -298,7 +343,9 @@ const pickedLocation = computed<Location | null>({
);
},
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 {
if (null === pickedLocation.value) {
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;
}
@ -376,7 +423,7 @@ function onClickDelete(event: EventApi): void {
store.dispatch(
"calendarRanges/deleteRange",
event.extendedProps.calendarRangeId
event.extendedProps.calendarRangeId,
);
}

View File

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

View File

@ -8,7 +8,8 @@ const appMessages = {
copy_range_from_to: "Copier les plages",
from_day_to_day: "d'un jour à 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",
update_range_to_save: "Plages à modifier",
delete_range_to_save: "Plages à supprimer",
@ -20,9 +21,7 @@ const appMessages = {
week: "Semaine",
month: "Mois",
today: "Aujourd'hui",
}
}
export {
appMessages
},
};
export { appMessages };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +1,25 @@
// this file loads all assets from the Chill calendar bundle
module.exports = function (encore, entries) {
entries.push(__dirname + '/Resources/public/chill/chill.js');
entries.push(__dirname + "/Resources/public/chill/chill.js");
encore.addAliases({
ChillCalendarAssets: __dirname + '/Resources/public'
ChillCalendarAssets: __dirname + "/Resources/public",
});
encore.addEntry('vue_calendar', __dirname + '/Resources/public/vuejs/Calendar/index.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');
encore.addEntry(
"vue_calendar",
__dirname + "/Resources/public/vuejs/Calendar/index.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) => {
let fqdnEntityClass = encodeURI(entityClass);
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
entityIdEncoded = encodeURI(entityId),
returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash),
const entityIdEncoded = encodeURI(entityId),
returnPath = encodeURIComponent(
window.location.pathname + window.location.search + window.location.hash,
),
entityClassEncoded = encodeURI(entityClass),
url = `/fr/doc/gen/generate/from/${templateId}/for/${entityClassEncoded}/${entityIdEncoded}?returnPath=${returnPath}`
;
console.log('computed Url');
url = `/fr/doc/gen/generate/from/${templateId}/for/${entityClassEncoded}/${entityIdEncoded}?returnPath=${returnPath}`;
console.log("computed Url");
return url;
};

View File

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

View File

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

View File

@ -1,8 +1,11 @@
// this file loads all assets from the Chill DocGenerator bundle
module.exports = function (encore, entries) {
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
buttons = root.querySelectorAll('a[data-download-button]');
var buttons = root.querySelectorAll("a[data-download-button]");
for (let i = 0; i < buttons.length; i++) {
initialize(buttons[i]);
@ -12,18 +11,17 @@ var initializeButtons = (root) => {
};
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
keyData = JSON.parse(button.dataset.key),
var keyData = JSON.parse(button.dataset.key),
ivData = JSON.parse(button.dataset.iv),
iv = new Uint8Array(ivData),
urlGenerator = button.dataset.tempUrlGetGenerator,
hasFilename = 'filename' in button.dataset,
hasFilename = "filename" in button.dataset,
filename = button.dataset.filename,
labelPreparing = button.dataset.labelPreparing,
labelReady = button.dataset.labelReady,
@ -31,65 +29,70 @@ var download = (button) => {
extension = mime.getExtension(mimeType),
decryptError = "Error while decrypting file",
fetchError = "Error while fetching file",
key, url
;
key,
url;
button.textContent = labelPreparing;
window.fetch(urlGenerator)
window
.fetch(urlGenerator)
.then((r) => {
if (r.ok) {
return r.json();
} 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);
})
.then(response => {
.then((response) => {
if (response.ok) {
return response.arrayBuffer();
}
throw new Error(response.status + response.statusText);
})
.then(buffer => {
.then((buffer) => {
if (keyData.alg !== undefined) {
return window.crypto.subtle
.importKey('jwk', keyData, { name: algo, iv: iv}, false, ['decrypt'])
.then(key => {
return window.crypto.subtle.decrypt({ name: algo, iv: iv }, key, buffer);
.importKey("jwk", keyData, { name: algo, iv: iv }, false, ["decrypt"])
.then((key) => {
return window.crypto.subtle.decrypt(
{ name: algo, iv: iv },
key,
buffer,
);
});
}
return Promise.resolve(buffer);
})
.then(decrypted => {
var
blob = new Blob([decrypted], { type: mimeType }),
url = window.URL.createObjectURL(blob)
;
.then((decrypted) => {
var blob = new Blob([decrypted], { type: mimeType }),
url = window.URL.createObjectURL(blob);
button.href = url;
button.target = '_blank';
button.target = "_blank";
button.type = mimeType;
button.textContent = labelReady;
if (hasFilename) {
button.download = filename;
if (extension !== false) {
button.download = button.download + '.' + extension;
button.download = button.download + "." + extension;
}
}
button.removeEventListener('click', onClick);
button.removeEventListener("click", onClick);
button.click();
})
.catch(error => {
.catch((error) => {
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);
});

View File

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

View File

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

View File

@ -1,23 +1,58 @@
<template>
<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
</button>
<ul class="dropdown-menu">
<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 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 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 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 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>
</ul>
</div>
@ -27,31 +62,34 @@
<div v-else-if="'failure' === props.storedObject.status">
<div class="btn btn-outline-danger">La génération a échoué</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted } from "vue";
import ConvertButton from "./StoredObjectButton/ConvertButton.vue";
import DownloadButton from "./StoredObjectButton/DownloadButton.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 {
StoredObject,
StoredObjectStatusChange, StoredObjectVersion,
WopiEditButtonExecutableBeforeLeaveFunction
StoredObjectStatusChange,
StoredObjectVersion,
WopiEditButtonExecutableBeforeLeaveFunction,
} from "../types";
import DesktopEditButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DesktopEditButton.vue";
import HistoryButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton.vue";
interface DocumentActionButtonsGroupConfig {
storedObject: StoredObject,
small?: boolean,
canEdit?: boolean,
canDownload?: boolean,
canConvertPdf?: boolean,
returnPath?: string,
storedObject: StoredObject;
small?: boolean;
canEdit?: boolean;
canDownload?: boolean;
canConvertPdf?: boolean;
returnPath?: string;
/**
* 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.
*/
filename?: string,
filename?: string;
/**
* If set, will execute this function before leaving to the editor
*/
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction,
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
/**
* a link to download and edit file using webdav
*/
davLink?: string,
davLink?: string;
/**
* the expiration date of the download, as a unix timestamp
*/
davLinkExpiration?: number,
davLinkExpiration?: number;
}
const emit = defineEmits<{
(e: 'onStoredObjectStatusChange', newStatus: StoredObjectStatusChange): void
}>();
const emit =
defineEmits<
(
e: "onStoredObjectStatusChange",
newStatus: StoredObjectStatusChange,
) => void
>();
const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
small: false,
canEdit: true,
canDownload: 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 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>(() => {
return props.storedObject.status === 'ready'
return (
props.storedObject.status === "ready" ||
// 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>(() => {
return props.storedObject.status === 'ready'
&& props.storedObject._permissions.canEdit
&& props.canEdit
&& props.storedObject.currentVersion !== null
&& is_extension_editable(props.storedObject.currentVersion.type)
&& props.storedObject.currentVersion.persisted !== false;
return (
props.storedObject.status === "ready" &&
props.storedObject._permissions.canEdit &&
props.canEdit &&
props.storedObject.currentVersion !== null &&
is_extension_editable(props.storedObject.currentVersion.type) &&
props.storedObject.currentVersion.persisted !== false
);
});
const isEditableOnDesktop = computed<boolean>(() => {
@ -123,26 +178,28 @@ const isEditableOnDesktop = computed<boolean>(() => {
});
const isConvertibleToPdf = computed<boolean>(() => {
return props.storedObject.status === 'ready'
&& props.storedObject._permissions.canSee
&& props.canConvertPdf
&& props.storedObject.currentVersion !== null
&& is_extension_viewable(props.storedObject.currentVersion.type)
&& props.storedObject.currentVersion.type !== 'application/pdf'
&& props.storedObject.currentVersion.persisted !== false;
return (
props.storedObject.status === "ready" &&
props.storedObject._permissions.canSee &&
props.canConvertPdf &&
props.storedObject.currentVersion !== null &&
is_extension_viewable(props.storedObject.currentVersion.type) &&
props.storedObject.currentVersion.type !== "application/pdf" &&
props.storedObject.currentVersion.persisted !== false
);
});
const isHistoryViewable = computed<boolean>(() => {
return props.storedObject.status === 'ready';
return props.storedObject.status === "ready";
});
const checkForReady = function (): void {
if (
'ready' === props.storedObject.status
|| 'empty' === props.storedObject.status
|| 'failure' === props.storedObject.status
"ready" === props.storedObject.status ||
"empty" === props.storedObject.status ||
"failure" === props.storedObject.status ||
// stop reloading if the page stays opened for a long time
|| tryiesForReady > maxTryiesForReady
tryiesForReady > maxTryiesForReady
) {
return;
}
@ -153,20 +210,19 @@ const checkForReady = function(): void {
};
const onObjectNewStatusCallback = async function (): Promise<void> {
if (props.storedObject.status === 'stored_object_created') {
if (props.storedObject.status === "stored_object_created") {
return Promise.resolve();
}
const new_status = await is_object_ready(props.storedObject);
if (props.storedObject.status !== new_status.status) {
emit('onStoredObjectStatusChange', new_status);
emit("onStoredObjectStatusChange", new_status);
return Promise.resolve();
} else if ('failure' === new_status.status) {
} else if ("failure" === new_status.status) {
return Promise.resolve();
}
if ('ready' !== new_status.status) {
if ("ready" !== new_status.status) {
// we check for new status, unless it is ready
checkForReady();
}
@ -176,9 +232,7 @@ const onObjectNewStatusCallback = async function(): Promise<void> {
onMounted(() => {
checkForReady();
})
});
</script>
<style scoped>
</style>
<style scoped></style>

View File

@ -27,7 +27,9 @@
</modal>
</teleport>
<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">
<select
class="form-select form-select-sm"
@ -89,7 +91,10 @@
class="col-5 p-0 text-center turnSignature"
>
<button
:disabled="userSignatureZone === null || userSignatureZone?.index < 1"
:disabled="
userSignatureZone === null ||
userSignatureZone?.index < 1
"
class="btn btn-light btn-sm"
@click="turnSignature(-1)"
>
@ -97,7 +102,9 @@
</button>
<span>|</span>
<button
:disabled="userSignatureZone?.index >= signature.zones.length - 1"
:disabled="
userSignatureZone?.index >= signature.zones.length - 1
"
class="btn btn-light btn-sm"
@click="turnSignature(1)"
>
@ -135,7 +142,10 @@
:title="$t('add_sign_zone')"
>
<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>
</div>
</template>
@ -190,7 +200,10 @@
class="col-4 d-xl-none text-center turnSignature p-0"
>
<button
:disabled="userSignatureZone === null || userSignatureZone?.index < 1"
:disabled="
userSignatureZone === null ||
userSignatureZone?.index < 1
"
class="btn btn-light btn-sm"
@click="turnSignature(-1)"
>
@ -198,7 +211,9 @@
</button>
<span>|</span>
<button
:disabled="userSignatureZone?.index >= signature.zones.length - 1"
:disabled="
userSignatureZone?.index >= signature.zones.length - 1
"
class="btn btn-light btn-sm"
@click="turnSignature(1)"
>
@ -210,7 +225,10 @@
class="col-4 d-none d-xl-flex p-0 text-center turnSignature"
>
<button
:disabled="userSignatureZone === null || userSignatureZone?.index < 1"
:disabled="
userSignatureZone === null ||
userSignatureZone?.index < 1
"
class="btn btn-light btn-sm"
@click="turnSignature(-1)"
>
@ -218,7 +236,9 @@
</button>
<span>|</span>
<button
:disabled="userSignatureZone?.index >= signature.zones.length - 1"
:disabled="
userSignatureZone?.index >= signature.zones.length - 1
"
class="btn btn-light btn-sm"
@click="turnSignature(1)"
>
@ -259,7 +279,10 @@
</template>
<template v-else>
{{ $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>
</div>
</template>
@ -336,7 +359,10 @@ console.log(PdfWorker); // incredible but this is needed
// pdfjsLib.GlobalWorkerOptions.workerSrc = PdfWorker;
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import {download_and_decrypt_doc, download_doc_as_pdf} from "../StoredObjectButton/helpers";
import {
download_and_decrypt_doc,
download_doc_as_pdf,
} from "../StoredObjectButton/helpers";
pdfjsLib.GlobalWorkerOptions.workerSrc = "pdfjs-dist/build/pdf.worker.mjs";
@ -479,12 +505,18 @@ const addCanvasEvents = () => {
canvas.addEventListener(
"pointerup",
(e) => canvasClick(e, canvas),
false
false,
);
});
} else {
const canvas = document.querySelectorAll("canvas")[0] as HTMLCanvasElement;
canvas.addEventListener("pointerup", (e) => canvasClick(e, canvas), false);
const canvas = document.querySelectorAll(
"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 = (
zone: SignatureZone,
xy: number[],
canvas: HTMLCanvasElement
canvas: HTMLCanvasElement,
) =>
scaleXToCanvas(zone.x, canvas.width, zone.PDFPage.width) < xy[0] &&
xy[0] <
@ -520,7 +552,11 @@ const hitSignature = (
scaleYToCanvas(zone.y, canvas.height, zone.PDFPage.height) <
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;
const selectZone = async (z: SignatureZone, canvas: HTMLCanvasElement) => {
@ -536,8 +572,9 @@ const selectZoneEvent = (e: PointerEvent, canvas: HTMLCanvasElement) =>
signature.zones
.filter(
(z) =>
(z.PDFPage.index + 1 === getCanvasId(canvas) && multiPage.value) ||
(z.PDFPage.index + 1 === page.value && !multiPage.value)
(z.PDFPage.index + 1 === getCanvasId(canvas) &&
multiPage.value) ||
(z.PDFPage.index + 1 === page.value && !multiPage.value),
)
.map((z) => {
if (hitSignature(z, [e.offsetX, e.offsetY], canvas)) {
@ -590,7 +627,7 @@ const drawZone = (
zone: SignatureZone,
ctx: CanvasRenderingContext2D,
canvasWidth: number,
canvasHeight: number
canvasHeight: number,
) => {
const unselectedBlue = "#007bff";
const selectedBlue = "#034286";
@ -605,7 +642,7 @@ const drawZone = (
zone.PDFPage.height * zoom.value -
scaleYToCanvas(zone.y, canvasHeight, zone.PDFPage.height),
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.textAlign = "center";
@ -633,7 +670,7 @@ const drawAllZones = () => {
.filter(
(z) =>
multiPage.value ||
(z.PDFPage.index + 1 === page.value && !multiPage.value)
(z.PDFPage.index + 1 === page.value && !multiPage.value),
)
.map((z) => {
const canvas = getCanvas(z.PDFPage.index + 1);
@ -663,7 +700,7 @@ const checkSignature = () => {
signedState.value = "error";
console.log("Error while checking the signature", 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;
console.log("Reached the maximum number of tentative to try signing");
$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") {
@ -724,7 +761,7 @@ const confirmSign = () => {
console.log("Error while posting the signature", error);
stopTrySigning();
$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 = () =>
window.location.search
? window.location.search.split("?returnPath=")[1] ??
window.location.pathname
? (window.location.search.split("?returnPath=")[1] ??
window.location.pathname)
: window.location.pathname;
init();
@ -842,4 +879,3 @@ div.signature-modal-body {
height: 8rem;
}
</style>

View File

@ -1,19 +1,31 @@
<script setup lang="ts">
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 FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
interface DropFileConfig {
existingDoc?: StoredObject,
existingDoc?: StoredObject;
}
const props = withDefaults(defineProps<DropFileConfig>(), {existingDoc: null});
const props = withDefaults(defineProps<DropFileConfig>(), {
existingDoc: null,
});
const emit = defineEmits<{
(e: 'addDocument', {stored_object_version: StoredObjectVersionCreated, stored_object: StoredObject}): void,
}>();
const emit =
defineEmits<
(
e: "addDocument",
{
stored_object_version: StoredObjectVersionCreated,
stored_object: StoredObject,
},
) => void
>();
const is_dragging: Ref<boolean> = ref(false);
const uploading: Ref<boolean> = ref(false);
@ -27,13 +39,13 @@ const onDragOver = (e: Event) => {
e.preventDefault();
is_dragging.value = true;
}
};
const onDragLeave = (e: Event) => {
e.preventDefault();
is_dragging.value = false;
}
};
const onDrop = (e: DragEvent) => {
e.preventDefault();
@ -49,8 +61,8 @@ const onDrop = (e: DragEvent) => {
return;
}
handleFile(files[0])
}
handleFile(files[0]);
};
const onZoneClick = (e: Event) => {
e.stopPropagation();
@ -61,21 +73,21 @@ const onZoneClick = (e: Event) => {
input.addEventListener("change", onFileChange);
input.click();
}
};
const onFileChange = async (event: Event): Promise<void> => {
const input = event.target as HTMLInputElement;
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];
await handleFile(file);
return Promise.resolve();
}
throw 'No file given';
}
throw "No file given";
};
const handleFile = async (file: File): Promise<void> => {
uploading.value = true;
@ -100,25 +112,39 @@ const handleFile = async (file: File): Promise<void> => {
keyInfos: jsonWebKey,
type: type,
persisted: false,
}
};
emit('addDocument', {stored_object, stored_object_version});
emit("addDocument", { stored_object, stored_object_version });
uploading.value = false;
}
};
</script>
<template>
<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">
<file-icon :type="props.existingDoc?.type"></file-icon>
</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 -->
<p v-if="has_existing_doc">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>
<p v-if="has_existing_doc">
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 v-else class="waiting">
<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;
}
& > .area, & > .waiting {
& > .area,
& > .waiting {
width: 100%;
height: 10rem;
@ -163,5 +190,4 @@ const handleFile = async (file: File): Promise<void> => {
}
}
}
</style>

View File

@ -1,14 +1,13 @@
<script setup lang="ts">
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import { StoredObject, StoredObjectVersion } from "../../types";
import DropFileWidget from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileWidget.vue";
import { computed, reactive } from "vue";
import {useToast} from 'vue-toast-notification';
import { useToast } from "vue-toast-notification";
interface DropFileConfig {
allowRemove: boolean,
existingDoc?: StoredObject,
allowRemove: boolean;
existingDoc?: StoredObject;
}
const props = withDefaults(defineProps<DropFileConfig>(), {
@ -16,8 +15,14 @@ const props = withDefaults(defineProps<DropFileConfig>(), {
});
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();
@ -26,23 +31,30 @@ const state = reactive({showModal: false});
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) {
return 'add';
return "add";
}
return 'replace';
})
return "replace";
});
function onAddDocument({stored_object, stored_object_version}: {stored_object: StoredObject, stored_object_version: StoredObjectVersion}): void {
const message = buttonState.value === 'add' ? "Document ajouté" : "Document remplacé";
function onAddDocument({
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);
emit('addDocument', {stored_object_version, stored_object});
emit("addDocument", { stored_object_version, stored_object });
state.showModal = false;
}
function onRemoveDocument(): void {
emit('removeDocument');
emit("removeDocument");
}
function openModal(): void {
@ -55,15 +67,30 @@ function closeModal(): void {
</script>
<template>
<button v-if="buttonState === 'add'" @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">
<button
v-if="buttonState === 'add'"
@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>
<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>
</modal>
</template>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

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

View File

@ -4,22 +4,43 @@ interface FileIconConfig {
}
const props = defineProps<FileIconConfig>();
</script>
<template>
<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 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-word-o"
v-else-if="props.type === 'application/vnd.oasis.opendocument.text'"
></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/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>
</template>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

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

View File

@ -1,23 +1,24 @@
<script setup lang="ts">
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import { computed, reactive } from "vue";
export interface DesktopEditButtonConfig {
editLink: null,
classes: { [k: string]: boolean },
expirationLink: number|Date,
editLink: null;
classes: Record<string, boolean>;
expirationLink: number | Date;
}
interface DesktopEditButtonState {
modalOpened: boolean
};
modalOpened: boolean;
}
const state: DesktopEditButtonState = reactive({ modalOpened: false });
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>(() => {
let d;
@ -29,9 +30,11 @@ const editionUntilFormatted = computed<string>(() => {
}
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>
<template>
@ -39,16 +42,40 @@ const editionUntilFormatted = computed<string>(() => {
<modal v-if="state.modalOpened" @close="state.modalOpened = false">
<template v-slot:body>
<div class="desktop-edit">
<p class="center">Veuillez enregistrer vos modifications avant le</p>
<p><strong>{{ editionUntilFormatted }}</strong></p>
<p class="center">
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>
</template>
</modal>

View File

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

View File

@ -1,7 +1,9 @@
<script setup lang="ts">
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 { get_versions } from "./HistoryButton/api";
@ -17,7 +19,7 @@ interface HistoryButtonState {
const props = defineProps<HistoryButtonConfig>();
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> {
if (null !== modal.value) {
@ -34,17 +36,26 @@ const download_version_and_open_modal = async function (): Promise<void> {
}
state.loaded = true;
}
}
};
const onRestoreVersion = ({newVersion}: {newVersion: StoredObjectVersionWithPointInTime}) => {
const onRestoreVersion = ({
newVersion,
}: {
newVersion: StoredObjectVersionWithPointInTime;
}) => {
state.versions.unshift(newVersion);
}
};
</script>
<template>
<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>
Historique
</a>

View File

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

View File

@ -1,5 +1,9 @@
<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 { ISOToDatetime } from "./../../../../../../ChillMainBundle/Resources/public/chill/js/date";
import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
@ -15,47 +19,120 @@ interface HistoryButtonListItemConfig {
}
const emit = defineEmits<{
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime]
}>()
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
}>();
const props = defineProps<HistoryButtonListItemConfig>();
const onRestore = ({newVersion}: {newVersion: StoredObjectVersionWithPointInTime}) => {
emit('restoreVersion', {newVersion});
}
const onRestore = ({
newVersion,
}: {
newVersion: StoredObjectVersionWithPointInTime;
}) => {
emit("restoreVersion", { newVersion });
};
const isKeptBeforeConversion = computed<boolean>(() => props.version["point-in-times"].reduce(
(accumulator: boolean, pit: StoredObjectPointInTime) => accumulator || "keep-before-conversion" === pit.reason,
false
const isKeptBeforeConversion = computed<boolean>(() =>
props.version["point-in-times"].reduce(
(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 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}));
const isDuplicated = computed<boolean>(
() =>
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>
<template>
<div :class="classes">
<div class="col-12 tags" v-if="isCurrent || 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
class="col-12 tags"
v-if="
isCurrent ||
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 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 class="col-12">
<ul class="record_actions small slim on-version-actions">
<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>
<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>
</ul>
</div>

View File

@ -2,7 +2,10 @@
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import { reactive } from "vue";
import HistoryButtonList from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue";
import {StoredObject, StoredObjectVersionWithPointInTime} from "./../../../types";
import {
StoredObject,
StoredObjectVersionWithPointInTime,
} from "./../../../types";
interface HistoryButtonListConfig {
versions: StoredObjectVersionWithPointInTime[];
@ -11,8 +14,8 @@ interface HistoryButtonListConfig {
}
const emit = defineEmits<{
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime]
}>()
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
}>();
interface HistoryButtonModalState {
opened: boolean;
@ -23,10 +26,9 @@ const state = reactive<HistoryButtonModalState>({opened: false});
const open = () => {
state.opened = true;
}
};
defineExpose({ open });
</script>
<template>
<Teleport to="body">
@ -36,12 +38,17 @@ defineExpose({open});
</template>
<template v-slot:body>
<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>
</modal>
</Teleport>
</template>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@ -1,17 +1,20 @@
<script setup lang="ts">
import {StoredObjectVersionPersisted, StoredObjectVersionWithPointInTime} from "../../../types";
import {
StoredObjectVersionPersisted,
StoredObjectVersionWithPointInTime,
} from "../../../types";
import { useToast } from "vue-toast-notification";
import { restore_version } from "./api";
interface RestoreVersionButtonProps {
storedObjectVersion: StoredObjectVersionPersisted,
storedObjectVersion: StoredObjectVersionPersisted;
}
const emit = defineEmits<{
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime]
}>()
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
}>();
const props = defineProps<RestoreVersionButtonProps>()
const props = defineProps<RestoreVersionButtonProps>();
const $toast = useToast();
@ -19,14 +22,18 @@ const restore_version_fn = async () => {
const newVersion = await restore_version(props.storedObjectVersion);
$toast.success("Version restaurée");
emit('restoreVersion', {newVersion});
}
emit("restoreVersion", { newVersion });
};
</script>
<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>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@ -1,5 +1,11 @@
<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>
Editer en ligne
</a>
@ -8,13 +14,16 @@
<script lang="ts" setup>
import WopiEditButton from "./WopiEditButton.vue";
import { build_wopi_editor_link } from "./helpers";
import {StoredObject, WopiEditButtonExecutableBeforeLeaveFunction} from "../../types";
import {
StoredObject,
WopiEditButtonExecutableBeforeLeaveFunction,
} from "../../types";
interface WopiEditButtonConfig {
storedObject: StoredObject,
returnPath?: string,
classes: {[k: string] : boolean},
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction,
storedObject: StoredObject;
returnPath?: string;
classes: Record<string, boolean>;
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
}
const props = defineProps<WopiEditButtonConfig>();
@ -43,4 +52,3 @@ i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}
</style>

View File

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

View File

@ -1,4 +1,3 @@
module.exports = function(encore, entries)
{
entries.push(__dirname + '/Resources/public/chill/index.js');
module.exports = function (encore, entries) {
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
var make_show_hide = function (entry) {
let
obtained = entry.querySelector('[data-diploma-obtained]'),
reconnue = entry.querySelector('[data-diploma-reconnue]')
;
let obtained = entry.querySelector("[data-diploma-obtained]"),
reconnue = entry.querySelector("[data-diploma-reconnue]");
var a = new ShowHide({
load_event: null,
froms: [obtained],
container: [reconnue],
test: function (froms, event) {
for (let f of froms.values()) {
for (let input of f.querySelectorAll('input').values()) {
if (input.value === 'non-fr') {
for (let input of f.querySelectorAll("input").values()) {
if (input.value === "non-fr") {
return input.checked;
}
}
}
return false;
}
},
});
};
window.addEventListener('collection-add-entry', function(e) {
if (e.detail.collection.dataset.collectionName === 'formations') {
window.addEventListener("collection-add-entry", function (e) {
if (e.detail.collection.dataset.collectionName === "formations") {
make_show_hide(e.detail.entry);
}
});
// starting the formation on load
window.addEventListener('load', function(_e) {
let
formations = document.querySelectorAll('[data-formation-entry]')
;
window.addEventListener("load", function (_e) {
let formations = document.querySelectorAll("[data-formation-entry]");
for (let f of formations.values()) {
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
div_accompagnement = document.getElementById("form_accompagnement"),
var div_accompagnement = document.getElementById("form_accompagnement"),
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_inscription_date = document.getElementById("cafInscriptionDate"),
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é
new ShowHide({
"froms": [div_accompagnement],
"test": function(froms, event) {
froms: [div_accompagnement],
test: function (froms, event) {
for (let el of froms.values()) {
for (let input of el.querySelectorAll('input').values()) {
if (input.value === 'rqth') {
for (let input of el.querySelectorAll("input").values()) {
if (input.value === "rqth") {
return input.checked;
}
}
}
},
"container": [div_accompagnement_rqth]
container: [div_accompagnement_rqth],
});
// faire apparaitre / disparaitre commetnaire si coché
new ShowHide({
"froms": [div_accompagnement],
"test": function(froms, event) {
froms: [div_accompagnement],
test: function (froms, event) {
for (let el of froms.values()) {
for (let input of el.querySelectorAll('input').values()) {
if (input.value === 'autre') {
for (let input of el.querySelectorAll("input").values()) {
if (input.value === "autre") {
return input.checked;
}
}
@ -39,7 +38,7 @@ new ShowHide({
return false;
},
"container": [div_accompagnement_comment]
container: [div_accompagnement_comment],
});
// faire apparaitre cafInscriptionDate seulement si cafID est rempli
@ -51,7 +50,7 @@ new ShowHide({
}
},
container: [div_caf_inscription_date],
event_name: 'input'
event_name: "input",
});
// faire apparaitre date de commission neet seulement si eligible
@ -59,7 +58,7 @@ new ShowHide({
froms: [div_neet_eligibilite],
test: function (froms, event) {
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") {
return input.checked;
}
@ -68,5 +67,5 @@ new ShowHide({
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';
var
div_objectifs = document.getElementById("objectifs"),
div_objectifs_autre = document.getElementById("objectifsAutre")
;
import { ShowHide } from "ShowHide/show_hide.js";
var div_objectifs = document.getElementById("objectifs"),
div_objectifs_autre = document.getElementById("objectifsAutre");
new ShowHide({
froms: [div_objectifs],
container: [div_objectifs_autre],
test: function (froms, event) {
for (let el of froms.values()) {
for (let input of el.querySelectorAll('input').values()) {
if (input.value === 'autre') {
for (let input of el.querySelectorAll("input").values()) {
if (input.value === "autre") {
return input.checked;
}
}
}
}
},
});

View File

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

View File

@ -4,47 +4,44 @@
*/
/// import jQuery
const $ = require('jquery');
const $ = require("jquery");
global.$ = global.jQuery = $;
/// import select2
const select2 = require('select2');
const select2 = require("select2");
global.select2 = select2;
require('select2/dist/css/select2.css');
require('select2-bootstrap-theme/dist/select2-bootstrap.css');
require("select2/dist/css/select2.css");
require("select2-bootstrap-theme/dist/select2-bootstrap.css");
/*
* 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;
require('./js/date');
require('./js/counter.js');
require("./js/date");
require("./js/counter.js");
/// Load fonts
require('./fonts/OpenSans/OpenSans.scss')
require("./fonts/OpenSans/OpenSans.scss");
/// Load images
require('./img/favicon.ico');
require('./img/logo-chill-sans-slogan_white.png');
require('./img/logo-chill-outil-accompagnement_white.png');
require("./img/favicon.ico");
require("./img/logo-chill-sans-slogan_white.png");
require("./img/logo-chill-outil-accompagnement_white.png");
/*
* Load local libs
* 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/download-report/index.js');
require('../lib/select_interactive_loading/index.js');
require("../lib/breadcrumb/index.js");
require("../lib/download-report/index.js");
require("../lib/select_interactive_loading/index.js");
//require('../lib/show_hide/index.js');
//require('../lib/tabs/index.js');

View File

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

View File

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

View File

@ -19,9 +19,9 @@ export const dateToISO = (date: Date|null): string|null => {
return [
date.getFullYear(),
(date.getMonth() + 1).toString().padStart(2, '0'),
date.getDate().toString().padStart(2, '0')
].join('-');
(date.getMonth() + 1).toString().padStart(2, "0"),
date.getDate().toString().padStart(2, "0"),
].join("-");
};
/**
@ -37,11 +37,10 @@ export const ISOToDate = (str: string|null): Date|null => {
return null;
}
let
[year, month, day] = str.split('-').map(p => parseInt(p));
const [year, month, day] = str.split("-").map((p) => parseInt(p));
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
@ -52,19 +51,18 @@ export const ISOToDatetime = (str: string|null): Date|null => {
return null;
}
let
[cal, times] = str.split('T'),
[year, month, date] = cal.split('-').map(s => parseInt(s)),
const [cal, times] = str.split("T"),
[year, month, date] = cal.split("-").map((s) => parseInt(s)),
[time, timezone] = times.split(times.charAt(8)),
[hours, minutes, seconds] = time.split(':').map(s => parseInt(s));
;
if ('0000' === timezone) {
return new Date(Date.UTC(year, month-1, date, hours, minutes, seconds));
[hours, minutes, seconds] = time.split(":").map((s) => parseInt(s));
if ("0000" === timezone) {
return new Date(
Date.UTC(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
@ -74,39 +72,43 @@ export const datetimeToISO = (date: Date): string => {
let cal, time, offset;
cal = [
date.getFullYear(),
(date.getMonth() + 1).toString().padStart(2, '0'),
date.getDate().toString().padStart(2, '0')
].join('-');
(date.getMonth() + 1).toString().padStart(2, "0"),
date.getDate().toString().padStart(2, "0"),
].join("-");
time = [
date.getHours().toString().padStart(2, '0'),
date.getMinutes().toString().padStart(2, '0'),
date.getSeconds().toString().padStart(2, '0')
].join(':');
date.getHours().toString().padStart(2, "0"),
date.getMinutes().toString().padStart(2, "0"),
date.getSeconds().toString().padStart(2, "0"),
].join(":");
offset = [
date.getTimezoneOffset() <= 0 ? '+' : '-',
Math.abs(Math.floor(date.getTimezoneOffset() / 60)).toString().padStart(2, '0'),
':',
Math.abs(date.getTimezoneOffset() % 60).toString().padStart(2, '0'),
].join('');
date.getTimezoneOffset() <= 0 ? "+" : "-",
Math.abs(Math.floor(date.getTimezoneOffset() / 60))
.toString()
.padStart(2, "0"),
":",
Math.abs(date.getTimezoneOffset() % 60)
.toString()
.padStart(2, "0"),
].join("");
let x = cal + 'T' + time + offset;
const x = cal + "T" + time + offset;
return x;
};
export const intervalDaysToISO = (days: number | string | null): string => {
if (null === days) {
return 'P0D';
return "P0D";
}
return `P${days}D`;
}
};
export const intervalISOToDays = (str: string | null): number | null => {
if (null === str) {
return null
return null;
}
if ("" === str.trim()) {
@ -121,40 +123,42 @@ export const intervalISOToDays = (str: string|null): number|null => {
continue;
}
switch (str.charAt(i)) {
case 'P':
case "P":
isDate = true;
break;
case 'T':
case "T":
isDate = false;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
vstring = vstring + str.charAt(i);
break;
case 'Y':
case "Y":
days = days + Number.parseInt(vstring) * 365;
vstring = "";
break;
case 'M':
case "M":
days = days + Number.parseInt(vstring) * 30;
vstring = "";
break;
case 'D':
case "D":
days = days + Number.parseInt(vstring);
vstring = "";
break;
default:
throw Error("this character should not appears: " + str.charAt(i));
throw Error(
"this character should not appears: " + str.charAt(i),
);
}
}
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";
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 response = await fetch(url);
@ -11,25 +14,48 @@ export const getAddressById = async (address_id: number): Promise<Address> =>
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[]> => {
return fetchResults<SimpleGeographicalUnit>(`/api/1.0/main/geographical-unit/by-address/${address.address_id}.json`);
}
export const getGeographicalUnitsByAddress = async (
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[]> => {
return fetchResults<GeographicalUnitLayer>(`/api/1.0/main/geographical-unit-layer.json`);
}
export const getAllGeographicalUnitLayers = async (): Promise<
GeographicalUnitLayer[]
> => {
return fetchResults<GeographicalUnitLayer>(
`/api/1.0/main/geographical-unit-layer.json`,
);
};
export const syncAddressWithReference = async (address: Address): Promise<Address> => {
return makeFetch<null, Address>("POST", `/api/1.0/main/address/reference-match/${address.address_id}/sync-with-reference`);
}
export const syncAddressWithReference = async (
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> => {
return makeFetch<null, Address>("POST", `/api/1.0/main/address/reference-match/${address.address_id}/set/reviewed`);
}
export const markAddressReviewed = async (
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> => {
return makeFetch<null, Address>("POST", `/api/1.0/main/address/reference-match/${address.address_id}/set/to_review`);
}
export const markAddressToReview = async (
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 fetchOption = {[key: string]: boolean|string|number|null};
export type body = Record<string, boolean | string | number | null>;
export type fetchOption = Record<string, boolean | string | number | null>;
export interface Params {
[key: string]: number|string
}
export type Params = Record<string, number | string>;
export interface PaginationResponse<T> {
pagination: {
@ -16,16 +14,15 @@ export interface PaginationResponse<T> {
count: number;
}
export interface FetchParams {
[K: string]: string|number|null;
};
export type FetchParams = Record<string, string | number | null>;
export interface TransportExceptionInterface {
name: string;
}
export interface ValidationExceptionInterface extends TransportExceptionInterface {
name: 'ValidationException';
export interface ValidationExceptionInterface
extends TransportExceptionInterface {
name: "ValidationException";
error: object;
violations: string[];
titles: string[];
@ -40,23 +37,25 @@ export interface ValidationErrorResponse extends TransportExceptionInterface {
}
export interface AccessExceptionInterface extends TransportExceptionInterface {
name: 'AccessException';
name: "AccessException";
violations: string[];
}
export interface NotFoundExceptionInterface extends TransportExceptionInterface {
name: 'NotFoundException';
export interface NotFoundExceptionInterface
extends TransportExceptionInterface {
name: "NotFoundException";
}
export interface ServerExceptionInterface extends TransportExceptionInterface {
name: 'ServerException';
name: "ServerException";
message: string;
code: number;
body: string;
}
export interface ConflictHttpExceptionInterface extends TransportExceptionInterface {
name: 'ConflictHttpException';
export interface ConflictHttpExceptionInterface
extends TransportExceptionInterface {
name: "ConflictHttpException";
violations: string[];
}
@ -64,35 +63,33 @@ export interface ConflictHttpExceptionInterface extends TransportExceptionInterf
* Generic api method that can be adapted to any fetch request
*/
export const makeFetch = <Input, Output>(
method: 'POST'|'GET'|'PUT'|'PATCH'|'DELETE',
url: string, body?: body | Input | null,
options?: FetchParams
method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE",
url: string,
body?: body | Input | null,
options?: FetchParams,
): Promise<Output> => {
let opts = {
method: method,
headers: {
'Content-Type': 'application/json;charset=utf-8'
"Content-Type": "application/json;charset=utf-8",
},
};
if (body !== null && typeof body !== 'undefined') {
Object.assign(opts, {body: JSON.stringify(body)})
if (body !== null && typeof body !== "undefined") {
Object.assign(opts, { body: JSON.stringify(body) });
}
if (typeof options !== 'undefined') {
if (typeof options !== "undefined") {
opts = Object.assign(opts, options);
}
return fetch(url, opts)
.then(response => {
return fetch(url, opts).then((response) => {
if (response.ok) {
return response.json();
}
if (response.status === 422) {
return response.json().then(response => {
throw ValidationException(response)
return response.json().then((response) => {
throw ValidationException(response);
});
}
@ -105,55 +102,62 @@ export const makeFetch = <Input, Output>(
}
throw {
name: 'Exception',
name: "Exception",
sta: response.status,
txt: response.statusText,
err: new Error(),
violations: response.body
violations: response.body,
};
});
}
};
/**
* Fetch results with certain parameters
*/
function _fetchAction<T>(page: number, uri: string, params?: FetchParams): Promise<PaginationResponse<T>> {
const item_per_page: number = 50;
function _fetchAction<T>(
page: number,
uri: string,
params?: FetchParams,
): Promise<PaginationResponse<T>> {
const item_per_page = 50;
let searchParams = new URLSearchParams();
searchParams.append('item_per_page', item_per_page.toString());
searchParams.append('page', page.toString());
const searchParams = new URLSearchParams();
searchParams.append("item_per_page", item_per_page.toString());
searchParams.append("page", page.toString());
if (params !== undefined) {
Object.keys(params).forEach(key => {
let v = params[key];
if (typeof v === 'string') {
Object.keys(params).forEach((key) => {
const v = params[key];
if (typeof v === "string") {
searchParams.append(key, v);
} else if (typeof v === 'number') {
} else if (typeof v === "number") {
searchParams.append(key, v.toString());
} else if (v === null) {
searchParams.append(key, '');
searchParams.append(key, "");
}
});
}
let url = uri + '?' + searchParams.toString();
const url = uri + "?" + searchParams.toString();
return fetch(url, {
method: 'GET',
method: "GET",
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) {
throw NotFoundException(response);
}
if (response.status === 422) {
return response.json().then(response => {
throw ValidationException(response)
return response.json().then((response) => {
throw ValidationException(response);
});
}
@ -162,22 +166,30 @@ function _fetchAction<T>(page: number, uri: string, params?: FetchParams): Promi
}
if (response.status >= 500) {
return response.text().then(body => {
return response.text().then((body) => {
throw ServerException(response.status, body);
});
}
throw new Error("other network error");
}).catch((reason: any) => {
})
.catch((reason: any) => {
console.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[]>[] = [],
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));
@ -185,61 +197,74 @@ export const fetchResults = async<T> (uri: string, params?: FetchParams): Promis
do {
page = ++page;
promises.push(
_fetchAction<T>(page, uri, params)
.then(r => Promise.resolve(r.results))
_fetchAction<T>(page, uri, params).then((r) =>
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());
};
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
*/
const ValidationException = (response: ValidationErrorResponse): ValidationExceptionInterface => {
const ValidationException = (
response: ValidationErrorResponse,
): ValidationExceptionInterface => {
const error = {} as ValidationExceptionInterface;
error.name = 'ValidationException';
error.violations = response.violations.map((violation) => `${violation.title}: ${violation.propertyPath}`);
error.name = "ValidationException";
error.violations = response.violations.map(
(violation) => `${violation.title}: ${violation.propertyPath}`,
);
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;
}
};
const AccessException = (response: Response): AccessExceptionInterface => {
const error = {} as AccessExceptionInterface;
error.name = 'AccessException';
error.violations = ['You are not allowed to perform this action'];
error.name = "AccessException";
error.violations = ["You are not allowed to perform this action"];
return error;
}
};
const NotFoundException = (response: Response): NotFoundExceptionInterface => {
const error = {} as NotFoundExceptionInterface;
error.name = 'NotFoundException';
error.name = "NotFoundException";
return error;
}
};
const ServerException = (code: number, body: string): ServerExceptionInterface => {
const ServerException = (
code: number,
body: string,
): ServerExceptionInterface => {
const error = {} as ServerExceptionInterface;
error.name = 'ServerException';
error.name = "ServerException";
error.code = code;
error.body = body;
return error;
}
};
const ConflictHttpException = (response: Response): ConflictHttpExceptionInterface => {
const ConflictHttpException = (
response: Response,
): ConflictHttpExceptionInterface => {
const error = {} as ConflictHttpExceptionInterface;
error.name = 'ConflictHttpException';
error.violations = ['Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again']
error.name = "ConflictHttpException";
error.violations = [
"Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again",
];
return error;
}
};

View File

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

View File

@ -1,6 +1,8 @@
import { fetchResults } from "./apiMethods";
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> => {
const url = `/api/1.0/main/whoami.json`;
return fetch(url)
.then(response => {
return fetch(url).then((response) => {
if (response.ok) {
return response.json();
}
throw {
msg: 'Error while getting whoami.',
msg: "Error while getting whoami.",
sta: response.status,
txt: response.statusText,
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`;
return makeFetch<null, Location | null>("GET", url);
}
};

View File

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

View File

@ -15,40 +15,44 @@
* 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) => {
var download_text = container.dataset.downloadText,
alias = container.dataset.alias;
window.fetch(url, { credentials: 'same-origin' })
.then(response => {
window
.fetch(url, { credentials: "same-origin" })
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response.blob();
}).then(blob => {
})
.then((blob) => {
var content = URL.createObjectURL(blob),
link = document.createElement("a"),
type = blob.type,
hasForcedType = 'mimeType' in container.dataset,
hasForcedType = "mimeType" in container.dataset,
extension;
if (hasForcedType) {
// force a type
type = container.dataset.mimeType;
blob = new Blob([ blob ], { 'type': type });
blob = new Blob([blob], { type: type });
content = URL.createObjectURL(blob);
}
const extensions = new Map();
extensions.set('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlsx');
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');
extensions.set(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"xlsx",
);
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);
@ -57,16 +61,15 @@ export const download_report = (url, container) => {
link.href = content;
link.download = alias;
if (extension !== false) {
link.download = link.download + '.' + extension;
link.download = link.download + "." + extension;
}
container.innerHTML = "";
container.appendChild(link);
}).catch(function(error) {
})
.catch(function (error) {
console.error(error);
var problem_text =
document.createTextNode("Problem during download");
var problem_text = document.createTextNode("Problem during download");
container
.replaceChild(problem_text, container.firstChild);
container.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();
params.append('entityClass', relatedEntityClass);
params.append('entityId', relatedEntityId.toString());
params.append("entityClass", relatedEntityClass);
params.append("entityId", relatedEntityId.toString());
if (null !== to) {
params.append('tos[0]', to.toString());
params.append("tos[0]", to.toString());
}
if (null !== returnPath) {
params.append('returnPath', returnPath);
params.append("returnPath", returnPath);
}
return `/fr/notification/create?${params.toString()}`;
}
};
export {
buildLinkCreate,
}
export { buildLinkCreate };

View File

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

View File

@ -1,5 +1,5 @@
//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