Revert "Merge branch 'ticket/supplementary-comments-on-motive' into 'master'"

This reverts merge request !855
This commit is contained in:
2025-07-20 18:50:33 +00:00
parent 5f01673404
commit e3a6b60fa2
392 changed files with 24023 additions and 35435 deletions

View File

@@ -1,130 +1,130 @@
<template>
<add-address
:key="key"
:context="context"
:options="options"
:address-changed-callback="submitAddress"
ref="addAddress"
/>
<add-address
:key="key"
:context="context"
:options="options"
:address-changed-callback="submitAddress"
ref="addAddress"
/>
</template>
<script>
import AddAddress from "./components/AddAddress.vue";
import {
postAddressToHousehold,
postAddressToPerson,
postAddressToHousehold,
postAddressToPerson,
} from "ChillPersonAssets/vuejs/_api/AddAddress";
export default {
name: "App",
components: {
AddAddress,
},
props: ["addAddress", "callback"],
emits: ["addressEdited", "addressCreated"],
computed: {
context() {
return this.addAddress.context;
name: "App",
components: {
AddAddress,
},
options() {
return this.addAddress.options;
props: ["addAddress", "callback"],
emits: ["addressEdited", "addressCreated"],
computed: {
context() {
return this.addAddress.context;
},
options() {
return this.addAddress.options;
},
key() {
return this.context.edit
? "address_" + this.context.addressId
: this.context.target.name + "_" + this.context.target.id;
},
},
key() {
return this.context.edit
? "address_" + this.context.addressId
: this.context.target.name + "_" + this.context.target.id;
mounted() {
//console.log('AddAddress: data context', this.context);
//console.log('AddAddress: data options', this.options);
},
},
mounted() {
//console.log('AddAddress: data context', this.context);
//console.log('AddAddress: data options', this.options);
},
methods: {
displayErrors() {
return this.$refs.addAddress.errorMsg;
methods: {
displayErrors() {
return this.$refs.addAddress.errorMsg;
},
submitAddress(payload) {
console.log("@@@ click on Submit Address Button", payload);
// Existing address
if (this.context.edit) {
// address is already linked, just finish !
this.$refs.addAddress.afterLastPaneAction({});
this.$emit("addressEdited", payload);
// New created address
} else {
this.postAddressTo(payload);
}
},
/*
* Post new created address to targetEntity
*/
postAddressTo(payload) {
this.$emit("addressCreated", payload);
console.log(
"postAddress",
payload.addressId,
"To",
payload.target,
payload.targetId,
);
switch (payload.target) {
case "household":
postAddressToHousehold(payload.targetId, payload.addressId)
.then(
(address) =>
new Promise((resolve, reject) => {
console.log("..household address", address);
this.$refs.addAddress.flag.loading = false;
this.$refs.addAddress.flag.success = true;
// finish
this.$refs.addAddress.afterLastPaneAction({
addressId: address.address_id,
});
resolve();
}),
)
.catch((error) => {
this.$refs.addAddress.errorMsg.push(error);
this.$refs.addAddress.flag.loading = false;
});
break;
case "person":
postAddressToPerson(payload.targetId, payload.addressId)
.then(
(address) =>
new Promise((resolve, reject) => {
console.log("..person address", address);
this.$refs.addAddress.flag.loading = false;
this.$refs.addAddress.flag.success = true;
// finish
this.$refs.addAddress.afterLastPaneAction({
addressId: address.address_id,
});
resolve();
}),
)
.catch((error) => {
this.$refs.addAddress.errorMsg.push(error);
this.$refs.addAddress.flag.loading = false;
});
break;
case "thirdparty":
console.log("TODO write postAddressToThirdparty");
break;
default:
this.$refs.addAddress.errorMsg.push(
"That entity is not managed by address !",
);
}
},
},
submitAddress(payload) {
console.log("@@@ click on Submit Address Button", payload);
// Existing address
if (this.context.edit) {
// address is already linked, just finish !
this.$refs.addAddress.afterLastPaneAction({});
this.$emit("addressEdited", payload);
// New created address
} else {
this.postAddressTo(payload);
}
},
/*
* Post new created address to targetEntity
*/
postAddressTo(payload) {
this.$emit("addressCreated", payload);
console.log(
"postAddress",
payload.addressId,
"To",
payload.target,
payload.targetId,
);
switch (payload.target) {
case "household":
postAddressToHousehold(payload.targetId, payload.addressId)
.then(
(address) =>
new Promise((resolve, reject) => {
console.log("..household address", address);
this.$refs.addAddress.flag.loading = false;
this.$refs.addAddress.flag.success = true;
// finish
this.$refs.addAddress.afterLastPaneAction({
addressId: address.address_id,
});
resolve();
}),
)
.catch((error) => {
this.$refs.addAddress.errorMsg.push(error);
this.$refs.addAddress.flag.loading = false;
});
break;
case "person":
postAddressToPerson(payload.targetId, payload.addressId)
.then(
(address) =>
new Promise((resolve, reject) => {
console.log("..person address", address);
this.$refs.addAddress.flag.loading = false;
this.$refs.addAddress.flag.success = true;
// finish
this.$refs.addAddress.afterLastPaneAction({
addressId: address.address_id,
});
resolve();
}),
)
.catch((error) => {
this.$refs.addAddress.errorMsg.push(error);
this.$refs.addAddress.flag.loading = false;
});
break;
case "thirdparty":
console.log("TODO write postAddressToThirdparty");
break;
default:
this.$refs.addAddress.errorMsg.push(
"That entity is not managed by address !",
);
}
},
},
};
</script>

View File

@@ -1,32 +1,32 @@
<template>
<ul
class="record_actions"
v-if="!options.onlyButton"
:class="{ 'sticky-form-buttons': isStickyForm }"
>
<li v-if="isStickyForm" class="cancel">
<slot name="before" />
</li>
<li>
<slot name="action" />
</li>
<li v-if="isStickyForm">
<slot name="after" />
</li>
</ul>
<slot v-else name="action" />
<ul
class="record_actions"
v-if="!options.onlyButton"
:class="{ 'sticky-form-buttons': isStickyForm }"
>
<li v-if="isStickyForm" class="cancel">
<slot name="before" />
</li>
<li>
<slot name="action" />
</li>
<li v-if="isStickyForm">
<slot name="after" />
</li>
</ul>
<slot v-else name="action" />
</template>
<script>
export default {
name: "ActionButtons",
props: ["options", "defaultz"],
computed: {
isStickyForm() {
return typeof this.options.stickyActions !== "undefined"
? this.options.stickyActions
: this.defaultz.stickyActions;
name: "ActionButtons",
props: ["options", "defaultz"],
computed: {
isStickyForm() {
return typeof this.options.stickyActions !== "undefined"
? this.options.stickyActions
: this.defaultz.stickyActions;
},
},
},
};
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div id="address_map" />
<div id="address_map" />
</template>
<script>
@@ -8,107 +8,112 @@ import markerIconPng from "leaflet/dist/images/marker-icon.png";
import "leaflet/dist/leaflet.css";
const lonLatForLeaflet = (coordinates) => {
return [coordinates[1], coordinates[0]];
return [coordinates[1], coordinates[0]];
};
export default {
name: "AddressMap",
props: ["entity"],
data() {
return {
map: null,
marker: null,
};
},
computed: {
center() {
return this.entity.addressMap.center;
name: "AddressMap",
props: ["entity"],
data() {
return {
map: null,
marker: null,
};
},
hasAddressPoint() {
if (Object.keys(this.entity.address).length === 0) {
return false;
}
if (null !== this.entity.address.addressReference) {
return true;
}
if (
null !== this.entity.address.postcode &&
null !== this.entity.address.postcode.center
) {
return true;
}
return false;
computed: {
center() {
return this.entity.addressMap.center;
},
hasAddressPoint() {
if (Object.keys(this.entity.address).length === 0) {
return false;
}
if (null !== this.entity.address.addressReference) {
return true;
}
if (
null !== this.entity.address.postcode &&
null !== this.entity.address.postcode.center
) {
return true;
}
return false;
},
/**
*
* @returns {coordinates: [float, float], type: "Point"}
*/
addressPoint() {
if (Object.keys(this.entity.address).length === 0) {
return null;
}
if (null !== this.entity.address.addressReference) {
return this.entity.address.addressReference.point;
}
if (
null !== this.entity.address.postcode &&
null !== this.entity.address.postcode.center
) {
return this.entity.address.postcode.center;
}
return null;
},
},
/**
*
* @returns {coordinates: [float, float], type: "Point"}
*/
addressPoint() {
if (Object.keys(this.entity.address).length === 0) {
return null;
}
methods: {
init() {
this.map = L.map("address_map");
if (null !== this.entity.address.addressReference) {
return this.entity.address.addressReference.point;
}
if (!this.hasAddressPoint) {
this.map.setView(
lonLatForLeaflet(this.entity.addressMap.center),
this.entity.addressMap.zoom,
);
} else {
this.map.setView(
lonLatForLeaflet(this.addressPoint.coordinates),
15,
);
}
if (
null !== this.entity.address.postcode &&
null !== this.entity.address.postcode.center
) {
return this.entity.address.postcode.center;
}
this.map.scrollWheelZoom.disable();
return null;
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(this.map);
const markerIcon = L.icon({
iconUrl: markerIconPng,
iconAnchor: [12, 41],
});
if (!this.hasAddressPoint) {
this.marker = L.marker(
lonLatForLeaflet(this.entity.addressMap.center),
{ icon: markerIcon },
);
} else {
this.marker = L.marker(
lonLatForLeaflet(this.addressPoint.coordinates),
{ icon: markerIcon },
);
}
this.marker.addTo(this.map);
},
update() {
if (this.marker && this.entity.addressMap.center) {
this.marker.setLatLng(
lonLatForLeaflet(this.entity.addressMap.center),
);
this.map.panTo(lonLatForLeaflet(this.entity.addressMap.center));
}
},
},
},
methods: {
init() {
this.map = L.map("address_map");
if (!this.hasAddressPoint) {
this.map.setView(
lonLatForLeaflet(this.entity.addressMap.center),
this.entity.addressMap.zoom,
);
} else {
this.map.setView(lonLatForLeaflet(this.addressPoint.coordinates), 15);
}
this.map.scrollWheelZoom.disable();
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(this.map);
const markerIcon = L.icon({
iconUrl: markerIconPng,
iconAnchor: [12, 41],
});
if (!this.hasAddressPoint) {
this.marker = L.marker(
lonLatForLeaflet(this.entity.addressMap.center),
{ icon: markerIcon },
);
} else {
this.marker = L.marker(
lonLatForLeaflet(this.addressPoint.coordinates),
{ icon: markerIcon },
);
}
this.marker.addTo(this.map);
mounted() {
this.init();
},
update() {
if (this.marker && this.entity.addressMap.center) {
this.marker.setLatLng(lonLatForLeaflet(this.entity.addressMap.center));
this.map.panTo(lonLatForLeaflet(this.entity.addressMap.center));
}
},
},
mounted() {
this.init();
},
};
</script>

View File

@@ -1,149 +1,149 @@
<template>
<h4 class="h3">
{{ $t("fill_an_address") }}
</h4>
<div class="row my-3">
<div class="col-lg-6" v-if="!isNoAddress">
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="floor"
:placeholder="$t('floor')"
v-model="floor"
/>
<label for="floor">{{ $t("floor") }}</label>
</div>
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="corridor"
:placeholder="$t('corridor')"
v-model="corridor"
/>
<label for="corridor">{{ $t("corridor") }}</label>
</div>
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="steps"
:placeholder="$t('steps')"
v-model="steps"
/>
<label for="steps">{{ $t("steps") }}</label>
</div>
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="flat"
:placeholder="$t('flat')"
v-model="flat"
/>
<label for="flat">{{ $t("flat") }}</label>
</div>
<h4 class="h3">
{{ $t("fill_an_address") }}
</h4>
<div class="row my-3">
<div class="col-lg-6" v-if="!isNoAddress">
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="floor"
:placeholder="$t('floor')"
v-model="floor"
/>
<label for="floor">{{ $t("floor") }}</label>
</div>
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="corridor"
:placeholder="$t('corridor')"
v-model="corridor"
/>
<label for="corridor">{{ $t("corridor") }}</label>
</div>
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="steps"
:placeholder="$t('steps')"
v-model="steps"
/>
<label for="steps">{{ $t("steps") }}</label>
</div>
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="flat"
:placeholder="$t('flat')"
v-model="flat"
/>
<label for="flat">{{ $t("flat") }}</label>
</div>
</div>
<div :class="isNoAddress ? 'col-lg-12' : 'col-lg-6'">
<div class="form-floating my-1" v-if="!isNoAddress">
<input
class="form-control"
type="text"
name="buildingName"
maxlength="255"
:placeholder="$t('buildingName')"
v-model="buildingName"
/>
<label for="buildingName">{{ $t("buildingName") }}</label>
</div>
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="extra"
maxlength="255"
:placeholder="$t('extra')"
v-model="extra"
/>
<label for="extra">{{ $t("extra") }}</label>
</div>
<div class="form-floating my-1" v-if="!isNoAddress">
<input
class="form-control"
type="text"
name="distribution"
maxlength="255"
:placeholder="$t('distribution')"
v-model="distribution"
/>
<label for="distribution">{{ $t("distribution") }}</label>
</div>
</div>
</div>
<div :class="isNoAddress ? 'col-lg-12' : 'col-lg-6'">
<div class="form-floating my-1" v-if="!isNoAddress">
<input
class="form-control"
type="text"
name="buildingName"
maxlength="255"
:placeholder="$t('buildingName')"
v-model="buildingName"
/>
<label for="buildingName">{{ $t("buildingName") }}</label>
</div>
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="extra"
maxlength="255"
:placeholder="$t('extra')"
v-model="extra"
/>
<label for="extra">{{ $t("extra") }}</label>
</div>
<div class="form-floating my-1" v-if="!isNoAddress">
<input
class="form-control"
type="text"
name="distribution"
maxlength="255"
:placeholder="$t('distribution')"
v-model="distribution"
/>
<label for="distribution">{{ $t("distribution") }}</label>
</div>
</div>
</div>
</template>
<script>
export default {
name: "AddressMore",
props: ["entity", "isNoAddress"],
computed: {
floor: {
set(value) {
this.entity.selected.address.floor = value;
},
get() {
return this.entity.selected.address.floor;
},
name: "AddressMore",
props: ["entity", "isNoAddress"],
computed: {
floor: {
set(value) {
this.entity.selected.address.floor = value;
},
get() {
return this.entity.selected.address.floor;
},
},
corridor: {
set(value) {
this.entity.selected.address.corridor = value;
},
get() {
return this.entity.selected.address.corridor;
},
},
steps: {
set(value) {
this.entity.selected.address.steps = value;
},
get() {
return this.entity.selected.address.steps;
},
},
flat: {
set(value) {
this.entity.selected.address.flat = value;
},
get() {
return this.entity.selected.address.flat;
},
},
buildingName: {
set(value) {
this.entity.selected.address.buildingName = value;
},
get() {
return this.entity.selected.address.buildingName;
},
},
extra: {
set(value) {
this.entity.selected.address.extra = value;
},
get() {
return this.entity.selected.address.extra;
},
},
distribution: {
set(value) {
this.entity.selected.address.distribution = value;
},
get() {
return this.entity.selected.address.distribution;
},
},
},
corridor: {
set(value) {
this.entity.selected.address.corridor = value;
},
get() {
return this.entity.selected.address.corridor;
},
},
steps: {
set(value) {
this.entity.selected.address.steps = value;
},
get() {
return this.entity.selected.address.steps;
},
},
flat: {
set(value) {
this.entity.selected.address.flat = value;
},
get() {
return this.entity.selected.address.flat;
},
},
buildingName: {
set(value) {
this.entity.selected.address.buildingName = value;
},
get() {
return this.entity.selected.address.buildingName;
},
},
extra: {
set(value) {
this.entity.selected.address.extra = value;
},
get() {
return this.entity.selected.address.extra;
},
},
distribution: {
set(value) {
this.entity.selected.address.distribution = value;
},
get() {
return this.entity.selected.address.distribution;
},
},
},
};
</script>

View File

@@ -1,223 +1,227 @@
<template>
<div class="my-1">
<label class="col-form-label" for="addressSelector">{{
$t("address")
}}</label>
<VueMultiselect
id="addressSelector"
v-model="value"
:placeholder="$t('select_address')"
:tag-placeholder="$t('create_address')"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('create_address')"
:selected-label="$t('multiselect.selected_label')"
@search-change="listenInputSearch"
:internal-search="false"
ref="addressSelector"
@select="selectAddress"
@remove="remove"
name="field"
track-by="id"
label="value"
:custom-label="transName"
:taggable="true"
:multiple="false"
@tag="addAddress"
:loading="isLoading"
:options="addresses"
/>
</div>
<div class="my-1">
<label class="col-form-label" for="addressSelector">{{
$t("address")
}}</label>
<VueMultiselect
id="addressSelector"
v-model="value"
:placeholder="$t('select_address')"
:tag-placeholder="$t('create_address')"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('create_address')"
:selected-label="$t('multiselect.selected_label')"
@search-change="listenInputSearch"
:internal-search="false"
ref="addressSelector"
@select="selectAddress"
@remove="remove"
name="field"
track-by="id"
label="value"
:custom-label="transName"
:taggable="true"
:multiple="false"
@tag="addAddress"
:loading="isLoading"
:options="addresses"
/>
</div>
<div
class="custom-address row g-1"
v-if="
writeNewAddress ||
writeNewPostalCode ||
(isEnteredCustomAddress && !isAddressSelectorOpen)
"
>
<div class="col-10">
<div class="form-floating">
<input
class="form-control"
type="text"
name="street"
:placeholder="$t('street')"
v-model="street"
/>
<label for="street">{{ $t("street") }}</label>
</div>
<div
class="custom-address row g-1"
v-if="
writeNewAddress ||
writeNewPostalCode ||
(isEnteredCustomAddress && !isAddressSelectorOpen)
"
>
<div class="col-10">
<div class="form-floating">
<input
class="form-control"
type="text"
name="street"
:placeholder="$t('street')"
v-model="street"
/>
<label for="street">{{ $t("street") }}</label>
</div>
</div>
<div class="col-2">
<div class="form-floating">
<input
class="form-control"
type="text"
name="streetNumber"
:placeholder="$t('streetNumber')"
v-model="streetNumber"
/>
<label for="streetNumber">{{ $t("streetNumber") }}</label>
</div>
</div>
</div>
<div class="col-2">
<div class="form-floating">
<input
class="form-control"
type="text"
name="streetNumber"
:placeholder="$t('streetNumber')"
v-model="streetNumber"
/>
<label for="streetNumber">{{ $t("streetNumber") }}</label>
</div>
</div>
</div>
</template>
<script>
import VueMultiselect from "vue-multiselect";
import {
searchReferenceAddresses,
fetchReferenceAddresses,
searchReferenceAddresses,
fetchReferenceAddresses,
} from "../../api.js";
export default {
name: "AddressSelection",
components: { VueMultiselect },
props: ["entity", "context", "updateMapCenter", "flag", "checkErrors"],
data() {
return {
value: this.context.edit ? this.entity.address.addressReference : null,
isLoading: false,
};
},
computed: {
writeNewAddress() {
return this.entity.selected.writeNew.address;
name: "AddressSelection",
components: { VueMultiselect },
props: ["entity", "context", "updateMapCenter", "flag", "checkErrors"],
data() {
return {
value: this.context.edit
? this.entity.address.addressReference
: null,
isLoading: false,
};
},
writeNewPostalCode() {
return this.entity.selected.writeNew.postCode;
computed: {
writeNewAddress() {
return this.entity.selected.writeNew.address;
},
writeNewPostalCode() {
return this.entity.selected.writeNew.postCode;
},
isAddressSelectorOpen() {
return this.$refs.addressSelector.$data.isOpen;
},
isEnteredCustomAddress() {
return (
this.$data.value !== null &&
typeof this.$data.value.text !== "undefined"
);
},
addresses() {
return this.entity.loaded.addresses;
},
street: {
set(value) {
this.entity.selected.address.street = value;
},
get() {
return this.entity.selected.address.street;
},
},
streetNumber: {
set(value) {
this.entity.selected.address.streetNumber = value;
},
get() {
return this.entity.selected.address.streetNumber;
},
},
},
isAddressSelectorOpen() {
return this.$refs.addressSelector.$data.isOpen;
},
isEnteredCustomAddress() {
return (
this.$data.value !== null &&
typeof this.$data.value.text !== "undefined"
);
},
addresses() {
return this.entity.loaded.addresses;
},
street: {
set(value) {
this.entity.selected.address.street = value;
},
get() {
return this.entity.selected.address.street;
},
},
streetNumber: {
set(value) {
this.entity.selected.address.streetNumber = value;
},
get() {
return this.entity.selected.address.streetNumber;
},
},
},
methods: {
transName(value) {
return value.streetNumber === undefined
? value.street
: `${value.streetNumber}, ${value.street}`;
},
selectAddress(value) {
this.entity.selected.address = value;
this.entity.selected.address.addressReference = {
id: value.id,
};
this.entity.selected.address.street = value.street;
this.entity.selected.address.streetNumber = value.streetNumber;
this.entity.selected.writeNew.address = false;
this.updateMapCenter(value.point);
this.checkErrors();
},
remove() {
this.entity.selected.address = {};
this.checkErrors();
},
listenInputSearch(query) {
//console.log('listenInputSearch', query, this.isAddressSelectorOpen);
if (
!this.entity.selected.writeNew.postcode &&
"id" in this.entity.selected.city
) {
if (query.length > 2) {
this.isLoading = true;
searchReferenceAddresses(query, this.entity.selected.city)
.then(
(addresses) =>
new Promise((resolve, reject) => {
this.entity.loaded.addresses = addresses.results;
this.isLoading = false;
resolve();
}),
)
.catch((error) => {
console.log(error); //TODO better error handling
this.isLoading = false;
});
} else {
if (query.length === 0) {
// Fetch all cities when suppressing the query
this.isLoading = true;
fetchReferenceAddresses(this.entity.selected.city)
.then(
(addresses) =>
new Promise((resolve, reject) => {
this.entity.loaded.addresses = addresses.results;
this.isLoading = false;
resolve();
}),
)
.catch((error) => {
console.log(error);
this.isLoading = false;
});
}
}
}
methods: {
transName(value) {
return value.streetNumber === undefined
? value.street
: `${value.streetNumber}, ${value.street}`;
},
selectAddress(value) {
this.entity.selected.address = value;
this.entity.selected.address.addressReference = {
id: value.id,
};
this.entity.selected.address.street = value.street;
this.entity.selected.address.streetNumber = value.streetNumber;
this.entity.selected.writeNew.address = false;
this.updateMapCenter(value.point);
this.checkErrors();
},
remove() {
this.entity.selected.address = {};
this.checkErrors();
},
listenInputSearch(query) {
//console.log('listenInputSearch', query, this.isAddressSelectorOpen);
if (
!this.entity.selected.writeNew.postcode &&
"id" in this.entity.selected.city
) {
if (query.length > 2) {
this.isLoading = true;
searchReferenceAddresses(query, this.entity.selected.city)
.then(
(addresses) =>
new Promise((resolve, reject) => {
this.entity.loaded.addresses =
addresses.results;
this.isLoading = false;
resolve();
}),
)
.catch((error) => {
console.log(error); //TODO better error handling
this.isLoading = false;
});
} else {
if (query.length === 0) {
// Fetch all cities when suppressing the query
this.isLoading = true;
fetchReferenceAddresses(this.entity.selected.city)
.then(
(addresses) =>
new Promise((resolve, reject) => {
this.entity.loaded.addresses =
addresses.results;
this.isLoading = false;
resolve();
}),
)
.catch((error) => {
console.log(error);
this.isLoading = false;
});
}
}
}
if (this.isAddressSelectorOpen) {
this.$data.value = { text: query };
} else if (this.isEnteredCustomAddress) {
let addr = this.splitAddress(this.$data.value.text);
this.entity.selected.address.street = addr.street;
this.entity.selected.address.streetNumber = addr.number;
this.entity.selected.writeNew.address = true;
this.checkErrors();
}
if (this.isAddressSelectorOpen) {
this.$data.value = { text: query };
} else if (this.isEnteredCustomAddress) {
let addr = this.splitAddress(this.$data.value.text);
this.entity.selected.address.street = addr.street;
this.entity.selected.address.streetNumber = addr.number;
this.entity.selected.writeNew.address = true;
this.checkErrors();
}
},
splitAddress(address) {
let substr = address.split(",").map((s) => s.trim());
if (substr.length === 1) {
substr = address.split(" ");
}
let decimal = [];
substr.forEach((s, i) => {
decimal[i] = /^\d+$/.test(s);
});
if (decimal[0] === true) {
return {
number: substr.shift(),
street: substr.join(" "),
};
} else if (decimal[decimal.length - 1] === true) {
return {
number: substr.pop(),
street: substr.join(" "),
};
}
return {
number: "",
street: substr.join(" "),
};
},
addAddress() {
this.entity.selected.writeNew.address = true;
},
},
splitAddress(address) {
let substr = address.split(",").map((s) => s.trim());
if (substr.length === 1) {
substr = address.split(" ");
}
let decimal = [];
substr.forEach((s, i) => {
decimal[i] = /^\d+$/.test(s);
});
if (decimal[0] === true) {
return {
number: substr.shift(),
street: substr.join(" "),
};
} else if (decimal[decimal.length - 1] === true) {
return {
number: substr.pop(),
street: substr.join(" "),
};
}
return {
number: "",
street: substr.join(" "),
};
},
addAddress() {
this.entity.selected.writeNew.address = true;
},
},
};
</script>

View File

@@ -1,60 +1,60 @@
<template>
<div class="my-1">
<label class="col-form-label">{{ $t("city") }}</label>
<VueMultiselect
id="citySelector"
v-model="value"
@search-change="listenInputSearch"
ref="citySelector"
@select="selectCity"
@remove="remove"
name="field"
track-by="id"
label="value"
:custom-label="transName"
:placeholder="$t('select_city')"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('create_postal_code')"
:selected-label="$t('multiselect.selected_label')"
:taggable="true"
:multiple="false"
:internal-search="false"
@tag="addPostcode"
:tag-placeholder="$t('create_postal_code')"
:loading="isLoading"
:options="cities"
/>
</div>
<div class="my-1">
<label class="col-form-label">{{ $t("city") }}</label>
<VueMultiselect
id="citySelector"
v-model="value"
@search-change="listenInputSearch"
ref="citySelector"
@select="selectCity"
@remove="remove"
name="field"
track-by="id"
label="value"
:custom-label="transName"
:placeholder="$t('select_city')"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('create_postal_code')"
:selected-label="$t('multiselect.selected_label')"
:taggable="true"
:multiple="false"
:internal-search="false"
@tag="addPostcode"
:tag-placeholder="$t('create_postal_code')"
:loading="isLoading"
:options="cities"
/>
</div>
<div
class="custom-postcode row g-1"
v-if="writeNewPostcode || (isEnteredCustomCity && !isCitySelectorOpen)"
>
<div class="col-4">
<div class="form-floating">
<input
class="form-control"
type="text"
id="code"
:placeholder="$t('postalCode_code')"
v-model="code"
/>
<label for="code">{{ $t("postalCode_code") }}</label>
</div>
<div
class="custom-postcode row g-1"
v-if="writeNewPostcode || (isEnteredCustomCity && !isCitySelectorOpen)"
>
<div class="col-4">
<div class="form-floating">
<input
class="form-control"
type="text"
id="code"
:placeholder="$t('postalCode_code')"
v-model="code"
/>
<label for="code">{{ $t("postalCode_code") }}</label>
</div>
</div>
<div class="col-8">
<div class="form-floating">
<input
class="form-control"
type="text"
id="name"
:placeholder="$t('postalCode_name')"
v-model="name"
/>
<label for="name">{{ $t("postalCode_name") }}</label>
</div>
</div>
</div>
<div class="col-8">
<div class="form-floating">
<input
class="form-control"
type="text"
id="name"
:placeholder="$t('postalCode_name')"
v-model="name"
/>
<label for="name">{{ $t("postalCode_name") }}</label>
</div>
</div>
</div>
</template>
<script>
@@ -62,185 +62,190 @@ import VueMultiselect from "vue-multiselect";
import { searchCities, fetchCities } from "../../api.js";
export default {
name: "CitySelection",
components: { VueMultiselect },
props: [
"entity",
"context",
"focusOnAddress",
"updateMapCenter",
"flag",
"checkErrors",
],
emits: ["getReferenceAddresses"],
data() {
return {
value: this.context.edit ? this.entity.address.postcode : null,
isLoading: false,
};
},
computed: {
writeNewPostcode() {
return this.entity.selected.writeNew.postcode;
name: "CitySelection",
components: { VueMultiselect },
props: [
"entity",
"context",
"focusOnAddress",
"updateMapCenter",
"flag",
"checkErrors",
],
emits: ["getReferenceAddresses"],
data() {
return {
value: this.context.edit ? this.entity.address.postcode : null,
isLoading: false,
};
},
isCitySelectorOpen() {
return this.$refs.citySelector.$data.isOpen;
computed: {
writeNewPostcode() {
return this.entity.selected.writeNew.postcode;
},
isCitySelectorOpen() {
return this.$refs.citySelector.$data.isOpen;
},
isEnteredCustomCity() {
return (
this.$data.value !== null &&
typeof this.$data.value.text !== "undefined"
);
},
cities() {
return this.entity.loaded.cities.sort(
(a, b) => Number(a.code) - Number(b.code) || a.name > b.name,
);
},
name: {
set(value) {
this.entity.selected.postcode.name = value;
},
get() {
return this.entity.selected.postcode.name;
},
},
code: {
set(value) {
this.entity.selected.postcode.code = value;
},
get() {
return this.entity.selected.postcode.code;
},
},
},
isEnteredCustomCity() {
return (
this.$data.value !== null &&
typeof this.$data.value.text !== "undefined"
);
},
cities() {
return this.entity.loaded.cities.sort(
(a, b) => Number(a.code) - Number(b.code) || a.name > b.name,
);
},
name: {
set(value) {
this.entity.selected.postcode.name = value;
},
get() {
return this.entity.selected.postcode.name;
},
},
code: {
set(value) {
this.entity.selected.postcode.code = value;
},
get() {
return this.entity.selected.postcode.code;
},
},
},
mounted() {
console.log(
"writeNew.postcode",
this.entity.selected.writeNew.postcode,
"in mounted",
);
if (this.context.edit) {
this.entity.selected.city = this.value;
this.entity.selected.postcode.name = this.value.name;
this.entity.selected.postcode.code = this.value.code;
this.$emit("getReferenceAddresses", this.value);
if (typeof this.value.center !== "undefined") {
this.updateMapCenter(this.value.center);
if (this.value.center.coordinates) {
this.entity.selected.postcode.coordinates =
this.value.center.coordinates;
mounted() {
console.log(
"writeNew.postcode",
this.entity.selected.writeNew.postcode,
"in mounted",
);
if (this.context.edit) {
this.entity.selected.city = this.value;
this.entity.selected.postcode.name = this.value.name;
this.entity.selected.postcode.code = this.value.code;
this.$emit("getReferenceAddresses", this.value);
if (typeof this.value.center !== "undefined") {
this.updateMapCenter(this.value.center);
if (this.value.center.coordinates) {
this.entity.selected.postcode.coordinates =
this.value.center.coordinates;
}
}
}
}
}
},
methods: {
transName(value) {
return value.code && value.name ? `${value.name} (${value.code})` : "";
},
selectCity(value) {
console.log(value);
this.entity.selected.city = value;
this.entity.selected.postcode.name = value.name;
this.entity.selected.postcode.code = value.code;
if (value.center) {
this.entity.selected.postcode.coordinates = value.center.coordinates;
}
this.entity.selected.writeNew.postcode = false;
this.$emit("getReferenceAddresses", value);
this.focusOnAddress();
if (value.center) {
this.updateMapCenter(value.center);
}
this.checkErrors();
},
remove() {
this.entity.selected.city = {};
this.checkErrors();
},
listenInputSearch(query) {
if (query.length > 2) {
this.isLoading = true;
searchCities(query, this.entity.selected.country)
.then(
(cities) =>
new Promise((resolve, reject) => {
this.entity.loaded.cities = cities.results.filter(
(c) => c.origin !== 3,
); // filter out user-defined cities
this.isLoading = false;
resolve();
}),
)
.catch((error) => {
console.log(error); //TODO better error handling
this.isLoading = false;
});
} else {
if (query.length === 0) {
// Fetch all cities when suppressing the query
this.isLoading = true;
fetchCities(this.entity.selected.country)
.then(
(cities) =>
new Promise((resolve, reject) => {
this.entity.loaded.cities = cities.results.filter(
(c) => c.origin !== 3,
); // filter out user-defined cities
this.isLoading = false;
resolve();
}),
)
.catch((error) => {
console.log(error);
this.isLoading = false;
methods: {
transName(value) {
return value.code && value.name
? `${value.name} (${value.code})`
: "";
},
selectCity(value) {
console.log(value);
this.entity.selected.city = value;
this.entity.selected.postcode.name = value.name;
this.entity.selected.postcode.code = value.code;
if (value.center) {
this.entity.selected.postcode.coordinates =
value.center.coordinates;
}
this.entity.selected.writeNew.postcode = false;
this.$emit("getReferenceAddresses", value);
this.focusOnAddress();
if (value.center) {
this.updateMapCenter(value.center);
}
this.checkErrors();
},
remove() {
this.entity.selected.city = {};
this.checkErrors();
},
listenInputSearch(query) {
if (query.length > 2) {
this.isLoading = true;
searchCities(query, this.entity.selected.country)
.then(
(cities) =>
new Promise((resolve, reject) => {
this.entity.loaded.cities =
cities.results.filter(
(c) => c.origin !== 3,
); // filter out user-defined cities
this.isLoading = false;
resolve();
}),
)
.catch((error) => {
console.log(error); //TODO better error handling
this.isLoading = false;
});
} else {
if (query.length === 0) {
// Fetch all cities when suppressing the query
this.isLoading = true;
fetchCities(this.entity.selected.country)
.then(
(cities) =>
new Promise((resolve, reject) => {
this.entity.loaded.cities =
cities.results.filter(
(c) => c.origin !== 3,
); // filter out user-defined cities
this.isLoading = false;
resolve();
}),
)
.catch((error) => {
console.log(error);
this.isLoading = false;
});
}
}
if (this.isCitySelectorOpen) {
this.$data.value = { text: query };
} else if (this.isEnteredCustomCity) {
let city = this.splitCity(this.$data.value.text);
this.$refs.citySelector.currentOptionLabel = "";
this.entity.selected.city = city;
this.entity.selected.postcode.name = city.name;
this.entity.selected.postcode.code = city.code;
this.entity.selected.writeNew.postcode = true;
console.log("writeNew.postcode true, in listenInputSearch");
}
},
splitCity(city) {
let substr = city.split("-").map((s) => s.trim());
if (substr.length === 1) {
substr = city.split(" ");
}
//console.log('substr', substr);
let decimal = [];
substr.forEach((s, i) => {
decimal[i] = /^\d+$/.test(s);
});
}
}
if (this.isCitySelectorOpen) {
this.$data.value = { text: query };
} else if (this.isEnteredCustomCity) {
let city = this.splitCity(this.$data.value.text);
this.$refs.citySelector.currentOptionLabel = "";
this.entity.selected.city = city;
this.entity.selected.postcode.name = city.name;
this.entity.selected.postcode.code = city.code;
this.entity.selected.writeNew.postcode = true;
console.log("writeNew.postcode true, in listenInputSearch");
}
if (decimal[0] === true) {
return {
code: substr.shift(),
name: substr.join(" "),
};
} else if (decimal[decimal.length - 1] === true) {
return {
code: substr.pop(),
name: substr.join(" "),
};
}
return {
code: "",
name: substr.join(" "),
};
},
addPostcode() {
console.log("addPostcode: pass here ?? never, it seems");
this.entity.selected.writeNew.postcode = true;
console.log("writeNew.postcode true, in addPostcode");
},
},
splitCity(city) {
let substr = city.split("-").map((s) => s.trim());
if (substr.length === 1) {
substr = city.split(" ");
}
//console.log('substr', substr);
let decimal = [];
substr.forEach((s, i) => {
decimal[i] = /^\d+$/.test(s);
});
if (decimal[0] === true) {
return {
code: substr.shift(),
name: substr.join(" "),
};
} else if (decimal[decimal.length - 1] === true) {
return {
code: substr.pop(),
name: substr.join(" "),
};
}
return {
code: "",
name: substr.join(" "),
};
},
addPostcode() {
console.log("addPostcode: pass here ?? never, it seems");
this.entity.selected.writeNew.postcode = true;
console.log("writeNew.postcode true, in addPostcode");
},
},
};
</script>

View File

@@ -1,23 +1,23 @@
<template>
<div class="my-1">
<label class="col-form-label" for="countrySelect">{{
$t("country")
}}</label>
<VueMultiselect
id="countrySelect"
label="name"
track-by="id"
:custom-label="transName"
:placeholder="$t('select_country')"
:options="sortedCountries"
v-model="value"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
@select="selectCountry"
@remove="remove"
/>
</div>
<div class="my-1">
<label class="col-form-label" for="countrySelect">{{
$t("country")
}}</label>
<VueMultiselect
id="countrySelect"
label="name"
track-by="id"
:custom-label="transName"
:placeholder="$t('select_country')"
:options="sortedCountries"
v-model="value"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
@select="selectCountry"
@remove="remove"
/>
</div>
</template>
<script>
@@ -25,59 +25,63 @@ import VueMultiselect from "vue-multiselect";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "CountrySelection",
components: { VueMultiselect },
props: ["context", "entity", "flag", "checkErrors"],
emits: ["getCities"],
data() {
return {
value: this.selectCountryByCode(
this.context.edit
? this.entity.selected.country.code
: this.context.defaults.default_country,
),
};
},
computed: {
sortedCountries() {
const countries = this.entity.loaded.countries;
let sortedCountries = [];
sortedCountries.push(...countries.filter((c) => c.countryCode === "FR"));
sortedCountries.push(...countries.filter((c) => c.countryCode === "BE"));
sortedCountries.push(
...countries
.filter((c) => c.countryCode !== "FR")
.filter((c) => c.countryCode !== "BE"),
);
return sortedCountries;
name: "CountrySelection",
components: { VueMultiselect },
props: ["context", "entity", "flag", "checkErrors"],
emits: ["getCities"],
data() {
return {
value: this.selectCountryByCode(
this.context.edit
? this.entity.selected.country.code
: this.context.defaults.default_country,
),
};
},
},
mounted() {
console.log("country selection mounted", this.value);
if (this.value !== undefined) {
this.selectCountry(this.value);
}
},
methods: {
selectCountryByCode(countryCode) {
return this.entity.loaded.countries.filter(
(c) => c.countryCode === countryCode,
)[0];
computed: {
sortedCountries() {
const countries = this.entity.loaded.countries;
let sortedCountries = [];
sortedCountries.push(
...countries.filter((c) => c.countryCode === "FR"),
);
sortedCountries.push(
...countries.filter((c) => c.countryCode === "BE"),
);
sortedCountries.push(
...countries
.filter((c) => c.countryCode !== "FR")
.filter((c) => c.countryCode !== "BE"),
);
return sortedCountries;
},
},
transName({ name }) {
return localizeString(name);
mounted() {
console.log("country selection mounted", this.value);
if (this.value !== undefined) {
this.selectCountry(this.value);
}
},
selectCountry(value) {
//console.log('select country', value);
this.entity.selected.country = value;
this.$emit("getCities", value);
this.checkErrors();
methods: {
selectCountryByCode(countryCode) {
return this.entity.loaded.countries.filter(
(c) => c.countryCode === countryCode,
)[0];
},
transName({ name }) {
return localizeString(name);
},
selectCountry(value) {
//console.log('select country', value);
this.entity.selected.country = value;
this.$emit("getCities", value);
this.checkErrors();
},
remove() {
this.entity.selected.country = null;
this.checkErrors();
},
},
remove() {
this.entity.selected.country = null;
this.checkErrors();
},
},
};
</script>

View File

@@ -1,66 +1,69 @@
<template>
<div v-if="insideModal === false" class="loading">
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw" />
<span class="sr-only">{{ $t("loading") }}</span>
</div>
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">
{{ errorMsg }}
</div>
<address-render-box :address="selectedAddress" />
<div class="row">
<div v-if="showDateFrom" class="col-lg-6 address-valid date-since">
<h3>{{ $t(getValidFromDateText) }}</h3>
<div class="input-group mb-3">
<span class="input-group-text" id="validFrom"
><i class="fa fa-fw fa-calendar"
/></span>
<input
type="date"
class="form-control form-control-lg"
name="validFrom"
:placeholder="$t(getValidFromDateText)"
v-model="validFrom"
aria-describedby="validFrom"
<div v-if="insideModal === false" class="loading">
<i
v-if="flag.loading"
class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"
/>
</div>
<span class="sr-only">{{ $t("loading") }}</span>
</div>
<div v-if="showDateTo" class="col-lg-6 address-valid date-until">
<h3>{{ $t(getValidToDateText) }}</h3>
<div class="input-group mb-3">
<span class="input-group-text" id="validTo"
><i class="fa fa-fw fa-calendar"
/></span>
<input
type="date"
class="form-control form-control-lg"
name="validTo"
:placeholder="$t(getValidToDateText)"
v-model="validTo"
aria-describedby="validTo"
/>
</div>
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">
{{ errorMsg }}
</div>
</div>
<action-buttons
v-if="insideModal === false"
:options="this.options"
:defaultz="this.defaultz"
>
<template #before>
<slot name="before" />
</template>
<template #action>
<slot name="action" />
</template>
<template #after>
<slot name="after" />
</template>
</action-buttons>
<address-render-box :address="selectedAddress" />
<div class="row">
<div v-if="showDateFrom" class="col-lg-6 address-valid date-since">
<h3>{{ $t(getValidFromDateText) }}</h3>
<div class="input-group mb-3">
<span class="input-group-text" id="validFrom"
><i class="fa fa-fw fa-calendar"
/></span>
<input
type="date"
class="form-control form-control-lg"
name="validFrom"
:placeholder="$t(getValidFromDateText)"
v-model="validFrom"
aria-describedby="validFrom"
/>
</div>
</div>
<div v-if="showDateTo" class="col-lg-6 address-valid date-until">
<h3>{{ $t(getValidToDateText) }}</h3>
<div class="input-group mb-3">
<span class="input-group-text" id="validTo"
><i class="fa fa-fw fa-calendar"
/></span>
<input
type="date"
class="form-control form-control-lg"
name="validTo"
:placeholder="$t(getValidToDateText)"
v-model="validTo"
aria-describedby="validTo"
/>
</div>
</div>
</div>
<action-buttons
v-if="insideModal === false"
:options="this.options"
:defaultz="this.defaultz"
>
<template #before>
<slot name="before" />
</template>
<template #action>
<slot name="action" />
</template>
<template #after>
<slot name="after" />
</template>
</action-buttons>
</template>
<script>
@@ -69,113 +72,116 @@ import AddressRenderBox from "ChillMainAssets/vuejs/_components/Entity/AddressRe
import ActionButtons from "./ActionButtons.vue";
export default {
name: "DatePane",
components: {
AddressRenderBox,
ActionButtons,
},
props: [
"context",
"options",
"defaultz",
"flag",
"entity",
"errorMsg",
"insideModal",
],
computed: {
address() {
return this.entity.address;
name: "DatePane",
components: {
AddressRenderBox,
ActionButtons,
},
validFrom: {
set(value) {
this.entity.selected.valid.from = ISOToDate(value);
},
get() {
return dateToISO(this.entity.selected.valid.from);
},
},
validTo: {
set(value) {
this.entity.selected.valid.to = ISOToDate(value);
},
get() {
return dateToISO(this.entity.selected.valid.to);
},
},
getValidFromDateText() {
return this.context.target.name === "household"
? "move_date"
: "validFrom";
},
getValidToDateText() {
return "validTo";
},
showDateFrom() {
return !this.context.edit && this.options.useDate.validFrom;
},
showDateTo() {
return !this.context.edit && this.options.useDate.validTo;
},
selectedAddress() {
let address = {};
props: [
"context",
"options",
"defaultz",
"flag",
"entity",
"errorMsg",
"insideModal",
],
computed: {
address() {
return this.entity.address;
},
validFrom: {
set(value) {
this.entity.selected.valid.from = ISOToDate(value);
},
get() {
return dateToISO(this.entity.selected.valid.from);
},
},
validTo: {
set(value) {
this.entity.selected.valid.to = ISOToDate(value);
},
get() {
return dateToISO(this.entity.selected.valid.to);
},
},
getValidFromDateText() {
return this.context.target.name === "household"
? "move_date"
: "validFrom";
},
getValidToDateText() {
return "validTo";
},
showDateFrom() {
return !this.context.edit && this.options.useDate.validFrom;
},
showDateTo() {
return !this.context.edit && this.options.useDate.validTo;
},
selectedAddress() {
let address = {};
address["country"] = this.entity.selected.country
? this.entity.selected.country
: null;
address["postcode"] = this.entity.selected.postcode
? this.entity.selected.postcode
: null;
address["country"] = this.entity.selected.country
? this.entity.selected.country
: null;
address["postcode"] = this.entity.selected.postcode
? this.entity.selected.postcode
: null;
if (this.entity.selected.address) {
let number = this.entity.selected.address.streetNumber
? this.entity.selected.address.streetNumber
: null;
let street = this.entity.selected.address.street
? this.entity.selected.address.street
: null;
address["text"] = number + ", " + street;
if (this.entity.selected.address) {
let number = this.entity.selected.address.streetNumber
? this.entity.selected.address.streetNumber
: null;
let street = this.entity.selected.address.street
? this.entity.selected.address.street
: null;
address["text"] = number + ", " + street;
address["street"] = this.entity.selected.address.street
? this.entity.selected.address.street
: null;
address["streetNumber"] = this.entity.selected.address.streetNumber
? this.entity.selected.address.streetNumber
: null;
address["floor"] = this.entity.selected.address.floor
? this.entity.selected.address.floor
: null;
address["corridor"] = this.entity.selected.address.corridor
? this.entity.selected.address.corridor
: null;
address["steps"] = this.entity.selected.address.steps
? this.entity.selected.address.steps
: null;
address["flat"] = this.entity.selected.address.flat
? this.entity.selected.address.flat
: null;
address["buildingName"] = this.entity.selected.address.buildingName
? this.entity.selected.address.buildingName
: null;
address["distribution"] = this.entity.selected.address.distribution
? this.entity.selected.address.distribution
: null;
address["extra"] = this.entity.selected.address.extra
? this.entity.selected.address.extra
: null;
}
address["street"] = this.entity.selected.address.street
? this.entity.selected.address.street
: null;
address["streetNumber"] = this.entity.selected.address
.streetNumber
? this.entity.selected.address.streetNumber
: null;
address["floor"] = this.entity.selected.address.floor
? this.entity.selected.address.floor
: null;
address["corridor"] = this.entity.selected.address.corridor
? this.entity.selected.address.corridor
: null;
address["steps"] = this.entity.selected.address.steps
? this.entity.selected.address.steps
: null;
address["flat"] = this.entity.selected.address.flat
? this.entity.selected.address.flat
: null;
address["buildingName"] = this.entity.selected.address
.buildingName
? this.entity.selected.address.buildingName
: null;
address["distribution"] = this.entity.selected.address
.distribution
? this.entity.selected.address.distribution
: null;
address["extra"] = this.entity.selected.address.extra
? this.entity.selected.address.extra
: null;
}
if (this.entity.selected.valid) {
address["validFrom"] = this.entity.selected.valid.from
? dateToISO(this.entity.selected.valid.from)
: null;
address["validTo"] = this.entity.selected.valid.to
? dateToISO(this.entity.selected.valid.to)
: null;
}
if (this.entity.selected.valid) {
address["validFrom"] = this.entity.selected.valid.from
? dateToISO(this.entity.selected.valid.from)
: null;
address["validTo"] = this.entity.selected.valid.to
? dateToISO(this.entity.selected.valid.to)
: null;
}
return address;
return address;
},
},
},
};
</script>

View File

@@ -1,101 +1,104 @@
<template>
<div class="address-form">
<!-- Not display in modal -->
<div v-if="insideModal === false" class="loading">
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw" />
<span class="sr-only">Loading...</span>
</div>
<div v-if="errors.length" class="alert alert-warning">
<ul>
<li v-for="(e, i) in errors" :key="i">
{{ e }}
</li>
</ul>
</div>
<h4 class="h3">
{{ $t("select_an_address_title") }}
</h4>
<div class="row my-3">
<div class="col-lg-6">
<div class="form-check">
<input
type="checkbox"
class="form-check-input"
id="isConfidential"
v-model="isConfidential"
:value="valueConfidential"
/>
<label class="form-check-label" for="isConfidential">
{{ $t("isConfidential") }}
</label>
</div>
<div class="form-check">
<input
type="checkbox"
class="form-check-input"
id="isNoAddress"
v-model="isNoAddress"
:value="value"
/>
<label class="form-check-label" for="isNoAddress">
{{ $t("isNoAddress") }}
</label>
<div class="address-form">
<!-- Not display in modal -->
<div v-if="insideModal === false" class="loading">
<i
v-if="flag.loading"
class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"
/>
<span class="sr-only">Loading...</span>
</div>
<country-selection
:context="context"
:entity="entity"
:flag="flag"
:check-errors="checkErrors"
@get-cities="$emit('getCities', selected.country)"
/>
<div v-if="errors.length" class="alert alert-warning">
<ul>
<li v-for="(e, i) in errors" :key="i">
{{ e }}
</li>
</ul>
</div>
<city-selection
:entity="entity"
:context="context"
:focus-on-address="focusOnAddress"
:update-map-center="updateMapCenter"
:flag="flag"
:check-errors="checkErrors"
@get-reference-addresses="
$emit('getReferenceAddresses', selected.city)
"
/>
<h4 class="h3">
{{ $t("select_an_address_title") }}
</h4>
<div class="row my-3">
<div class="col-lg-6">
<div class="form-check">
<input
type="checkbox"
class="form-check-input"
id="isConfidential"
v-model="isConfidential"
:value="valueConfidential"
/>
<label class="form-check-label" for="isConfidential">
{{ $t("isConfidential") }}
</label>
</div>
<div class="form-check">
<input
type="checkbox"
class="form-check-input"
id="isNoAddress"
v-model="isNoAddress"
:value="value"
/>
<label class="form-check-label" for="isNoAddress">
{{ $t("isNoAddress") }}
</label>
</div>
<address-selection
v-if="!isNoAddress"
:entity="entity"
:context="context"
:update-map-center="updateMapCenter"
:flag="flag"
:check-errors="checkErrors"
/>
</div>
<div class="col-lg-6 mt-3 mt-lg-0">
<address-map :entity="entity" ref="addressMap" />
</div>
<country-selection
:context="context"
:entity="entity"
:flag="flag"
:check-errors="checkErrors"
@get-cities="$emit('getCities', selected.country)"
/>
<city-selection
:entity="entity"
:context="context"
:focus-on-address="focusOnAddress"
:update-map-center="updateMapCenter"
:flag="flag"
:check-errors="checkErrors"
@get-reference-addresses="
$emit('getReferenceAddresses', selected.city)
"
/>
<address-selection
v-if="!isNoAddress"
:entity="entity"
:context="context"
:update-map-center="updateMapCenter"
:flag="flag"
:check-errors="checkErrors"
/>
</div>
<div class="col-lg-6 mt-3 mt-lg-0">
<address-map :entity="entity" ref="addressMap" />
</div>
</div>
<address-more :entity="entity" :is-no-address="isNoAddress" />
<action-buttons
v-if="insideModal === false"
:options="this.options"
:defaultz="this.defaultz"
>
<template #before>
<slot name="before" />
</template>
<template #action>
<slot name="action" />
</template>
<template #after>
<slot name="after" />
</template>
</action-buttons>
</div>
<address-more :entity="entity" :is-no-address="isNoAddress" />
<action-buttons
v-if="insideModal === false"
:options="this.options"
:defaultz="this.defaultz"
>
<template #before>
<slot name="before" />
</template>
<template #action>
<slot name="action" />
</template>
<template #after>
<slot name="after" />
</template>
</action-buttons>
</div>
</template>
<script>
@@ -107,88 +110,88 @@ import AddressMore from "./AddAddress/AddressMore";
import ActionButtons from "./ActionButtons.vue";
export default {
name: "EditPane",
components: {
CountrySelection,
CitySelection,
AddressSelection,
AddressMap,
AddressMore,
ActionButtons,
},
props: [
"context",
"options",
"defaultz",
"flag",
"entity",
"errorMsg",
"insideModal",
"errors",
"checkErrors",
],
emits: ["getCities", "getReferenceAddresses"],
data() {
return {
value: false,
valueConfidential: false,
};
},
computed: {
address() {
return this.entity.address;
name: "EditPane",
components: {
CountrySelection,
CitySelection,
AddressSelection,
AddressMap,
AddressMore,
ActionButtons,
},
loaded() {
return this.entity.loaded;
props: [
"context",
"options",
"defaultz",
"flag",
"entity",
"errorMsg",
"insideModal",
"errors",
"checkErrors",
],
emits: ["getCities", "getReferenceAddresses"],
data() {
return {
value: false,
valueConfidential: false,
};
},
selected() {
return this.entity.selected;
computed: {
address() {
return this.entity.address;
},
loaded() {
return this.entity.loaded;
},
selected() {
return this.entity.selected;
},
addressMap() {
return this.entity.addressMap;
},
isConfidential: {
set(value) {
this.entity.selected.confidential = value;
},
get() {
return this.entity.selected.confidential;
},
},
isNoAddress: {
set(value) {
console.log("isNoAddress value", value);
this.entity.selected.isNoAddress = value;
this.checkErrors();
},
get() {
return this.entity.selected.isNoAddress;
},
},
},
addressMap() {
return this.entity.addressMap;
methods: {
focusOnAddress() {
const addressSelector = document.getElementById("addressSelector");
if (addressSelector !== null) {
addressSelector.focus();
}
},
updateMapCenter(point) {
console.log("point", point);
this.addressMap.center[0] = point.coordinates[0];
this.addressMap.center[1] = point.coordinates[1];
this.$refs.addressMap.update(); // cast child methods
},
},
isConfidential: {
set(value) {
this.entity.selected.confidential = value;
},
get() {
return this.entity.selected.confidential;
},
},
isNoAddress: {
set(value) {
console.log("isNoAddress value", value);
this.entity.selected.isNoAddress = value;
this.checkErrors();
},
get() {
return this.entity.selected.isNoAddress;
},
},
},
methods: {
focusOnAddress() {
const addressSelector = document.getElementById("addressSelector");
if (addressSelector !== null) {
addressSelector.focus();
}
},
updateMapCenter(point) {
console.log("point", point);
this.addressMap.center[0] = point.coordinates[0];
this.addressMap.center[1] = point.coordinates[1];
this.$refs.addressMap.update(); // cast child methods
},
},
};
</script>
<style lang="scss">
div.address-form {
div#address_map {
height: 400px;
width: 100%;
z-index: 1;
}
div#address_map {
height: 400px;
width: 100%;
z-index: 1;
}
}
</style>

View File

@@ -1,98 +1,109 @@
<template>
<div v-if="!onlyButton" class="mt-4 flex-grow-1">
<div class="loading">
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw" />
<span class="sr-only">{{ $t("loading") }}</span>
<div v-if="!onlyButton" class="mt-4 flex-grow-1">
<div class="loading">
<i
v-if="flag.loading"
class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"
/>
<span class="sr-only">{{ $t("loading") }}</span>
</div>
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">
{{ errorMsg }}
</div>
<div v-if="flag.success" class="alert alert-success">
{{ $t(getSuccessText) }}
<span v-if="forceRedirect">{{ $t("wait_redirection") }}</span>
</div>
<div
v-if="
!this.context.edit &&
!this.flag.success &&
this.context.target.name !== 'household'
"
class="mt-5"
>
<div class="no-address-yet">
<i class="fa fa-map-marker" aria-hidden="true" />
<p class="chill-no-data-statement">
{{ $t("not_yet_address") }}
</p>
<action-buttons
:options="this.options"
:defaultz="this.defaultz"
class="add-address-btn"
>
<template #action>
<button
@click.prevent="$emit('openEditPane')"
class="btn"
:class="getClassButton"
type="button"
name="button"
:title="$t(getTextButton)"
>
<span v-if="displayTextButton">{{
$t(getTextButton)
}}</span>
</button>
</template>
</action-buttons>
</div>
</div>
<address-render-box
:address="address"
:is-multiline="false"
:use-date-pane="useDatePane"
/>
<div
v-if="this.context.target.name === 'household' || this.context.edit"
>
<action-buttons :options="this.options" :defaultz="this.defaultz">
<template #action>
<button
@click.prevent="$emit('openEditPane')"
class="btn"
:class="getClassButton"
type="button"
name="button"
:title="$t(getTextButton)"
>
<span v-if="displayTextButton">{{
$t(getTextButton)
}}</span>
</button>
</template>
</action-buttons>
</div>
</div>
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">
{{ errorMsg }}
</div>
<div v-if="flag.success" class="alert alert-success">
{{ $t(getSuccessText) }}
<span v-if="forceRedirect">{{ $t("wait_redirection") }}</span>
</div>
<div
v-if="
!this.context.edit &&
!this.flag.success &&
this.context.target.name !== 'household'
"
class="mt-5"
>
<div class="no-address-yet">
<i class="fa fa-map-marker" aria-hidden="true" />
<p class="chill-no-data-statement">
{{ $t("not_yet_address") }}
</p>
<div v-if="onlyButton">
<action-buttons
:options="this.options"
:defaultz="this.defaultz"
class="add-address-btn"
:options="this.options"
:defaultz="this.defaultz"
class="add-address-btn"
>
<template #action>
<button
@click.prevent="$emit('openEditPane')"
class="btn"
:class="getClassButton"
type="button"
name="button"
:title="$t(getTextButton)"
>
<span v-if="displayTextButton">{{ $t(getTextButton) }}</span>
</button>
</template>
<template #action>
<button
@click.prevent="$emit('openEditPane')"
class="btn"
:class="getClassButton"
type="button"
name="button"
:title="$t(getTextButton)"
>
<span v-if="displayTextButton">{{
$t(getTextButton)
}}</span>
</button>
</template>
</action-buttons>
</div>
</div>
<address-render-box
:address="address"
:is-multiline="false"
:use-date-pane="useDatePane"
/>
<div v-if="this.context.target.name === 'household' || this.context.edit">
<action-buttons :options="this.options" :defaultz="this.defaultz">
<template #action>
<button
@click.prevent="$emit('openEditPane')"
class="btn"
:class="getClassButton"
type="button"
name="button"
:title="$t(getTextButton)"
>
<span v-if="displayTextButton">{{ $t(getTextButton) }}</span>
</button>
</template>
</action-buttons>
</div>
</div>
<div v-if="onlyButton">
<action-buttons
:options="this.options"
:defaultz="this.defaultz"
class="add-address-btn"
>
<template #action>
<button
@click.prevent="$emit('openEditPane')"
class="btn"
:class="getClassButton"
type="button"
name="button"
:title="$t(getTextButton)"
>
<span v-if="displayTextButton">{{ $t(getTextButton) }}</span>
</button>
</template>
</action-buttons>
</div>
</template>
<script>
@@ -100,101 +111,103 @@ import AddressRenderBox from "ChillMainAssets/vuejs/_components/Entity/AddressRe
import ActionButtons from "./ActionButtons.vue";
export default {
name: "ShowPane",
components: {
AddressRenderBox,
ActionButtons,
},
props: [
"context",
"defaultz",
"options",
"flag",
"entity",
"errorMsg",
"useDatePane",
],
emits: ["openEditPane"],
mounted() {
//console.log('context', this.context)
},
computed: {
address() {
return this.entity.address;
name: "ShowPane",
components: {
AddressRenderBox,
ActionButtons,
},
displayTextButton() {
return typeof this.options.button !== "undefined" &&
typeof this.options.button.displayText !== "undefined"
? this.options.button.displayText
: this.defaultz.button.displayText;
props: [
"context",
"defaultz",
"options",
"flag",
"entity",
"errorMsg",
"useDatePane",
],
emits: ["openEditPane"],
mounted() {
//console.log('context', this.context)
},
getClassButton() {
let type = this.context.edit
? this.defaultz.button.type.edit
: this.defaultz.button.type.create;
let size =
typeof this.options.button !== "undefined" &&
this.options.button.size !== null
? `${this.options.button.size} `
: "";
return `${size}${type}`;
computed: {
address() {
return this.entity.address;
},
displayTextButton() {
return typeof this.options.button !== "undefined" &&
typeof this.options.button.displayText !== "undefined"
? this.options.button.displayText
: this.defaultz.button.displayText;
},
getClassButton() {
let type = this.context.edit
? this.defaultz.button.type.edit
: this.defaultz.button.type.create;
let size =
typeof this.options.button !== "undefined" &&
this.options.button.size !== null
? `${this.options.button.size} `
: "";
return `${size}${type}`;
},
getTextButton() {
if (
typeof this.options.button.text !== "undefined" &&
(this.options.button.text.edit !== null ||
this.options.button.text.create !== null)
) {
return this.context.edit
? this.options.button.text.edit
: this.options.button.text.create;
}
return this.context.edit
? this.defaultz.button.text.edit
: this.defaultz.button.text.create;
},
getSuccessText() {
return this.context.edit
? "address_edit_success"
: "address_new_success";
},
onlyButton() {
return typeof this.options.onlyButton !== "undefined"
? this.options.onlyButton
: this.defaultz.onlyButton;
},
forceRedirect() {
return !(
this.context.backUrl === null ||
typeof this.context.backUrl === "undefined"
);
},
// showMessageWhenNoAddress() {
// let showMessageWhenNoAddress = this.options.showMessageWhenNoAddress === undefined ? this.defaultz.showMessageWhenNoAddress : this.options.showMessageWhenNoAddress;
// if (showMessageWhenNoAddress === true || showMessageWhenNoAddress === false) {
// return !this.context.edit && !this.address.id && showMessageWhenNoAddress;
// }
// return !this.context.edit && !this.address.id && this.options.stickyActions;
// }
},
getTextButton() {
if (
typeof this.options.button.text !== "undefined" &&
(this.options.button.text.edit !== null ||
this.options.button.text.create !== null)
) {
return this.context.edit
? this.options.button.text.edit
: this.options.button.text.create;
}
return this.context.edit
? this.defaultz.button.text.edit
: this.defaultz.button.text.create;
},
getSuccessText() {
return this.context.edit ? "address_edit_success" : "address_new_success";
},
onlyButton() {
return typeof this.options.onlyButton !== "undefined"
? this.options.onlyButton
: this.defaultz.onlyButton;
},
forceRedirect() {
return !(
this.context.backUrl === null ||
typeof this.context.backUrl === "undefined"
);
},
// showMessageWhenNoAddress() {
// let showMessageWhenNoAddress = this.options.showMessageWhenNoAddress === undefined ? this.defaultz.showMessageWhenNoAddress : this.options.showMessageWhenNoAddress;
// if (showMessageWhenNoAddress === true || showMessageWhenNoAddress === false) {
// return !this.context.edit && !this.address.id && showMessageWhenNoAddress;
// }
// return !this.context.edit && !this.address.id && this.options.stickyActions;
// }
},
};
</script>
<style lang="scss">
.address-container {
display: flex;
justify-content: flex-end;
border-radius: 5px;
display: flex;
justify-content: flex-end;
border-radius: 5px;
}
.no-address-yet {
text-align: center;
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
padding: 1.5rem;
margin-bottom: 2rem;
i {
font-size: 2rem;
text-align: center;
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.add-address-btn {
display: block;
}
i {
font-size: 2rem;
margin-bottom: 2rem;
}
.add-address-btn {
display: block;
}
}
</style>

View File

@@ -1,64 +1,67 @@
<template>
<div v-if="insideModal === false" class="loading">
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw" />
<span class="sr-only">{{ $t("loading") }}</span>
</div>
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">
{{ errorMsg }}
</div>
<h4 class="h3">
{{ $t("address_suggestions") }}
</h4>
<div class="flex-table AddressSuggestionList">
<div
v-for="(a, i) in context.suggestions"
class="item-bloc"
:key="`suggestions-${i}`"
>
<div class="float-button bottom">
<div class="box">
<div class="action">
<!-- QUESTION normal que ça vienne avant l'adresse ? pourquoi pas après avoir affiché le address-render-box ? -->
<ul class="record_actions">
<li>
<button
class="btn btn-sm btn-choose"
@click="this.pickAddress(a)"
>
{{ $t("use_this_address") }}
</button>
</li>
</ul>
</div>
<ul class="list-content fa-ul">
<li>
<i class="fa fa-li fa-map-marker" />
<address-render-box :address="a" />
</li>
</ul>
</div>
</div>
<div v-if="insideModal === false" class="loading">
<i
v-if="flag.loading"
class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"
/>
<span class="sr-only">{{ $t("loading") }}</span>
</div>
</div>
<action-buttons
v-if="insideModal === false"
:options="this.options"
:defaultz="this.defaultz"
>
<template #before>
<slot name="before" />
</template>
<template #action>
<slot name="action" />
</template>
<template #after>
<slot name="after" />
</template>
</action-buttons>
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">
{{ errorMsg }}
</div>
<h4 class="h3">
{{ $t("address_suggestions") }}
</h4>
<div class="flex-table AddressSuggestionList">
<div
v-for="(a, i) in context.suggestions"
class="item-bloc"
:key="`suggestions-${i}`"
>
<div class="float-button bottom">
<div class="box">
<div class="action">
<!-- QUESTION normal que ça vienne avant l'adresse ? pourquoi pas après avoir affiché le address-render-box ? -->
<ul class="record_actions">
<li>
<button
class="btn btn-sm btn-choose"
@click="this.pickAddress(a)"
>
{{ $t("use_this_address") }}
</button>
</li>
</ul>
</div>
<ul class="list-content fa-ul">
<li>
<i class="fa fa-li fa-map-marker" />
<address-render-box :address="a" />
</li>
</ul>
</div>
</div>
</div>
</div>
<action-buttons
v-if="insideModal === false"
:options="this.options"
:defaultz="this.defaultz"
>
<template #before>
<slot name="before" />
</template>
<template #action>
<slot name="action" />
</template>
<template #after>
<slot name="after" />
</template>
</action-buttons>
</template>
<script>
@@ -66,26 +69,26 @@ import AddressRenderBox from "ChillMainAssets/vuejs/_components/Entity/AddressRe
import ActionButtons from "./ActionButtons.vue";
export default {
name: "SuggestPane",
components: {
AddressRenderBox,
ActionButtons,
},
props: [
"context",
"options",
"defaultz",
"flag",
"entity",
"errorMsg",
"insideModal",
],
computed: {},
methods: {
pickAddress(address) {
console.log("pickAddress in suggest pane", address);
this.$emit("pickAddress", address);
name: "SuggestPane",
components: {
AddressRenderBox,
ActionButtons,
},
props: [
"context",
"options",
"defaultz",
"flag",
"entity",
"errorMsg",
"insideModal",
],
computed: {},
methods: {
pickAddress(address) {
console.log("pickAddress in suggest pane", address);
this.$emit("pickAddress", address);
},
},
},
};
</script>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts">
import {
trans,
EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING,
EXPORT_GENERATION_TOO_MANY_RETRIES,
EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT,
EXPORT_GENERATION_EXPORT_READY,
trans,
EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING,
EXPORT_GENERATION_TOO_MANY_RETRIES,
EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT,
EXPORT_GENERATION_EXPORT_READY,
} from "translator";
import { computed, onMounted, ref } from "vue";
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
@@ -13,9 +13,9 @@ import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentAction
import { ExportGeneration } from "ChillMainAssets/types";
interface AppProps {
exportGenerationId: string;
title: string;
createdDate: string;
exportGenerationId: string;
title: string;
createdDate: string;
}
const props = defineProps<AppProps>();
@@ -23,19 +23,19 @@ const props = defineProps<AppProps>();
const exportGeneration = ref<ExportGeneration | null>(null);
const status = computed<StoredObjectStatus>(
() => exportGeneration.value?.status ?? "pending",
() => exportGeneration.value?.status ?? "pending",
);
const storedObject = computed<null | StoredObject>(() => {
if (exportGeneration.value === null) {
return null;
}
if (exportGeneration.value === null) {
return null;
}
return exportGeneration.value?.storedObject;
return exportGeneration.value?.storedObject;
});
const isPending = computed<boolean>(() => status.value === "pending");
const isFetching = computed<boolean>(
() => tryiesForReady.value < maxTryiesForReady,
() => tryiesForReady.value < maxTryiesForReady,
);
const isReady = computed<boolean>(() => status.value === "ready");
const isFailure = computed<boolean>(() => status.value === "failure");
@@ -52,87 +52,90 @@ let tryiesForReady = ref<number>(0);
const maxTryiesForReady = 120;
const checkForReady = function (): void {
if (
"ready" === status.value ||
"empty" === status.value ||
"failure" === status.value ||
// stop reloading if the page stays opened for a long time
tryiesForReady.value > maxTryiesForReady
) {
return;
}
if (
"ready" === status.value ||
"empty" === status.value ||
"failure" === status.value ||
// stop reloading if the page stays opened for a long time
tryiesForReady.value > maxTryiesForReady
) {
return;
}
tryiesForReady.value = tryiesForReady.value + 1;
setTimeout(onObjectNewStatusCallback, 5000);
tryiesForReady.value = tryiesForReady.value + 1;
setTimeout(onObjectNewStatusCallback, 5000);
};
const onObjectNewStatusCallback = async function (): Promise<void> {
exportGeneration.value = await fetchExportGenerationStatus(
props.exportGenerationId,
);
exportGeneration.value = await fetchExportGenerationStatus(
props.exportGenerationId,
);
if (isPending.value) {
checkForReady();
return Promise.resolve();
}
if (isPending.value) {
checkForReady();
return Promise.resolve();
}
return Promise.resolve();
};
onMounted(() => {
onObjectNewStatusCallback();
onObjectNewStatusCallback();
});
</script>
<template>
<div id="waiting-screen">
<div v-if="isPending && isFetching" class="alert alert-danger text-center">
<div>
<p>
{{ trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING) }}
</p>
</div>
<div id="waiting-screen">
<div
v-if="isPending && isFetching"
class="alert alert-danger text-center"
>
<div>
<p>
{{ trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING) }}
</p>
</div>
<div>
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
<span class="sr-only">Loading...</span>
</div>
</div>
<div v-if="isPending && !isFetching" class="alert alert-info">
<div>
<p>
{{ trans(EXPORT_GENERATION_TOO_MANY_RETRIES) }}
</p>
</div>
</div>
<div v-if="isFailure" class="alert alert-danger text-center">
<div>
<p>
{{ trans(EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT) }}
</p>
</div>
</div>
<div v-if="isReady" class="alert alert-success text-center">
<div>
<p>
{{ trans(EXPORT_GENERATION_EXPORT_READY) }}
</p>
<div>
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
<span class="sr-only">Loading...</span>
</div>
</div>
<div v-if="isPending && !isFetching" class="alert alert-info">
<div>
<p>
{{ trans(EXPORT_GENERATION_TOO_MANY_RETRIES) }}
</p>
</div>
</div>
<div v-if="isFailure" class="alert alert-danger text-center">
<div>
<p>
{{ trans(EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT) }}
</p>
</div>
</div>
<div v-if="isReady" class="alert alert-success text-center">
<div>
<p>
{{ trans(EXPORT_GENERATION_EXPORT_READY) }}
</p>
<p v-if="storedObject !== null">
<document-action-buttons-group
:stored-object="storedObject"
:filename="filename"
></document-action-buttons-group>
</p>
</div>
<p v-if="storedObject !== null">
<document-action-buttons-group
:stored-object="storedObject"
:filename="filename"
></document-action-buttons-group>
</p>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
#waiting-screen {
> .alert {
min-height: 350px;
}
> .alert {
min-height: 350px;
}
}
</style>

View File

@@ -4,8 +4,8 @@ import App from "./App.vue";
const el = document.getElementById("app");
if (null === el) {
console.error("div element app was not found");
throw new Error("div element app was not found");
console.error("div element app was not found");
throw new Error("div element app was not found");
}
const exportGenerationId = el?.dataset.exportGenerationId as string;

View File

@@ -1,36 +1,36 @@
<template>
<h2>{{ $t("main_title") }}</h2>
<h2>{{ $t("main_title") }}</h2>
<ul class="nav nav-tabs">
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyCustoms' }"
@click="selectTab('MyCustoms')"
>
<i class="fa fa-dashboard" />
</a>
</li>
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyNotifications' }"
@click="selectTab('MyNotifications')"
>
{{ $t("my_notifications.tab") }}
<tab-counter :count="state.notifications.count" />
</a>
</li>
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyAccompanyingCourses' }"
@click="selectTab('MyAccompanyingCourses')"
>
{{ $t("my_accompanying_courses.tab") }}
</a>
</li>
<!-- <li class="nav-item">
<ul class="nav nav-tabs">
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyCustoms' }"
@click="selectTab('MyCustoms')"
>
<i class="fa fa-dashboard" />
</a>
</li>
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyNotifications' }"
@click="selectTab('MyNotifications')"
>
{{ $t("my_notifications.tab") }}
<tab-counter :count="state.notifications.count" />
</a>
</li>
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyAccompanyingCourses' }"
@click="selectTab('MyAccompanyingCourses')"
>
{{ $t("my_accompanying_courses.tab") }}
</a>
</li>
<!-- <li class="nav-item">
<a class="nav-link"
:class="{'active': activeTab === 'MyWorks'}"
@click="selectTab('MyWorks')">
@@ -38,57 +38,59 @@
<tab-counter :count="state.works.count"></tab-counter>
</a>
</li> -->
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyEvaluations' }"
@click="selectTab('MyEvaluations')"
>
{{ $t("my_evaluations.tab") }}
<tab-counter :count="state.evaluations.count" />
</a>
</li>
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyTasks' }"
@click="selectTab('MyTasks')"
>
{{ $t("my_tasks.tab") }}
<tab-counter
:count="state.tasks.warning.count + state.tasks.alert.count"
/>
</a>
</li>
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyWorkflows' }"
@click="selectTab('MyWorkflows')"
>
{{ $t("my_workflows.tab") }}
<tab-counter :count="state.workflows.count + state.workflowsCc.count" />
</a>
</li>
<li class="nav-item loading ms-auto py-2" v-if="loading">
<i
class="fa fa-circle-o-notch fa-spin fa-lg text-chill-gray"
:title="$t('loading')"
/>
</li>
</ul>
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyEvaluations' }"
@click="selectTab('MyEvaluations')"
>
{{ $t("my_evaluations.tab") }}
<tab-counter :count="state.evaluations.count" />
</a>
</li>
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyTasks' }"
@click="selectTab('MyTasks')"
>
{{ $t("my_tasks.tab") }}
<tab-counter
:count="state.tasks.warning.count + state.tasks.alert.count"
/>
</a>
</li>
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyWorkflows' }"
@click="selectTab('MyWorkflows')"
>
{{ $t("my_workflows.tab") }}
<tab-counter
:count="state.workflows.count + state.workflowsCc.count"
/>
</a>
</li>
<li class="nav-item loading ms-auto py-2" v-if="loading">
<i
class="fa fa-circle-o-notch fa-spin fa-lg text-chill-gray"
:title="$t('loading')"
/>
</li>
</ul>
<div class="my-4">
<my-customs v-if="activeTab === 'MyCustoms'" />
<my-works v-else-if="activeTab === 'MyWorks'" />
<my-evaluations v-else-if="activeTab === 'MyEvaluations'" />
<my-tasks v-else-if="activeTab === 'MyTasks'" />
<my-accompanying-courses
v-else-if="activeTab === 'MyAccompanyingCourses'"
/>
<my-notifications v-else-if="activeTab === 'MyNotifications'" />
<my-workflows v-else-if="activeTab === 'MyWorkflows'" />
</div>
<div class="my-4">
<my-customs v-if="activeTab === 'MyCustoms'" />
<my-works v-else-if="activeTab === 'MyWorks'" />
<my-evaluations v-else-if="activeTab === 'MyEvaluations'" />
<my-tasks v-else-if="activeTab === 'MyTasks'" />
<my-accompanying-courses
v-else-if="activeTab === 'MyAccompanyingCourses'"
/>
<my-notifications v-else-if="activeTab === 'MyNotifications'" />
<my-workflows v-else-if="activeTab === 'MyWorkflows'" />
</div>
</template>
<script>
@@ -103,55 +105,55 @@ import TabCounter from "./TabCounter";
import { mapState } from "vuex";
export default {
name: "App",
components: {
MyCustoms,
MyWorks,
MyEvaluations,
MyTasks,
MyWorkflows,
MyAccompanyingCourses,
MyNotifications,
TabCounter,
},
data() {
return {
activeTab: "MyCustoms",
};
},
computed: {
...mapState(["loading"]),
// just to see all in devtool :
...mapState({
state: (state) => state,
}),
},
methods: {
selectTab(tab) {
if (tab !== "MyCustoms") {
this.$store.dispatch("getByTab", { tab: tab });
}
this.activeTab = tab;
console.log(this.activeTab);
name: "App",
components: {
MyCustoms,
MyWorks,
MyEvaluations,
MyTasks,
MyWorkflows,
MyAccompanyingCourses,
MyNotifications,
TabCounter,
},
data() {
return {
activeTab: "MyCustoms",
};
},
computed: {
...mapState(["loading"]),
// just to see all in devtool :
...mapState({
state: (state) => state,
}),
},
methods: {
selectTab(tab) {
if (tab !== "MyCustoms") {
this.$store.dispatch("getByTab", { tab: tab });
}
this.activeTab = tab;
console.log(this.activeTab);
},
},
mounted() {
for (const m of [
"MyNotifications",
"MyAccompanyingCourses",
// 'MyWorks',
"MyEvaluations",
"MyTasks",
"MyWorkflows",
]) {
this.$store.dispatch("getByTab", { tab: m, param: "countOnly=1" });
}
},
},
mounted() {
for (const m of [
"MyNotifications",
"MyAccompanyingCourses",
// 'MyWorks',
"MyEvaluations",
"MyTasks",
"MyWorkflows",
]) {
this.$store.dispatch("getByTab", { tab: m, param: "countOnly=1" });
}
},
};
</script>
<style scoped>
a.nav-link {
cursor: pointer;
cursor: pointer;
}
</style>

View File

@@ -1,13 +1,13 @@
<template>
<div>
<h1>{{ $t("widget.news.title") }}</h1>
<ul v-if="newsItems.length > 0" class="scrollable">
<NewsItem v-for="item in newsItems" :item="item" :key="item.id" />
</ul>
<p v-if="newsItems.length === 0" class="chill-no-data-statement">
{{ $t("widget.news.none") }}
</p>
</div>
<div>
<h1>{{ $t("widget.news.title") }}</h1>
<ul v-if="newsItems.length > 0" class="scrollable">
<NewsItem v-for="item in newsItems" :item="item" :key="item.id" />
</ul>
<p v-if="newsItems.length === 0" class="chill-no-data-statement">
{{ $t("widget.news.none") }}
</p>
</div>
</template>
<script setup lang="ts">
@@ -19,26 +19,26 @@ import NewsItem from "./NewsItem.vue";
const newsItems = ref<NewsItemType[]>([]);
onMounted(() => {
fetchResults<NewsItemType>("/api/1.0/main/news/current.json")
.then((news): Promise<void> => {
// console.log('news articles', response.results)
newsItems.value = news;
fetchResults<NewsItemType>("/api/1.0/main/news/current.json")
.then((news): Promise<void> => {
// console.log('news articles', response.results)
newsItems.value = news;
return Promise.resolve();
})
.catch((error: string) => {
console.error("Error fetching news items", error);
});
return Promise.resolve();
})
.catch((error: string) => {
console.error("Error fetching news items", error);
});
});
</script>
<style scoped>
ul {
list-style: none;
padding: 0;
list-style: none;
padding: 0;
}
h1 {
text-align: center;
text-align: center;
}
</style>

View File

@@ -1,38 +1,40 @@
<template>
<li>
<h2>{{ props.item.title }}</h2>
<time class="createdBy" datetime="{{item.startDate.datetime}}">{{
$d(newsItemStartDate(), "text")
}}</time>
<div class="content" v-if="shouldTruncate(item.content)">
<div v-html="prepareContent(item.content)"></div>
<div class="float-end">
<button
class="btn btn-sm btn-show read-more"
@click="() => openModal(item)"
>
{{ $t("widget.news.readMore") }}
</button>
</div>
</div>
<div class="content" v-else>
<div v-html="convertMarkdownToHtml(item.content)"></div>
</div>
<modal v-if="showModal" @close="closeModal">
<template #header>
<p class="news-title">{{ item.title }}</p>
</template>
<template #body>
<p class="news-date">
<time class="createdBy" datetime="{{item.startDate.datetime}}">{{
<li>
<h2>{{ props.item.title }}</h2>
<time class="createdBy" datetime="{{item.startDate.datetime}}">{{
$d(newsItemStartDate(), "text")
}}</time>
</p>
<div v-html="convertMarkdownToHtml(item.content)"></div>
</template>
</modal>
</li>
}}</time>
<div class="content" v-if="shouldTruncate(item.content)">
<div v-html="prepareContent(item.content)"></div>
<div class="float-end">
<button
class="btn btn-sm btn-show read-more"
@click="() => openModal(item)"
>
{{ $t("widget.news.readMore") }}
</button>
</div>
</div>
<div class="content" v-else>
<div v-html="convertMarkdownToHtml(item.content)"></div>
</div>
<modal v-if="showModal" @close="closeModal">
<template #header>
<p class="news-title">{{ item.title }}</p>
</template>
<template #body>
<p class="news-date">
<time
class="createdBy"
datetime="{{item.startDate.datetime}}"
>{{ $d(newsItemStartDate(), "text") }}</time
>
</p>
<div v-html="convertMarkdownToHtml(item.content)"></div>
</template>
</modal>
</li>
</template>
<script setup lang="ts">
@@ -45,147 +47,151 @@ import { ref } from "vue";
import { ISOToDatetime } from "../../../chill/js/date";
const props = defineProps({
item: {
type: Object as PropType<NewsItemType>,
required: true,
},
maxLength: {
type: Number,
required: false,
default: 350,
},
maxLines: {
type: Number,
required: false,
default: 3,
},
item: {
type: Object as PropType<NewsItemType>,
required: true,
},
maxLength: {
type: Number,
required: false,
default: 350,
},
maxLines: {
type: Number,
required: false,
default: 3,
},
});
const selectedArticle = ref<NewsItemType | null>(null);
const showModal = ref(false);
const openModal = (item: NewsItemType) => {
selectedArticle.value = item;
showModal.value = true;
selectedArticle.value = item;
showModal.value = true;
};
const closeModal = () => {
selectedArticle.value = null;
showModal.value = false;
selectedArticle.value = null;
showModal.value = false;
};
const shouldTruncate = (content: string): boolean => {
const lines = content.split("\n");
const lines = content.split("\n");
// Check if any line exceeds the maximum length
const tooManyLines = lines.length > props.maxLines;
// Check if any line exceeds the maximum length
const tooManyLines = lines.length > props.maxLines;
return content.length > props.maxLength || tooManyLines;
return content.length > props.maxLength || tooManyLines;
};
const truncateContent = (content: string): string => {
let truncatedContent = content.slice(0, props.maxLength);
let linkDepth = 0;
let linkStartIndex = -1;
const lines = content.split("\n");
let truncatedContent = content.slice(0, props.maxLength);
let linkDepth = 0;
let linkStartIndex = -1;
const lines = content.split("\n");
// Truncate if amount of lines are too many
if (lines.length > props.maxLines && content.length < props.maxLength) {
const truncatedContent = lines.slice(0, props.maxLines).join("\n").trim();
return truncatedContent + "...";
}
for (let i = 0; i < truncatedContent.length; i++) {
const char = truncatedContent[i];
if (char === "[") {
linkDepth++;
if (linkDepth === 1) {
linkStartIndex = i;
}
} else if (char === "]") {
linkDepth = Math.max(0, linkDepth - 1);
} else if (char === "(" && linkDepth === 0) {
truncatedContent = truncatedContent.slice(0, i);
break;
// Truncate if amount of lines are too many
if (lines.length > props.maxLines && content.length < props.maxLength) {
const truncatedContent = lines
.slice(0, props.maxLines)
.join("\n")
.trim();
return truncatedContent + "...";
}
}
while (linkDepth > 0) {
truncatedContent += "]";
linkDepth--;
}
for (let i = 0; i < truncatedContent.length; i++) {
const char = truncatedContent[i];
// If a link was found, append the URL inside the parentheses
if (linkStartIndex !== -1) {
const linkEndIndex = content.indexOf(")", linkStartIndex);
const url = content.slice(linkStartIndex + 1, linkEndIndex);
truncatedContent = truncatedContent.slice(0, linkStartIndex) + `(${url})`;
}
if (char === "[") {
linkDepth++;
if (linkDepth === 1) {
linkStartIndex = i;
}
} else if (char === "]") {
linkDepth = Math.max(0, linkDepth - 1);
} else if (char === "(" && linkDepth === 0) {
truncatedContent = truncatedContent.slice(0, i);
break;
}
}
truncatedContent += "...";
while (linkDepth > 0) {
truncatedContent += "]";
linkDepth--;
}
return truncatedContent;
// If a link was found, append the URL inside the parentheses
if (linkStartIndex !== -1) {
const linkEndIndex = content.indexOf(")", linkStartIndex);
const url = content.slice(linkStartIndex + 1, linkEndIndex);
truncatedContent =
truncatedContent.slice(0, linkStartIndex) + `(${url})`;
}
truncatedContent += "...";
return truncatedContent;
};
const preprocess = (markdown: string): string => {
return markdown;
return markdown;
};
const postprocess = (html: string): string => {
DOMPurify.addHook("afterSanitizeAttributes", (node: any) => {
if ("target" in node) {
node.setAttribute("target", "_blank");
node.setAttribute("rel", "noopener noreferrer");
}
if (
!node.hasAttribute("target") &&
(node.hasAttribute("xlink:href") || node.hasAttribute("href"))
) {
node.setAttribute("xlink:show", "new");
}
});
DOMPurify.addHook("afterSanitizeAttributes", (node: any) => {
if ("target" in node) {
node.setAttribute("target", "_blank");
node.setAttribute("rel", "noopener noreferrer");
}
if (
!node.hasAttribute("target") &&
(node.hasAttribute("xlink:href") || node.hasAttribute("href"))
) {
node.setAttribute("xlink:show", "new");
}
});
return DOMPurify.sanitize(html);
return DOMPurify.sanitize(html);
};
const convertMarkdownToHtml = (markdown: string): string => {
marked.use({ hooks: { postprocess, preprocess }, async: false });
const rawHtml = marked(markdown) as string;
return rawHtml;
marked.use({ hooks: { postprocess, preprocess }, async: false });
const rawHtml = marked(markdown) as string;
return rawHtml;
};
const prepareContent = (content: string): string => {
const htmlContent = convertMarkdownToHtml(content);
return truncateContent(htmlContent);
const htmlContent = convertMarkdownToHtml(content);
return truncateContent(htmlContent);
};
const newsItemStartDate = (): null | Date => {
return ISOToDatetime(props.item?.startDate.datetime);
return ISOToDatetime(props.item?.startDate.datetime);
};
</script>
<style scoped>
li {
margin-bottom: 20px;
overflow: hidden;
padding: 0.8rem;
background-color: #fbfbfb;
border-radius: 4px;
margin-bottom: 20px;
overflow: hidden;
padding: 0.8rem;
background-color: #fbfbfb;
border-radius: 4px;
}
h2 {
font-size: 1rem !important;
text-transform: uppercase;
font-size: 1rem !important;
text-transform: uppercase;
}
.content {
overflow: hidden;
font-size: 0.9rem;
position: relative;
overflow: hidden;
font-size: 0.9rem;
position: relative;
}
.news-title {
font-weight: bold;
font-weight: bold;
}
</style>

View File

@@ -1,65 +1,76 @@
<template>
<div class="alert alert-light">
{{ $t("my_accompanying_courses.description") }}
</div>
<span v-if="noResults" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("opening_date") }}
</th>
<th scope="col">
{{ $t("social_issues") }}
</th>
<th scope="col">
{{ $t("concerned_persons") }}
</th>
<th scope="col" />
<th scope="col" />
</template>
<template #tbody>
<tr v-for="(c, i) in accompanyingCourses.results" :key="`course-${i}`">
<td>{{ $d(c.openingDate.datetime, "short") }}</td>
<td>
<span
v-for="(i, index) in c.socialIssues"
:key="index"
class="chill-entity entity-social-issue"
>
<span class="badge bg-chill-l-gray text-dark">
{{ localizeString(i.title) }}
</span>
</span>
</td>
<td>
<span v-for="p in c.participations" class="me-1" :key="p.person.id">
<on-the-fly
:type="p.person.type"
:id="p.person.id"
:button-text="p.person.textAge"
:display-badge="'true' === 'true'"
action="show"
/>
</span>
</td>
<td>
<span v-if="c.emergency" class="badge rounded-pill bg-danger me-1">{{
$t("emergency")
}}</span>
<span v-if="c.confidential" class="badge rounded-pill bg-danger">{{
$t("confidential")
}}</span>
</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(c)">
{{ $t("show_entity", { entity: $t("the_course") }) }}
</a>
</td>
</tr>
</template>
</tab-table>
<div class="alert alert-light">
{{ $t("my_accompanying_courses.description") }}
</div>
<span v-if="noResults" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("opening_date") }}
</th>
<th scope="col">
{{ $t("social_issues") }}
</th>
<th scope="col">
{{ $t("concerned_persons") }}
</th>
<th scope="col" />
<th scope="col" />
</template>
<template #tbody>
<tr
v-for="(c, i) in accompanyingCourses.results"
:key="`course-${i}`"
>
<td>{{ $d(c.openingDate.datetime, "short") }}</td>
<td>
<span
v-for="(i, index) in c.socialIssues"
:key="index"
class="chill-entity entity-social-issue"
>
<span class="badge bg-chill-l-gray text-dark">
{{ localizeString(i.title) }}
</span>
</span>
</td>
<td>
<span
v-for="p in c.participations"
class="me-1"
:key="p.person.id"
>
<on-the-fly
:type="p.person.type"
:id="p.person.id"
:button-text="p.person.textAge"
:display-badge="'true' === 'true'"
action="show"
/>
</span>
</td>
<td>
<span
v-if="c.emergency"
class="badge rounded-pill bg-danger me-1"
>{{ $t("emergency") }}</span
>
<span
v-if="c.confidential"
class="badge rounded-pill bg-danger"
>{{ $t("confidential") }}</span
>
</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(c)">
{{ $t("show_entity", { entity: $t("the_course") }) }}
</a>
</td>
</tr>
</template>
</tab-table>
</template>
<script>
@@ -69,33 +80,33 @@ import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "MyAccompanyingCourses",
components: {
TabTable,
OnTheFly,
},
computed: {
...mapState(["accompanyingCourses"]),
...mapGetters(["isAccompanyingCoursesLoaded"]),
noResults() {
if (!this.isAccompanyingCoursesLoaded) {
return false;
} else {
return this.accompanyingCourses.count === 0;
}
name: "MyAccompanyingCourses",
components: {
TabTable,
OnTheFly,
},
},
methods: {
localizeString,
getUrl(c) {
return `/fr/parcours/${c.id}`;
computed: {
...mapState(["accompanyingCourses"]),
...mapGetters(["isAccompanyingCoursesLoaded"]),
noResults() {
if (!this.isAccompanyingCoursesLoaded) {
return false;
} else {
return this.accompanyingCourses.count === 0;
}
},
},
methods: {
localizeString,
getUrl(c) {
return `/fr/parcours/${c.id}`;
},
},
},
};
</script>
<style scoped>
span.badge.rounded-pill.bg-danger {
text-transform: uppercase;
text-transform: uppercase;
}
</style>

View File

@@ -1,103 +1,105 @@
<template>
<span v-if="noResults" class="chill-no-data-statement">{{
$t("no_dashboard")
}}</span>
<div v-else id="dashboards" class="container g-3">
<div class="row">
<div class="mbloc col-xs-12 col-sm-4">
<div class="custom1">
<ul class="list-unstyled">
<li v-if="counter.notifications > 0">
<i18n-t
keypath="counter.unread_notifications"
tag="span"
:class="counterClass"
:plural="counter.notifications"
>
<template #n>
<span>{{ counter.notifications }}</span>
</template>
</i18n-t>
</li>
<li v-if="counter.accompanyingCourses > 0">
<i18n-t
keypath="counter.assignated_courses"
tag="span"
:class="counterClass"
:plural="counter.accompanyingCourses"
>
<template #n>
<span>{{ counter.accompanyingCourses }}</span>
</template>
</i18n-t>
</li>
<li v-if="counter.works > 0">
<i18n-t
keypath="counter.assignated_actions"
tag="span"
:class="counterClass"
:plural="counter.works"
>
<template #n>
<span>{{ counter.works }}</span>
</template>
</i18n-t>
</li>
<li v-if="counter.evaluations > 0">
<i18n-t
keypath="counter.assignated_evaluations"
tag="span"
:class="counterClass"
:plural="counter.evaluations"
>
<template #n>
<span>{{ counter.evaluations }}</span>
</template>
</i18n-t>
</li>
<li v-if="counter.tasksAlert > 0">
<i18n-t
keypath="counter.alert_tasks"
tag="span"
:class="counterClass"
:plural="counter.tasksAlert"
>
<template #n>
<span>{{ counter.tasksAlert }}</span>
</template>
</i18n-t>
</li>
<li v-if="counter.tasksWarning > 0">
<i18n-t
keypath="counter.warning_tasks"
tag="span"
:class="counterClass"
:plural="counter.tasksWarning"
>
<template #n>
<span>{{ counter.tasksWarning }}</span>
</template>
</i18n-t>
</li>
</ul>
</div>
</div>
<span v-if="noResults" class="chill-no-data-statement">{{
$t("no_dashboard")
}}</span>
<div v-else id="dashboards" class="container g-3">
<div class="row">
<div class="mbloc col-xs-12 col-sm-4">
<div class="custom1">
<ul class="list-unstyled">
<li v-if="counter.notifications > 0">
<i18n-t
keypath="counter.unread_notifications"
tag="span"
:class="counterClass"
:plural="counter.notifications"
>
<template #n>
<span>{{ counter.notifications }}</span>
</template>
</i18n-t>
</li>
<li v-if="counter.accompanyingCourses > 0">
<i18n-t
keypath="counter.assignated_courses"
tag="span"
:class="counterClass"
:plural="counter.accompanyingCourses"
>
<template #n>
<span>{{
counter.accompanyingCourses
}}</span>
</template>
</i18n-t>
</li>
<li v-if="counter.works > 0">
<i18n-t
keypath="counter.assignated_actions"
tag="span"
:class="counterClass"
:plural="counter.works"
>
<template #n>
<span>{{ counter.works }}</span>
</template>
</i18n-t>
</li>
<li v-if="counter.evaluations > 0">
<i18n-t
keypath="counter.assignated_evaluations"
tag="span"
:class="counterClass"
:plural="counter.evaluations"
>
<template #n>
<span>{{ counter.evaluations }}</span>
</template>
</i18n-t>
</li>
<li v-if="counter.tasksAlert > 0">
<i18n-t
keypath="counter.alert_tasks"
tag="span"
:class="counterClass"
:plural="counter.tasksAlert"
>
<template #n>
<span>{{ counter.tasksAlert }}</span>
</template>
</i18n-t>
</li>
<li v-if="counter.tasksWarning > 0">
<i18n-t
keypath="counter.warning_tasks"
tag="span"
:class="counterClass"
:plural="counter.tasksWarning"
>
<template #n>
<span>{{ counter.tasksWarning }}</span>
</template>
</i18n-t>
</li>
</ul>
</div>
</div>
<template v-if="this.hasDashboardItems">
<template
v-for="(dashboardItem, index) in this.dashboardItems"
:key="index"
>
<div
class="mbloc col-xs-12 col-sm-8 news"
v-if="dashboardItem.type === 'news'"
>
<News />
</div>
</template>
</template>
<template v-if="this.hasDashboardItems">
<template
v-for="(dashboardItem, index) in this.dashboardItems"
:key="index"
>
<div
class="mbloc col-xs-12 col-sm-8 news"
v-if="dashboardItem.type === 'news'"
>
<News />
</div>
</template>
</template>
</div>
</div>
</div>
</template>
<script>
@@ -106,37 +108,37 @@ import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import News from "./DashboardWidgets/News.vue";
export default {
name: "MyCustoms",
components: {
News,
},
data() {
return {
counterClass: {
counter: true, //hack to pass class 'counter' in i18n-t
},
dashboardItems: [],
masonry: null,
};
},
computed: {
...mapGetters(["counter"]),
noResults() {
return false;
name: "MyCustoms",
components: {
News,
},
hasDashboardItems() {
return this.dashboardItems.length > 0;
data() {
return {
counterClass: {
counter: true, //hack to pass class 'counter' in i18n-t
},
dashboardItems: [],
masonry: null,
};
},
computed: {
...mapGetters(["counter"]),
noResults() {
return false;
},
hasDashboardItems() {
return this.dashboardItems.length > 0;
},
},
mounted() {
makeFetch("GET", "/api/1.0/main/dashboard-config-item.json")
.then((response) => {
this.dashboardItems = response;
})
.catch((error) => {
throw error;
});
},
},
mounted() {
makeFetch("GET", "/api/1.0/main/dashboard-config-item.json")
.then((response) => {
this.dashboardItems = response;
})
.catch((error) => {
throw error;
});
},
};
</script>
@@ -144,18 +146,18 @@ export default {
div.custom4,
div.custom3,
div.custom2 {
font-style: italic;
color: var(--bs-chill-gray);
font-style: italic;
color: var(--bs-chill-gray);
}
span.counter {
& > span {
background-color: unset;
}
& > span {
background-color: unset;
}
}
div.news {
max-height: 22rem;
overflow: hidden;
overflow-y: scroll;
max-height: 22rem;
overflow: hidden;
overflow-y: scroll;
}
</style>

View File

@@ -1,81 +1,96 @@
<template>
<div class="accompanying-course-work">
<div class="alert alert-light">
{{ $t("my_evaluations.description") }}
<div class="accompanying-course-work">
<div class="alert alert-light">
{{ $t("my_evaluations.description") }}
</div>
<span v-if="noResults" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("max_date") }}
</th>
<th scope="col">
{{ $t("evaluation") }}
</th>
<th scope="col">
{{ $t("SocialAction") }}
</th>
<th scope="col" />
</template>
<template #tbody>
<tr
v-for="(e, i) in evaluations.results"
:key="`evaluation-${i}`"
>
<td>{{ $d(e.maxDate.datetime, "short") }}</td>
<td>
{{ localizeString(e.evaluation.title) }}
</td>
<td>
<span class="chill-entity entity-social-issue">
<span class="badge bg-chill-l-gray text-dark">
{{
e.accompanyingPeriodWork.socialAction.issue
.text
}}
</span>
</span>
<h4 class="badge-title">
<span class="title_label" />
<span class="title_action">
{{ e.accompanyingPeriodWork.socialAction.text }}
</span>
</h4>
<span
v-for="person in e.accompanyingPeriodWork.persons"
class="me-1"
:key="person.id"
>
<on-the-fly
:type="person.type"
:id="person.id"
:button-text="person.textAge"
:display-badge="'true' === 'true'"
action="show"
/>
</span>
</td>
<td>
<div
class="btn-group-vertical"
role="group"
aria-label="Actions"
>
<a class="btn btn-sm btn-show" :href="getUrl(e)">
{{
$t("show_entity", {
entity: $t("the_evaluation"),
})
}}
</a>
<a
class="btn btn-sm btn-show"
:href="
getUrl(
e.accompanyingPeriodWork
.accompanyingPeriod,
)
"
>
{{
$t("show_entity", {
entity: $t("the_course"),
})
}}
</a>
</div>
</td>
</tr>
</template>
</tab-table>
</div>
<span v-if="noResults" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("max_date") }}
</th>
<th scope="col">
{{ $t("evaluation") }}
</th>
<th scope="col">
{{ $t("SocialAction") }}
</th>
<th scope="col" />
</template>
<template #tbody>
<tr v-for="(e, i) in evaluations.results" :key="`evaluation-${i}`">
<td>{{ $d(e.maxDate.datetime, "short") }}</td>
<td>
{{ localizeString(e.evaluation.title) }}
</td>
<td>
<span class="chill-entity entity-social-issue">
<span class="badge bg-chill-l-gray text-dark">
{{ e.accompanyingPeriodWork.socialAction.issue.text }}
</span>
</span>
<h4 class="badge-title">
<span class="title_label" />
<span class="title_action">
{{ e.accompanyingPeriodWork.socialAction.text }}
</span>
</h4>
<span
v-for="person in e.accompanyingPeriodWork.persons"
class="me-1"
:key="person.id"
>
<on-the-fly
:type="person.type"
:id="person.id"
:button-text="person.textAge"
:display-badge="'true' === 'true'"
action="show"
/>
</span>
</td>
<td>
<div class="btn-group-vertical" role="group" aria-label="Actions">
<a class="btn btn-sm btn-show" :href="getUrl(e)">
{{
$t("show_entity", {
entity: $t("the_evaluation"),
})
}}
</a>
<a
class="btn btn-sm btn-show"
:href="getUrl(e.accompanyingPeriodWork.accompanyingPeriod)"
>
{{
$t("show_entity", {
entity: $t("the_course"),
})
}}
</a>
</div>
</td>
</tr>
</template>
</tab-table>
</div>
</template>
<script>
@@ -85,38 +100,38 @@ import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly";
import { localizeString } from "../../lib/localizationHelper/localizationHelper";
export default {
name: "MyEvaluations",
components: {
TabTable,
OnTheFly,
},
computed: {
...mapState(["evaluations"]),
...mapGetters(["isEvaluationsLoaded"]),
noResults() {
if (!this.isEvaluationsLoaded) {
return false;
} else {
return this.evaluations.count === 0;
}
name: "MyEvaluations",
components: {
TabTable,
OnTheFly,
},
},
methods: {
localizeString,
getUrl(e) {
switch (e.type) {
case "accompanying_period_work_evaluation":
let anchor = "#evaluations";
return `/fr/person/accompanying-period/work/${e.accompanyingPeriodWork.id}/edit${anchor}`;
case "accompanying_period_work":
return `/fr/person/accompanying-period/work/${e.id}/edit`;
case "accompanying_period":
return `/fr/parcours/${e.id}`;
default:
throw "entity type unknown";
}
computed: {
...mapState(["evaluations"]),
...mapGetters(["isEvaluationsLoaded"]),
noResults() {
if (!this.isEvaluationsLoaded) {
return false;
} else {
return this.evaluations.count === 0;
}
},
},
methods: {
localizeString,
getUrl(e) {
switch (e.type) {
case "accompanying_period_work_evaluation":
let anchor = "#evaluations";
return `/fr/person/accompanying-period/work/${e.accompanyingPeriodWork.id}/edit${anchor}`;
case "accompanying_period_work":
return `/fr/person/accompanying-period/work/${e.id}/edit`;
case "accompanying_period":
return `/fr/parcours/${e.id}`;
default:
throw "entity type unknown";
}
},
},
},
};
</script>

View File

@@ -1,46 +1,46 @@
<template>
<div class="alert alert-light">
{{ $t("my_notifications.description") }}
</div>
<span v-if="noResults" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("Date") }}
</th>
<th scope="col">
{{ $t("Subject") }}
</th>
<th scope="col">
{{ $t("From") }}
</th>
<th scope="col" />
</template>
<template #tbody>
<tr v-for="(n, i) in notifications.results" :key="`notify-${i}`">
<td>{{ $d(n.date.datetime, "long") }}</td>
<td>
<span class="unread">
<i class="fa fa-envelope-o" />
<a :href="getNotificationUrl(n)">{{ n.title }}</a>
</span>
</td>
<td v-if="n.sender != null">
{{ n.sender.text }}
</td>
<td v-else>
{{ $t("automatic_notification") }}
</td>
<td>
<a class="btn btn-sm btn-show" :href="getEntityUrl(n)">
{{ $t("show_entity", { entity: getEntityName(n) }) }}
</a>
</td>
</tr>
</template>
</tab-table>
<div class="alert alert-light">
{{ $t("my_notifications.description") }}
</div>
<span v-if="noResults" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("Date") }}
</th>
<th scope="col">
{{ $t("Subject") }}
</th>
<th scope="col">
{{ $t("From") }}
</th>
<th scope="col" />
</template>
<template #tbody>
<tr v-for="(n, i) in notifications.results" :key="`notify-${i}`">
<td>{{ $d(n.date.datetime, "long") }}</td>
<td>
<span class="unread">
<i class="fa fa-envelope-o" />
<a :href="getNotificationUrl(n)">{{ n.title }}</a>
</span>
</td>
<td v-if="n.sender != null">
{{ n.sender.text }}
</td>
<td v-else>
{{ $t("automatic_notification") }}
</td>
<td>
<a class="btn btn-sm btn-show" :href="getEntityUrl(n)">
{{ $t("show_entity", { entity: getEntityName(n) }) }}
</a>
</td>
</tr>
</template>
</tab-table>
</template>
<script>
@@ -49,69 +49,69 @@ import TabTable from "./TabTable";
import { appMessages } from "ChillMainAssets/vuejs/HomepageWidget/js/i18n";
export default {
name: "MyNotifications",
components: {
TabTable,
},
computed: {
...mapState(["notifications"]),
...mapGetters(["isNotificationsLoaded"]),
noResults() {
if (!this.isNotificationsLoaded) {
return false;
} else {
return this.notifications.count === 0;
}
name: "MyNotifications",
components: {
TabTable,
},
},
methods: {
getNotificationUrl(n) {
return `/fr/notification/${n.id}/show`;
computed: {
...mapState(["notifications"]),
...mapGetters(["isNotificationsLoaded"]),
noResults() {
if (!this.isNotificationsLoaded) {
return false;
} else {
return this.notifications.count === 0;
}
},
},
getEntityName(n) {
switch (n.relatedEntityClass) {
case "Chill\\ActivityBundle\\Entity\\Activity":
return appMessages.fr.the_activity;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod":
return appMessages.fr.the_course;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork":
return appMessages.fr.the_action;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument":
return appMessages.fr.the_evaluation_document;
case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow":
return appMessages.fr.the_workflow;
default:
throw "notification type unknown";
}
methods: {
getNotificationUrl(n) {
return `/fr/notification/${n.id}/show`;
},
getEntityName(n) {
switch (n.relatedEntityClass) {
case "Chill\\ActivityBundle\\Entity\\Activity":
return appMessages.fr.the_activity;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod":
return appMessages.fr.the_course;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork":
return appMessages.fr.the_action;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument":
return appMessages.fr.the_evaluation_document;
case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow":
return appMessages.fr.the_workflow;
default:
throw "notification type unknown";
}
},
getEntityUrl(n) {
switch (n.relatedEntityClass) {
case "Chill\\ActivityBundle\\Entity\\Activity":
return `/fr/activity/${n.relatedEntityId}/show`;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod":
return `/fr/parcours/${n.relatedEntityId}`;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork":
return `/fr/person/accompanying-period/work/${n.relatedEntityId}/show`;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument":
return `/fr/person/accompanying-period/work/evaluation/document/${n.relatedEntityId}/show`;
case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow":
return `/fr/main/workflow/${n.relatedEntityId}/show`;
default:
throw "notification type unknown";
}
},
},
getEntityUrl(n) {
switch (n.relatedEntityClass) {
case "Chill\\ActivityBundle\\Entity\\Activity":
return `/fr/activity/${n.relatedEntityId}/show`;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod":
return `/fr/parcours/${n.relatedEntityId}`;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork":
return `/fr/person/accompanying-period/work/${n.relatedEntityId}/show`;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument":
return `/fr/person/accompanying-period/work/evaluation/document/${n.relatedEntityId}/show`;
case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow":
return `/fr/main/workflow/${n.relatedEntityId}/show`;
default:
throw "notification type unknown";
}
},
},
};
</script>
<style lang="scss" scoped>
span.unread {
font-weight: bold;
i {
margin-right: 0.5em;
}
a {
text-decoration: unset;
}
font-weight: bold;
i {
margin-right: 0.5em;
}
a {
text-decoration: unset;
}
}
</style>

View File

@@ -1,78 +1,83 @@
<template>
<div class="alert alert-light">
{{ $t("my_tasks.description_warning") }}
</div>
<span v-if="noResultsAlert" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("warning_date") }}
</th>
<th scope="col">
{{ $t("max_date") }}
</th>
<th scope="col">
{{ $t("task") }}
</th>
<th scope="col" />
</template>
<template #tbody>
<tr v-for="(t, i) in tasks.alert.results" :key="`task-alert-${i}`">
<td v-if="null !== t.warningDate">
{{ $d(t.warningDate.datetime, "short") }}
</td>
<td v-else />
<td>
<span class="outdated">{{ $d(t.endDate.datetime, "short") }}</span>
</td>
<td>{{ t.title }}</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(t)">
{{ $t("show_entity", { entity: $t("the_task") }) }}
</a>
</td>
</tr>
</template>
</tab-table>
<div class="alert alert-light">
{{ $t("my_tasks.description_warning") }}
</div>
<span v-if="noResultsAlert" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("warning_date") }}
</th>
<th scope="col">
{{ $t("max_date") }}
</th>
<th scope="col">
{{ $t("task") }}
</th>
<th scope="col" />
</template>
<template #tbody>
<tr v-for="(t, i) in tasks.alert.results" :key="`task-alert-${i}`">
<td v-if="null !== t.warningDate">
{{ $d(t.warningDate.datetime, "short") }}
</td>
<td v-else />
<td>
<span class="outdated">{{
$d(t.endDate.datetime, "short")
}}</span>
</td>
<td>{{ t.title }}</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(t)">
{{ $t("show_entity", { entity: $t("the_task") }) }}
</a>
</td>
</tr>
</template>
</tab-table>
<div class="alert alert-light">
{{ $t("my_tasks.description_alert") }}
</div>
<span v-if="noResultsWarning" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("warning_date") }}
</th>
<th scope="col">
{{ $t("max_date") }}
</th>
<th scope="col">
{{ $t("task") }}
</th>
<th scope="col" />
</template>
<template #tbody>
<tr v-for="(t, i) in tasks.warning.results" :key="`task-warning-${i}`">
<td>
<span class="outdated">{{
$d(t.warningDate.datetime, "short")
}}</span>
</td>
<td>{{ $d(t.endDate.datetime, "short") }}</td>
<td>{{ t.title }}</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(t)">
{{ $t("show_entity", { entity: $t("the_task") }) }}
</a>
</td>
</tr>
</template>
</tab-table>
<div class="alert alert-light">
{{ $t("my_tasks.description_alert") }}
</div>
<span v-if="noResultsWarning" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("warning_date") }}
</th>
<th scope="col">
{{ $t("max_date") }}
</th>
<th scope="col">
{{ $t("task") }}
</th>
<th scope="col" />
</template>
<template #tbody>
<tr
v-for="(t, i) in tasks.warning.results"
:key="`task-warning-${i}`"
>
<td>
<span class="outdated">{{
$d(t.warningDate.datetime, "short")
}}</span>
</td>
<td>{{ $d(t.endDate.datetime, "short") }}</td>
<td>{{ t.title }}</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(t)">
{{ $t("show_entity", { entity: $t("the_task") }) }}
</a>
</td>
</tr>
</template>
</tab-table>
</template>
<script>
@@ -80,39 +85,39 @@ import { mapState, mapGetters } from "vuex";
import TabTable from "./TabTable";
export default {
name: "MyTasks",
components: {
TabTable,
},
computed: {
...mapState(["tasks"]),
...mapGetters(["isTasksWarningLoaded", "isTasksAlertLoaded"]),
noResultsAlert() {
if (!this.isTasksAlertLoaded) {
return false;
} else {
return this.tasks.alert.count === 0;
}
name: "MyTasks",
components: {
TabTable,
},
noResultsWarning() {
if (!this.isTasksWarningLoaded) {
return false;
} else {
return this.tasks.warning.count === 0;
}
computed: {
...mapState(["tasks"]),
...mapGetters(["isTasksWarningLoaded", "isTasksAlertLoaded"]),
noResultsAlert() {
if (!this.isTasksAlertLoaded) {
return false;
} else {
return this.tasks.alert.count === 0;
}
},
noResultsWarning() {
if (!this.isTasksWarningLoaded) {
return false;
} else {
return this.tasks.warning.count === 0;
}
},
},
},
methods: {
getUrl(t) {
return `/fr/task/single-task/${t.id}/show`;
methods: {
getUrl(t) {
return `/fr/task/single-task/${t.id}/show`;
},
},
},
};
</script>
<style scoped>
span.outdated {
font-weight: bold;
color: var(--bs-warning);
font-weight: bold;
color: var(--bs-warning);
}
</style>

View File

@@ -1,13 +1,13 @@
<template>
<div class="alert alert-light">
{{ $t("my_workflows.description") }}
</div>
<my-workflows-table :workflows="workflows" />
<div class="alert alert-light">
{{ $t("my_workflows.description") }}
</div>
<my-workflows-table :workflows="workflows" />
<div class="alert alert-light">
{{ $t("my_workflows.description_cc") }}
</div>
<my-workflows-table :workflows="workflowsCc" />
<div class="alert alert-light">
{{ $t("my_workflows.description_cc") }}
</div>
<my-workflows-table :workflows="workflowsCc" />
</template>
<script>
@@ -15,12 +15,12 @@ import { mapState } from "vuex";
import MyWorkflowsTable from "./MyWorkflowsTable.vue";
export default {
name: "MyWorkflows",
components: {
MyWorkflowsTable,
},
computed: {
...mapState(["workflows", "workflowsCc"]),
},
name: "MyWorkflows",
components: {
MyWorkflowsTable,
},
computed: {
...mapState(["workflows", "workflowsCc"]),
},
};
</script>

View File

@@ -1,57 +1,59 @@
<template>
<span v-if="hasNoResults(workflows)" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("Object_workflow") }}
</th>
<th scope="col">
{{ $t("Step") }}
</th>
<th scope="col">
{{ $t("concerned_users") }}
</th>
<th scope="col" />
</template>
<template #tbody>
<tr v-for="(w, i) in workflows.results" :key="`workflow-${i}`">
<td>
{{ w.title }}
</td>
<td>
<div class="workflow">
<div class="breadcrumb">
<i class="fa fa-circle me-1 text-chill-yellow mx-2" />
<span class="mx-2">{{ getStep(w) }}</span>
</div>
<span
v-if="w.isOnHoldAtCurrentStep"
class="badge bg-success rounded-pill"
>{{ $t("on_hold") }}</span
>
</div>
</td>
<td v-if="w.datas.persons !== null">
<span v-for="p in w.datas.persons" class="me-1" :key="p.id">
<on-the-fly
:type="p.type"
:id="p.id"
:button-text="p.textAge"
:display-badge="'true' === 'true'"
action="show"
/>
</span>
</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(w)">
{{ $t("show_entity", { entity: $t("the_workflow") }) }}
</a>
</td>
</tr>
</template>
</tab-table>
<span v-if="hasNoResults(workflows)" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("Object_workflow") }}
</th>
<th scope="col">
{{ $t("Step") }}
</th>
<th scope="col">
{{ $t("concerned_users") }}
</th>
<th scope="col" />
</template>
<template #tbody>
<tr v-for="(w, i) in workflows.results" :key="`workflow-${i}`">
<td>
{{ w.title }}
</td>
<td>
<div class="workflow">
<div class="breadcrumb">
<i
class="fa fa-circle me-1 text-chill-yellow mx-2"
/>
<span class="mx-2">{{ getStep(w) }}</span>
</div>
<span
v-if="w.isOnHoldAtCurrentStep"
class="badge bg-success rounded-pill"
>{{ $t("on_hold") }}</span
>
</div>
</td>
<td v-if="w.datas.persons !== null">
<span v-for="p in w.datas.persons" class="me-1" :key="p.id">
<on-the-fly
:type="p.type"
:id="p.id"
:button-text="p.textAge"
:display-badge="'true' === 'true'"
action="show"
/>
</span>
</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(w)">
{{ $t("show_entity", { entity: $t("the_workflow") }) }}
</a>
</td>
</tr>
</template>
</tab-table>
</template>
<script>
@@ -60,37 +62,37 @@ import TabTable from "./TabTable";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly";
export default {
name: "MyWorkflows",
components: {
TabTable,
OnTheFly,
},
props: ["workflows"],
computed: {
...mapGetters(["isWorkflowsLoaded"]),
},
methods: {
hasNoResults(workflows) {
if (!this.isWorkflowsLoaded) {
return false;
} else {
return workflows.count === 0;
}
name: "MyWorkflows",
components: {
TabTable,
OnTheFly,
},
getUrl(w) {
return `/fr/main/workflow/${w.id}/show`;
props: ["workflows"],
computed: {
...mapGetters(["isWorkflowsLoaded"]),
},
getStep(w) {
const lastStep = w.steps.length - 1;
return w.steps[lastStep].currentStep.text;
methods: {
hasNoResults(workflows) {
if (!this.isWorkflowsLoaded) {
return false;
} else {
return workflows.count === 0;
}
},
getUrl(w) {
return `/fr/main/workflow/${w.id}/show`;
},
getStep(w) {
const lastStep = w.steps.length - 1;
return w.steps[lastStep].currentStep.text;
},
},
},
};
</script>
<style scoped>
span.outdated {
font-weight: bold;
color: var(--bs-warning);
font-weight: bold;
color: var(--bs-warning);
}
</style>

View File

@@ -1,77 +1,85 @@
// CURRENTLY NOT IN USE
<template>
<div class="accompanying-course-work">
<div class="alert alert-light">
{{ $t("my_works.description") }}
<div class="accompanying-course-work">
<div class="alert alert-light">
{{ $t("my_works.description") }}
</div>
<span v-if="noResults" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("StartDate") }}
</th>
<th scope="col">
{{ $t("SocialAction") }}
</th>
<th scope="col">
{{ $t("concerned_persons") }}
</th>
<th scope="col" />
</template>
<template #tbody>
<tr v-for="(w, i) in works.results" :key="`works-${i}`">
<td>{{ $d(w.startDate.datetime, "short") }}</td>
<td>
<span class="chill-entity entity-social-issue">
<span class="badge bg-chill-l-gray text-dark">
{{ w.socialAction.issue.text }}
</span>
</span>
<h4 class="badge-title">
<span class="title_label" />
<span class="title_action">
{{ w.socialAction.text }}
</span>
</h4>
</td>
<td>
<span
v-for="person in w.persons"
class="me-1"
:key="person.id"
>
<on-the-fly
:type="person.type"
:id="person.id"
:button-text="person.textAge"
:display-badge="'true' === 'true'"
action="show"
/>
</span>
</td>
<td>
<div
class="btn-group-vertical"
role="group"
aria-label="Actions"
>
<a class="btn btn-sm btn-update" :href="getUrl(w)">
{{
$t("show_entity", {
entity: $t("the_action"),
})
}}
</a>
<a
class="btn btn-sm btn-show"
:href="getUrl(w.accompanyingPeriod)"
>
{{
$t("show_entity", {
entity: $t("the_course"),
})
}}
</a>
</div>
</td>
</tr>
</template>
</tab-table>
</div>
<span v-if="noResults" class="chill-no-data-statement">{{
$t("no_data")
}}</span>
<tab-table v-else>
<template #thead>
<th scope="col">
{{ $t("StartDate") }}
</th>
<th scope="col">
{{ $t("SocialAction") }}
</th>
<th scope="col">
{{ $t("concerned_persons") }}
</th>
<th scope="col" />
</template>
<template #tbody>
<tr v-for="(w, i) in works.results" :key="`works-${i}`">
<td>{{ $d(w.startDate.datetime, "short") }}</td>
<td>
<span class="chill-entity entity-social-issue">
<span class="badge bg-chill-l-gray text-dark">
{{ w.socialAction.issue.text }}
</span>
</span>
<h4 class="badge-title">
<span class="title_label" />
<span class="title_action">
{{ w.socialAction.text }}
</span>
</h4>
</td>
<td>
<span v-for="person in w.persons" class="me-1" :key="person.id">
<on-the-fly
:type="person.type"
:id="person.id"
:button-text="person.textAge"
:display-badge="'true' === 'true'"
action="show"
/>
</span>
</td>
<td>
<div class="btn-group-vertical" role="group" aria-label="Actions">
<a class="btn btn-sm btn-update" :href="getUrl(w)">
{{
$t("show_entity", {
entity: $t("the_action"),
})
}}
</a>
<a
class="btn btn-sm btn-show"
:href="getUrl(w.accompanyingPeriod)"
>
{{
$t("show_entity", {
entity: $t("the_course"),
})
}}
</a>
</div>
</td>
</tr>
</template>
</tab-table>
</div>
</template>
<script>
@@ -80,34 +88,34 @@ import TabTable from "./TabTable";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly";
export default {
name: "MyWorks",
components: {
TabTable,
OnTheFly,
},
computed: {
...mapState(["works"]),
...mapGetters(["isWorksLoaded"]),
noResults() {
if (!this.isWorksLoaded) {
return false;
} else {
return this.works.count === 0;
}
name: "MyWorks",
components: {
TabTable,
OnTheFly,
},
},
methods: {
getUrl(e) {
switch (e.type) {
case "accompanying_period_work":
return `/fr/person/accompanying-period/work/${e.id}/edit`;
case "accompanying_period":
return `/fr/parcours/${e.id}`;
default:
throw "entity type unknown";
}
computed: {
...mapState(["works"]),
...mapGetters(["isWorksLoaded"]),
noResults() {
if (!this.isWorksLoaded) {
return false;
} else {
return this.works.count === 0;
}
},
},
methods: {
getUrl(e) {
switch (e.type) {
case "accompanying_period_work":
return `/fr/person/accompanying-period/work/${e.id}/edit`;
case "accompanying_period":
return `/fr/parcours/${e.id}`;
default:
throw "entity type unknown";
}
},
},
},
};
</script>

View File

@@ -1,17 +1,17 @@
<template>
<span v-if="isCounterAvailable" class="badge rounded-pill bg-danger">
{{ count }}
</span>
<span v-if="isCounterAvailable" class="badge rounded-pill bg-danger">
{{ count }}
</span>
</template>
<script>
export default {
name: "TabCounter",
props: ["count"],
computed: {
isCounterAvailable() {
return typeof this.count !== "undefined" && this.count > 0;
name: "TabCounter",
props: ["count"],
computed: {
isCounterAvailable() {
return typeof this.count !== "undefined" && this.count > 0;
},
},
},
};
</script>

View File

@@ -1,20 +1,20 @@
<template>
<table class="table table-striped table-hover">
<thead>
<tr>
<slot name="thead" />
</tr>
</thead>
<tbody>
<slot name="tbody" />
</tbody>
</table>
<table class="table table-striped table-hover">
<thead>
<tr>
<slot name="thead" />
</tr>
</thead>
<tbody>
<slot name="tbody" />
</tbody>
</table>
</template>
<script>
export default {
name: "TabTable",
props: [],
name: "TabTable",
props: [],
};
</script>

View File

@@ -1,42 +1,42 @@
<template>
<on-the-fly
:type="context.type"
:id="context.id"
:action="context.action"
:button-text="options.buttonText"
:display-badge="options.displayBadge === 'true'"
:is-dead="options.isDead"
:parent="options.parent"
@save-form-on-the-fly="saveFormOnTheFly"
/>
<on-the-fly
:type="context.type"
:id="context.id"
:action="context.action"
:button-text="options.buttonText"
:display-badge="options.displayBadge === 'true'"
:is-dead="options.isDead"
:parent="options.parent"
@save-form-on-the-fly="saveFormOnTheFly"
/>
</template>
<script>
import OnTheFly from "./components/OnTheFly.vue";
export default {
name: "App",
components: {
OnTheFly,
},
props: ["onTheFly"],
computed: {
context() {
return this.onTheFly.context;
name: "App",
components: {
OnTheFly,
},
options() {
return this.onTheFly.options;
props: ["onTheFly"],
computed: {
context() {
return this.onTheFly.context;
},
options() {
return this.onTheFly.options;
},
},
},
mounted() {
//console.log('OnTheFly mounted');
//console.log('OnTheFly: data context', this.context);
//console.log('OnTheFly: data options', this.options);
},
methods: {
saveFormOnTheFly(payload) {
console.log("saveFormOnTheFly", payload);
mounted() {
//console.log('OnTheFly mounted');
//console.log('OnTheFly: data context', this.context);
//console.log('OnTheFly: data options', this.options);
},
methods: {
saveFormOnTheFly(payload) {
console.log("saveFormOnTheFly", payload);
},
},
},
};
</script>

View File

@@ -1,115 +1,113 @@
<template>
<ul class="nav nav-tabs">
<li v-if="allowedTypes.includes('person')" class="nav-item">
<a class="nav-link" :class="{ active: isActive('person') }">
<label for="person">
<input
type="radio"
name="person"
id="person"
v-model="radioType"
value="person"
/>
{{ trans(ONTHEFLY_CREATE_PERSON) }}
</label>
</a>
</li>
<li v-if="allowedTypes.includes('thirdparty')" class="nav-item">
<a class="nav-link" :class="{ active: isActive('thirdparty') }">
<label for="thirdparty">
<input
type="radio"
name="thirdparty"
id="thirdparty"
v-model="radioType"
value="thirdparty"
/>
{{ trans(ONTHEFLY_CREATE_THIRDPARTY) }}
</label>
</a>
</li>
</ul>
<ul class="nav nav-tabs">
<li v-if="allowedTypes.includes('person')" class="nav-item">
<a class="nav-link" :class="{ active: isActive('person') }">
<label for="person">
<input
type="radio"
name="person"
id="person"
v-model="radioType"
value="person"
/>
{{ $t("onthefly.create.person") }}
</label>
</a>
</li>
<li v-if="allowedTypes.includes('thirdparty')" class="nav-item">
<a class="nav-link" :class="{ active: isActive('thirdparty') }">
<label for="thirdparty">
<input
type="radio"
name="thirdparty"
id="thirdparty"
v-model="radioType"
value="thirdparty"
/>
{{ $t("onthefly.create.thirdparty") }}
</label>
</a>
</li>
</ul>
<div class="my-4">
<on-the-fly-person
v-if="type === 'person'"
:action="action"
:query="query"
ref="castPerson"
/>
<div class="my-4">
<on-the-fly-person
v-if="type === 'person'"
:action="action"
:query="query"
ref="castPerson"
/>
<on-the-fly-thirdparty
v-if="type === 'thirdparty'"
:action="action"
:query="query"
ref="castThirdparty"
/>
</div>
<on-the-fly-thirdparty
v-if="type === 'thirdparty'"
:action="action"
:query="query"
ref="castThirdparty"
/>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
<script>
import OnTheFlyPerson from "ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue";
import OnTheFlyThirdparty from "ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdParty.vue";
import {
trans,
ONTHEFLY_CREATE_PERSON,
ONTHEFLY_CREATE_THIRDPARTY,
} from "translator";
const props = defineProps({
action: String,
allowedTypes: Array,
query: String,
});
export default {
name: "OnTheFlyCreate",
props: ["action", "allowedTypes", "query"],
components: {
OnTheFlyPerson,
OnTheFlyThirdparty,
},
data() {
return {
type: null,
};
},
computed: {
radioType: {
set(type) {
this.type = type;
console.log("## type:", type, ", action:", this.action);
},
get() {
return this.type;
},
},
},
mounted() {
this.type =
this.allowedTypes.length === 1 &&
this.allowedTypes.includes("thirdparty")
? "thirdparty"
: "person";
},
methods: {
isActive(tab) {
return this.type === tab ? true : false;
},
castDataByType() {
switch (this.radioType) {
case "person":
return this.$refs.castPerson.$data.person;
case "thirdparty":
let data = this.$refs.castThirdparty.$data.thirdparty;
if (data.address !== undefined && data.address !== null) {
data.address = { id: data.address.address_id };
} else {
data.address = null;
}
const type = ref(null);
const radioType = computed({
get: () => type.value,
set: (val) => {
type.value = val;
console.log("## type:", val, ", action:", props.action);
},
});
const castPerson = ref(null);
const castThirdparty = ref(null);
onMounted(() => {
type.value =
props.allowedTypes.length === 1 && props.allowedTypes.includes("thirdparty")
? "thirdparty"
: "person";
});
function isActive(tab) {
return type.value === tab;
}
function castDataByType() {
switch (radioType.value) {
case "person":
return castPerson.value.$data.person;
case "thirdparty":
let data = castThirdparty.value.$data.thirdparty;
if (data.address !== undefined && data.address !== null) {
data.address = { id: data.address.address_id };
} else {
data.address = null;
}
return data;
default:
throw Error("Invalid type of entity");
}
}
defineExpose({
castDataByType,
});
return data;
default:
throw Error("Invalid type of entity");
}
},
},
};
</script>
<style lang="css" scoped>
label {
cursor: pointer;
cursor: pointer;
}
</style>

View File

@@ -1,354 +1,328 @@
<template>
<a v-if="isDisplayBadge" @click="openModal">
<span class="chill-entity" :class="badgeType">
{{ buttonText }}<span v-if="isDead"> ()</span>
</span>
</a>
<a
v-else
class="btn btn-sm"
target="_blank"
:class="classAction"
:title="trans(titleAction)"
@click="openModal"
>
{{ buttonText }}<span v-if="isDead"> ()</span>
</a>
<teleport to="body">
<modal
v-if="modal.showModal"
:modal-dialog-class="modal.modalDialogClass"
@close="modal.showModal = false"
<a v-if="isDisplayBadge" @click="openModal">
<span class="chill-entity" :class="badgeType">
{{ buttonText }}<span v-if="isDead"> ()</span>
</span>
</a>
<a
v-else
class="btn btn-sm"
target="_blank"
:class="classAction"
:title="$t(titleAction)"
@click="openModal"
>
<template #header>
<h3 v-if="parent" class="modal-title">
{{ trans(titleModal, { q: parent.text }) }}
</h3>
<h3 v-else class="modal-title">
{{ trans(titleModal) }}
</h3>
</template>
{{ buttonText }}<span v-if="isDead"> ()</span>
</a>
<template #body v-if="type === 'person'">
<on-the-fly-person
:id="id"
:type="type"
:action="action"
ref="castPerson"
/>
<div v-if="hasResourceComment">
<h3>{{ trans(ONTHEFLY_RESOURCE_COMMENT_TITLE) }}</h3>
<blockquote class="chill-user-quote">
{{ parent.comment }}
</blockquote>
</div>
</template>
<teleport to="body">
<modal
v-if="modal.showModal"
:modal-dialog-class="modal.modalDialogClass"
@close="modal.showModal = false"
>
<template #header>
<h3 v-if="parent" class="modal-title">
{{ $t(titleModal, { q: parent.text }) }}
</h3>
<h3 v-else class="modal-title">
{{ $t(titleModal) }}
</h3>
</template>
<template #body v-else-if="type === 'thirdparty'">
<on-the-fly-thirdparty
:id="id"
:type="type"
:action="action"
ref="castThirdparty"
/>
<div v-if="hasResourceComment">
<h3>{{ trans(ONTHEFLY_RESOURCE_COMMENT_TITLE) }}</h3>
<blockquote class="chill-user-quote">
{{ parent.comment }}
</blockquote>
</div>
</template>
<template #body v-if="type === 'person'">
<on-the-fly-person
:id="id"
:type="type"
:action="action"
ref="castPerson"
/>
<div v-if="hasResourceComment">
<h3>{{ $t("onthefly.resource_comment_title") }}</h3>
<blockquote class="chill-user-quote">
{{ parent.comment }}
</blockquote>
</div>
</template>
<template #body v-else-if="parent">
<on-the-fly-thirdparty
:parent="parent"
:action="action"
type="thirdparty"
ref="castThirdparty"
/>
</template>
<template #body v-else-if="type === 'thirdparty'">
<on-the-fly-thirdparty
:id="id"
:type="type"
:action="action"
ref="castThirdparty"
/>
<div v-if="hasResourceComment">
<h3>{{ $t("onthefly.resource_comment_title") }}</h3>
<blockquote class="chill-user-quote">
{{ parent.comment }}
</blockquote>
</div>
</template>
<template #body v-else>
<on-the-fly-create
:action="action"
:allowed-types="allowedTypes"
:query="query"
ref="castNew"
/>
</template>
<template #body v-else-if="parent">
<on-the-fly-thirdparty
:parent="parent"
:action="action"
type="thirdparty"
ref="castThirdparty"
/>
</template>
<template #footer>
<a
v-if="action === 'show'"
:href="buildLocation(id, type)"
:title="trans(titleMessage)"
class="btn btn-show"
>{{ trans(buttonMessage) }}
</a>
<a v-else class="btn btn-save" @click="saveAction">
{{ trans(ACTION_SAVE) }}
</a>
</template>
</modal>
</teleport>
<template #body v-else>
<on-the-fly-create
:action="action"
:allowed-types="allowedTypes"
:query="query"
ref="castNew"
/>
</template>
<template #footer>
<a
v-if="action === 'show'"
:href="buildLocation(id, type)"
:title="$t(titleMessage)"
class="btn btn-show"
>{{ $t(buttonMessage) }}
</a>
<a v-else class="btn btn-save" @click="saveAction">
{{ $t("action.save") }}
</a>
</template>
</modal>
</teleport>
</template>
<script setup>
import { ref, computed, defineEmits, defineProps } from "vue";
<script>
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import OnTheFlyCreate from "./Create.vue";
import OnTheFlyPerson from "ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue";
import OnTheFlyThirdparty from "ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdParty.vue";
import {
trans,
ACTION_SHOW,
ACTION_EDIT,
ACTION_CREATE,
ACTION_ADDCONTACT,
ONTHEFLY_CREATE_TITLE_DEFAULT,
ONTHEFLY_CREATE_TITLE_PERSON,
ONTHEFLY_CREATE_TITLE_THIRDPARTY,
ONTHEFLY_SHOW_PERSON,
ONTHEFLY_SHOW_THIRDPARTY,
ONTHEFLY_EDIT_PERSON,
ONTHEFLY_EDIT_THIRDPARTY,
ONTHEFLY_ADDCONTACT_TITLE,
ACTION_REDIRECT_PERSON,
ACTION_REDIRECT_THIRDPARTY,
ONTHEFLY_SHOW_FILE_PERSON,
ONTHEFLY_SHOW_FILE_THIRDPARTY,
ONTHEFLY_SHOW_FILE_DEFAULT,
ONTHEFLY_RESOURCE_COMMENT_TITLE,
ACTION_SAVE,
} from "translator";
const props = defineProps({
type: String,
id: [String, Number],
action: String,
buttonText: String,
displayBadge: Boolean,
isDead: Boolean,
parent: Object,
allowedTypes: Array,
query: String,
});
export default {
name: "OnTheFly",
components: {
Modal,
OnTheFlyPerson,
OnTheFlyThirdparty,
OnTheFlyCreate,
},
props: [
"type",
"id",
"action",
"buttonText",
"displayBadge",
"isDead",
"parent",
"allowedTypes",
"query",
],
emits: ["saveFormOnTheFly"],
data() {
return {
modal: {
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl",
},
};
},
computed: {
hasResourceComment() {
return (
typeof this.parent !== "undefined" &&
this.parent !== null &&
this.action === "show" &&
this.parent.type === "accompanying_period_resource" &&
this.parent.comment !== null &&
this.parent.comment !== ""
);
},
classAction() {
switch (this.action) {
case "show":
return "btn-show";
case "edit":
return "btn-update";
case "create":
return "btn-create";
case "addContact":
return "btn-tpchild";
default:
return "";
}
},
titleAction() {
switch (this.action) {
case "show":
return "action.show";
case "edit":
return "action.edit";
case "create":
return "action.create";
case "addContact":
return "action.addContact";
default:
return "";
}
},
titleCreate() {
if (typeof this.allowedTypes === "undefined") {
return "onthefly.create.title.default";
}
return this.allowedTypes.every((t) => t === "person")
? "onthefly.create.title.person"
: this.allowedTypes.every((t) => t === "thirdparty")
? "onthefly.create.title.thirdparty"
: "onthefly.create.title.default";
},
titleModal() {
switch (this.action) {
case "show":
return "onthefly.show." + this.type;
case "edit":
return "onthefly.edit." + this.type;
case "create":
return this.titleCreate;
case "addContact":
return "onthefly.addContact.title";
default:
return "";
}
},
titleMessage() {
switch (this.type) {
case "person":
return "action.redirect." + this.type;
case "thirdparty":
return "action.redirect." + this.type;
default:
return "";
}
},
buttonMessage() {
switch (this.type) {
case "person":
return "onthefly.show.file_" + this.type;
case "thirdparty":
return "onthefly.show.file_" + this.type;
}
},
isDisplayBadge() {
return this.displayBadge === true && this.buttonText !== null;
},
badgeType() {
return "entity-" + this.type + " badge-" + this.type;
},
getReturnPath() {
return `?returnPath=${window.location.pathname}${window.location.search}${window.location.hash}`;
},
},
methods: {
closeModal() {
this.modal.showModal = false;
},
openModal() {
// console.log('## OPEN ON THE FLY MODAL');
// console.log('## type:', this.type, ', action:', this.action);
this.modal.showModal = true;
this.$nextTick(function () {
//this.$refs.search.focus();
});
},
changeActionTo(action) {
this.$data.action = action;
},
saveAction() {
// console.log('saveAction button: create/edit action with', this.type);
let type = this.type,
data = {};
switch (type) {
case "person":
data = this.$refs.castPerson.$data.person;
console.log("person data are", data);
break;
const emit = defineEmits(["saveFormOnTheFly"]);
case "thirdparty":
data = this.$refs.castThirdparty.$data.thirdparty;
/* never executed ? */
break;
const modal = ref({
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl",
});
const castPerson = ref();
const castThirdparty = ref();
const castNew = ref();
const hasResourceComment = computed(() => {
return (
typeof props.parent !== "undefined" &&
props.parent !== null &&
props.action === "show" &&
props.parent.type === "accompanying_period_resource" &&
props.parent.comment !== null &&
props.parent.comment !== ""
);
});
const classAction = computed(() => {
switch (props.action) {
case "show":
return "btn-show";
case "edit":
return "btn-update";
case "create":
return "btn-create";
case "addContact":
return "btn-tpchild";
default:
return "";
}
});
const titleAction = computed(() => {
switch (props.action) {
case "show":
return ACTION_SHOW;
case "edit":
return ACTION_EDIT;
case "create":
return ACTION_CREATE;
case "addContact":
return ACTION_ADDCONTACT;
default:
return "";
}
});
const titleCreate = computed(() => {
if (typeof props.allowedTypes === "undefined") {
return ONTHEFLY_CREATE_TITLE_DEFAULT;
}
return props.allowedTypes.every((t) => t === "person")
? ONTHEFLY_CREATE_TITLE_PERSON
: props.allowedTypes.every((t) => t === "thirdparty")
? ONTHEFLY_CREATE_TITLE_THIRDPARTY
: ONTHEFLY_CREATE_TITLE_DEFAULT;
});
const titleModal = computed(() => {
switch (props.action) {
case "show":
if (props.type == "person") {
return ONTHEFLY_SHOW_PERSON;
} else if (props.type == "thirdparty") {
return ONTHEFLY_SHOW_THIRDPARTY;
}
break;
case "edit":
if (props.type == "person") {
return ONTHEFLY_EDIT_PERSON;
} else if (props.type == "thirdparty") {
return ONTHEFLY_EDIT_THIRDPARTY;
}
break;
case "create":
return titleCreate.value;
case "addContact":
return ONTHEFLY_ADDCONTACT_TITLE;
default:
break;
}
return "";
});
const titleMessage = computed(() => {
switch (props.type) {
case "person":
return ACTION_REDIRECT_PERSON;
case "thirdparty":
return ACTION_REDIRECT_THIRDPARTY;
default:
return "";
}
});
const buttonMessage = computed(() => {
switch (props.type) {
case "person":
return ONTHEFLY_SHOW_FILE_PERSON;
case "thirdparty":
return ONTHEFLY_SHOW_FILE_THIRDPARTY;
default:
return ONTHEFLY_SHOW_FILE_DEFAULT;
}
});
const isDisplayBadge = computed(() => {
return props.displayBadge === true && props.buttonText !== null;
});
const badgeType = computed(() => {
return "entity-" + props.type + " badge-" + props.type;
});
const getReturnPath = computed(() => {
return `?returnPath=${window.location.pathname}${window.location.search}${window.location.hash}`;
});
function closeModal() {
modal.value.showModal = false;
}
function openModal() {
modal.value.showModal = true;
}
function changeActionTo(action) {
console.log(action);
// Not reactive in setup, but you can emit or use a ref if needed
}
function saveAction() {
let type = props.type,
data = {};
switch (type) {
case "person":
data = castPerson.value?.$data.person;
break;
case "thirdparty":
data = castThirdparty.value?.$data.thirdparty;
break;
default:
if (typeof props.type === "undefined") {
if (props.action === "addContact") {
type = "thirdparty";
data = castThirdparty.value?.$data.thirdparty;
data.parent = {
type: "thirdparty",
id: props.parent.id,
};
data.civility =
data.civility !== null
? {
type: "chill_main_civility",
id: data.civility.id,
}
: null;
data.profession = data.profession !== "" ? data.profession : "";
} else {
type = castNew.value.radioType;
data = castNew.value.castDataByType();
if (typeof data.civility !== "undefined" && null !== data.civility) {
data.civility =
data.civility !== null
? {
type: "chill_main_civility",
id: data.civility.id,
}
: null;
}
if (
typeof data.profession !== "undefined" &&
"" !== data.profession
) {
data.profession = data.profession !== "" ? data.profession : "";
}
}
} else {
throw "error with object type";
}
}
emit("saveFormOnTheFly", { type: type, data: data });
}
function buildLocation(id, type) {
if (type === "person") {
return encodeURI(`/fr/person/${id}/general${getReturnPath.value}`);
} else if (type === "thirdparty") {
return encodeURI(`/fr/3party/3party/${id}/view${getReturnPath.value}`);
}
}
defineExpose({
openModal,
closeModal,
changeActionTo,
saveAction,
castPerson,
castThirdparty,
castNew,
hasResourceComment,
modal,
isDisplayBadge,
Modal,
});
default:
if (typeof this.type === "undefined") {
// action=create or addContact
// console.log('will rewrite data');
if (this.action === "addContact") {
type = "thirdparty";
data = this.$refs.castThirdparty.$data.thirdparty;
// console.log('data original', data);
data.parent = {
type: "thirdparty",
id: this.parent.id,
};
data.civility =
data.civility !== null
? {
type: "chill_main_civility",
id: data.civility.id,
}
: null;
data.profession =
data.profession !== "" ? data.profession : "";
} else {
type = this.$refs.castNew.radioType;
data = this.$refs.castNew.castDataByType();
// console.log('type', type);
if (
typeof data.civility !== "undefined" &&
null !== data.civility
) {
data.civility =
data.civility !== null
? {
type: "chill_main_civility",
id: data.civility.id,
}
: null;
}
if (
typeof data.profession !== "undefined" &&
"" !== data.profession
) {
data.profession =
data.profession !== ""
? data.profession
: "";
}
// console.log('onthefly data', data);
}
} else {
throw "error with object type";
}
}
// pass datas to parent
this.$emit("saveFormOnTheFly", { type: type, data: data });
},
buildLocation(id, type) {
if (type === "person") {
// TODO i18n
return encodeURI(
`/fr/person/${id}/general${this.getReturnPath}`,
);
} else if (type === "thirdparty") {
return encodeURI(
`/fr/3party/3party/${id}/view${this.getReturnPath}`,
);
}
},
},
};
</script>
<style lang="css" scoped>
a {
cursor: pointer;
cursor: pointer;
}
/* .btn-add-contact {

View File

@@ -1,327 +1,122 @@
<template>
<div class="grey-card">
<ul :class="listClasses" v-if="picked.length > 0 && displayPicked">
<li
v-for="p in picked"
@click="removeEntity(p)"
:key="p === 'me' ? 'me' : p.type + p.id"
>
<span
v-if="'me' === p"
class="chill_denomination current-user updatedBy"
>{{ trans(USER_CURRENT_USER) }}</span
>
<span
v-else
:class="getBadgeClass(p)"
class="chill_denomination"
:style="getBadgeStyle(p)"
>
{{ p.text }}
</span>
</li>
<ul :class="listClasses" v-if="picked.length && displayPicked">
<li v-for="p in picked" @click="removeEntity(p)" :key="p.type + p.id">
<span
v-if="'me' === p"
class="chill_denomination current-user updatedBy"
>{{ trans(USER_CURRENT_USER) }}</span
>
<span v-else class="chill_denomination">{{ p.text }}</span>
</li>
</ul>
<ul class="record_actions">
<li v-if="isCurrentUserPicker" class="btn btn-sm btn-misc">
<label class="flex items-center gap-2">
<input
:checked="isMePicked"
ref="itsMeCheckbox"
:type="multiple ? 'checkbox' : 'radio'"
@change="selectItsMe($event as InputEvent)"
/>
{{ trans(USER_CURRENT_USER) }}
</label>
</li>
<li class="add-persons">
<add-persons
:options="addPersonsOptions"
:key="uniqid"
:buttonTitle="translatedListOfTypes"
:modalTitle="translatedListOfTypes"
@addNewPersons="addNewEntity"
>
</add-persons>
</li>
<li v-if="isCurrentUserPicker" class="btn btn-sm btn-misc">
<label class="flex items-center gap-2">
<input
:checked="picked.indexOf('me') >= 0 ? true : null"
ref="itsMeCheckbox"
:type="multiple ? 'checkbox' : 'radio'"
@change="selectItsMe"
/>
{{ trans(USER_CURRENT_USER) }}
</label>
</li>
<li class="add-persons">
<add-persons
:options="addPersonsOptions"
:key="uniqid"
:buttonTitle="translatedListOfTypes"
:modalTitle="translatedListOfTypes"
ref="addPersons"
@addNewPersons="addNewEntity"
>
</add-persons>
</li>
</ul>
<ul class="badge-suggest add-items inline" style="float: right">
<li
v-for="s in suggested"
:key="s.type + s.id"
@click="addNewSuggested(s)"
>
<span :class="getBadgeClass(s)" :style="getBadgeStyle(s)">
{{ s.text }}
</span>
</li>
<ul class="list-suggest add-items inline">
<li v-for="s in suggested" :key="s.id" @click="addNewSuggested(s)">
<span>{{ s.text }}</span>
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import {
ref,
computed,
defineProps,
defineEmits,
defineComponent,
withDefaults,
} from "vue";
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
import {
Entities,
EntitiesOrMe,
EntityType,
SearchOptions,
} from "ChillPersonAssets/types";
import {
PICK_ENTITY_MODAL_TITLE,
PICK_ENTITY_USER,
PICK_ENTITY_USER_GROUP,
PICK_ENTITY_PERSON,
PICK_ENTITY_THIRDPARTY,
USER_CURRENT_USER,
trans,
} from "translator";
import { addNewEntities } from "ChillMainAssets/types";
<script setup>
import { ref, computed } from "vue";
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue"; // eslint-disable-line
import { appMessages } from "./i18n";
import { trans, USER_CURRENT_USER } from "translator";
defineComponent({
components: {
AddPersons,
},
const props = defineProps({
multiple: Boolean,
types: Array,
picked: Array,
uniqid: String,
removableIfSet: { type: Boolean, default: true },
displayPicked: { type: Boolean, default: true },
suggested: { type: Array, default: () => [] },
label: String,
isCurrentUserPicker: { type: Boolean, default: false },
});
const props = withDefaults(
defineProps<{
multiple: boolean;
types: EntityType[];
picked: EntitiesOrMe[];
uniqid: string;
removableIfSet?: boolean;
displayPicked?: boolean;
suggested?: Entities[];
label?: string;
isCurrentUserPicker?: boolean;
}>(),
{ isCurrentUserPicker: false, displayPicked: true, removableIfSet: true },
);
const emits = defineEmits<{
(e: "addNewEntity", payload: { entity: EntitiesOrMe }): void;
(e: "removeEntity", payload: { entity: EntitiesOrMe }): void;
(e: "addNewEntityProcessEnded"): void;
}>();
const emit = defineEmits([
"addNewEntity",
"removeEntity",
"addNewEntityProcessEnded",
]);
const itsMeCheckbox = ref<null | HTMLInputElement>(null);
const addPersons = ref();
const itsMeCheckbox = ref(null);
const addPersons = ref(null);
const addPersonsOptions = computed(
() =>
({
uniq: !props.multiple,
type: props.types,
priority: null,
button: {
size: "btn-sm",
class: "btn-submit",
},
}) as SearchOptions,
);
const isMePicked = computed<boolean>(() => props.picked.indexOf("me") >= 0);
const addPersonsOptions = computed(() => ({
uniq: !props.multiple,
type: props.types,
priority: null,
button: { size: "btn-sm", class: "btn-submit" },
}));
const translatedListOfTypes = computed(() => {
if (props.label !== undefined && props.label !== "") {
return props.label;
}
const translatedTypes = props.types.map((type: EntityType) => {
switch (type) {
case "user":
return trans(PICK_ENTITY_USER, {
count: props.multiple ? 2 : 1,
});
case "person":
return trans(PICK_ENTITY_PERSON, {
count: props.multiple ? 2 : 1,
});
case "thirdparty":
return trans(PICK_ENTITY_THIRDPARTY, {
count: props.multiple ? 2 : 1,
});
case "user_group":
return trans(PICK_ENTITY_USER_GROUP, {
count: props.multiple ? 2 : 1,
});
}
});
return `${trans(PICK_ENTITY_MODAL_TITLE, {
count: props.multiple ? 2 : 1,
})} ${translatedTypes.join(", ")}`;
if (props.label) return props.label;
let trans = props.types.map((t) =>
props.multiple
? appMessages.fr.pick_entity[t].toLowerCase()
: appMessages.fr.pick_entity[t + "_one"].toLowerCase(),
);
return props.multiple
? appMessages.fr.pick_entity.modal_title + trans.join(", ")
: appMessages.fr.pick_entity.modal_title_one + trans.join(", ");
});
const listClasses = computed(() => ({
"badge-suggest": true,
"remove-items": props.removableIfSet,
inline: true,
"list-suggest": true,
"remove-items": props.removableIfSet,
}));
const selectItsMe = (event: InputEvent): void => {
const target = event.target as HTMLInputElement;
if (target.checked) {
addNewSuggested("me");
} else {
removeEntity("me");
}
const selectItsMe = (event) =>
event.target.checked ? addNewSuggested("me") : removeEntity("me");
const addNewSuggested = (entity) => {
emit("addNewEntity", { entity });
};
function addNewSuggested(entity: EntitiesOrMe) {
emits("addNewEntity", { entity });
}
function addNewEntity({ selected }: addNewEntities) {
Object.values(selected).forEach((item) => {
emits("addNewEntity", { entity: item.result });
});
addPersons.value?.resetSearch();
emits("addNewEntityProcessEnded");
}
const removeEntity = (entity: EntitiesOrMe): void => {
if (!props.removableIfSet) return;
if (entity === "me" && itsMeCheckbox.value) {
itsMeCheckbox.value.checked = false;
}
emits("removeEntity", { entity });
const addNewEntity = ({ selected, modal }) => {
selected.forEach((item) => emit("addNewEntity", { entity: item.result }));
addPersons.value?.resetSearch();
modal.showModal = false;
emit("addNewEntityProcessEnded");
};
function getBadgeClass(entities: Entities) {
if (entities.type !== "user_group") {
return entities.type;
}
return "";
}
function getBadgeStyle(entities: Entities) {
if (entities.type === "user_group") {
return [
`ul.badge-suggest li > span {
color: ${entities.foregroundColor}!important;
border-bottom-color: ${entities.backgroundColor};
}`,
];
}
return [];
}
const removeEntity = (entity) => {
if (!props.removableIfSet) return;
if (entity === "me" && itsMeCheckbox.value) {
itsMeCheckbox.value.checked = false;
}
emit("removeEntity", { entity });
};
</script>
<style lang="scss" scoped>
.grey-card {
background: #f8f9fa;
border-radius: 8px;
padding: 1.5rem;
min-height: 160px;
}
.btn-check:checked + .btn,
:not(.btn-check) + .btn:active,
.btn:first-child:active,
.btn.active,
.btn.show {
color: white;
box-shadow: 0 0 0 0.2rem var(--bs-chill-green);
outline: 0;
}
.as-user-group {
display: inline-block;
}
ul.badge-suggest {
list-style-type: none;
padding-left: 0;
margin-bottom: 0px;
}
ul.badge-suggest li > span {
white-space: normal;
text-align: start;
margin-bottom: 3px;
}
ul.badge-suggest.inline li {
display: inline-block;
margin-right: 0.2em;
}
ul.badge-suggest.add-items li {
position: relative;
}
ul.badge-suggest.add-items li span {
cursor: pointer;
padding-left: 2rem;
}
ul.badge-suggest.add-items li span:hover {
color: #ced4da;
}
ul.badge-suggest.add-items li > span:before {
font: normal normal normal 13px ForkAwesome;
margin-right: 1.8em;
content: "\f067";
color: var(--bs-success);
position: absolute;
display: block;
top: 50%;
left: 0.75rem;
-webkit-transform: translateY(-50%);
-moz-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
}
ul.badge-suggest.remove-items li {
position: relative;
}
ul.badge-suggest.remove-items li span {
cursor: pointer;
padding-left: 2rem;
}
ul.badge-suggest.remove-items li span:hover {
color: #ced4da;
}
ul.badge-suggest.remove-items li > span:before {
font: normal normal normal 13px ForkAwesome;
margin-right: 1.8em;
content: "\f1f8";
color: var(--bs-danger);
position: absolute;
display: block;
top: 50%;
left: 0.75rem;
-webkit-transform: translateY(-50%);
-moz-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
}
ul.badge-suggest li > span {
margin: 0.2rem 0.1rem;
display: inline-block;
padding: 0 1em 0 2.2em !important;
background-color: #fff;
border: 1px solid #dee2e6;
border-bottom-width: 3px;
border-bottom-style: solid;
border-radius: 6px;
font-size: 0.75em;
font-weight: 700;
}
ul.badge-suggest li > span.person {
border-bottom-color: #43b29d;
}
ul.badge-suggest li > span.thirdparty {
border-bottom-color: rgb(198.9, 72, 98.1);
}
.current-user {
color: var(--bs-body-color);
background-color: var(--bs-chill-l-gray) !important;
color: var(--bs-body-color);
background-color: var(--bs-chill-l-gray) !important;
}
</style>

View File

@@ -1,27 +1,27 @@
<template>
<div class="PickPostalCode">
<vue-multiselect
id="citySelector"
@search-change="listenInputSearch"
ref="citySelector"
v-model="internalPicked"
@select="selectCity"
@remove="remove"
name=""
track-by="id"
label="value"
:custom-label="transName"
:placeholder="$t('select_city')"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
:taggable="true"
:multiple="false"
:internal-search="false"
:loading="isLoading"
:options="cities"
/>
</div>
<div class="PickPostalCode">
<vue-multiselect
id="citySelector"
@search-change="listenInputSearch"
ref="citySelector"
v-model="internalPicked"
@select="selectCity"
@remove="remove"
name=""
track-by="id"
label="value"
:custom-label="transName"
:placeholder="$t('select_city')"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
:taggable="true"
:multiple="false"
:internal-search="false"
:loading="isLoading"
:options="cities"
/>
</div>
</template>
<script lang="js">
@@ -29,77 +29,81 @@ import VueMultiselect from "vue-multiselect";
import { searchCities } from "./api";
export default {
components: {
VueMultiselect,
},
data() {
return {
cities: [],
internalPicked: null,
isLoading: false,
abortControllers: [],
};
},
emits: ["pickCity", "removeCity"],
props: {
picked: {
type: Object,
required: false,
default: null,
components: {
VueMultiselect,
},
country: {
type: Object,
required: false,
default: null,
data() {
return {
cities: [],
internalPicked: null,
isLoading: false,
abortControllers: [],
};
},
},
mounted() {
if (this.picked !== null) {
this.internalPicked = this.picked;
this.cities.push(this.picked);
}
},
methods: {
transName(value) {
return value.code && value.name ? `${value.name} (${value.code})` : "";
emits: ["pickCity", "removeCity"],
props: {
picked: {
type: Object,
required: false,
default: null,
},
country: {
type: Object,
required: false,
default: null,
},
},
selectCity(city) {
this.$emit("selectCity", city);
mounted() {
if (this.picked !== null) {
this.internalPicked = this.picked;
this.cities.push(this.picked);
}
},
listenInputSearch(query) {
if (query.length <= 2) {
return;
}
methods: {
transName(value) {
return value.code && value.name
? `${value.name} (${value.code})`
: "";
},
selectCity(city) {
this.$emit("selectCity", city);
},
listenInputSearch(query) {
if (query.length <= 2) {
return;
}
let c = this.abortControllers.pop();
let c = this.abortControllers.pop();
while (typeof c !== "undefined") {
c.abort();
c = this.abortControllers.pop();
}
while (typeof c !== "undefined") {
c.abort();
c = this.abortControllers.pop();
}
this.isLoading = true;
let controller = new AbortController();
this.abortControllers.push(controller);
this.isLoading = true;
let controller = new AbortController();
this.abortControllers.push(controller);
searchCities(query, this.country, controller)
.then((newCities) => {
this.cities = this.cities.filter((city) => city.id === this.picked);
newCities.forEach((item) => {
this.cities.push(item);
});
this.isLoading = false;
searchCities(query, this.country, controller)
.then((newCities) => {
this.cities = this.cities.filter(
(city) => city.id === this.picked,
);
newCities.forEach((item) => {
this.cities.push(item);
});
this.isLoading = false;
return Promise.resolve();
})
.catch((error) => {
console.log(error); //TODO better error handling
this.isLoading = false;
});
return Promise.resolve();
})
.catch((error) => {
console.log(error); //TODO better error handling
this.isLoading = false;
});
},
remove(item) {
this.$emit("removeCity", item);
},
},
remove(item) {
this.$emit("removeCity", item);
},
},
};
</script>

View File

@@ -2,15 +2,17 @@
import GenerateButton from "ChillMainAssets/vuejs/SavedExportButtons/Component/GenerateButton.vue";
interface SavedExportButtonsConfig {
savedExportUuid: string;
savedExportAlias: string;
savedExportUuid: string;
savedExportAlias: string;
}
const props = defineProps<SavedExportButtonsConfig>();
</script>
<template>
<generate-button :saved-export-uuid="props.savedExportUuid"></generate-button>
<generate-button
:saved-export-uuid="props.savedExportUuid"
></generate-button>
</template>
<style scoped lang="scss"></style>

View File

@@ -1,15 +1,15 @@
<script setup lang="ts">
import {
trans,
SAVED_EXPORT_EXECUTE,
EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING_SHORT,
EXPORT_GENERATION_TOO_MANY_RETRIES,
EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT,
EXPORT_GENERATION_EXPORT_READY,
trans,
SAVED_EXPORT_EXECUTE,
EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING_SHORT,
EXPORT_GENERATION_TOO_MANY_RETRIES,
EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT,
EXPORT_GENERATION_EXPORT_READY,
} from "translator";
import {
fetchExportGenerationStatus,
generateFromSavedExport,
fetchExportGenerationStatus,
generateFromSavedExport,
} from "ChillMainAssets/lib/api/export";
import { computed, ref } from "vue";
import { ExportGeneration } from "ChillMainAssets/types";
@@ -19,50 +19,50 @@ import { useToast } from "vue-toast-notification";
import "vue-toast-notification/dist/theme-sugar.css";
interface SavedExportButtonGenerateConfig {
savedExportUuid: string;
savedExportUuid: string;
}
const props = defineProps<SavedExportButtonGenerateConfig>();
const emits = defineEmits<{
(e: "generate");
(e: "generate");
}>();
const toast = useToast();
const exportGeneration = ref<ExportGeneration | null>(null);
const status = computed<StoredObjectStatus | "inactive">(
() => exportGeneration.value?.status ?? "inactive",
() => exportGeneration.value?.status ?? "inactive",
);
const storedObject = computed<null | StoredObject>(() => {
if (exportGeneration.value === null) {
return null;
}
if (exportGeneration.value === null) {
return null;
}
return exportGeneration.value?.storedObject;
return exportGeneration.value?.storedObject;
});
const isInactive = computed<boolean>(() => status.value === "inactive");
const isPending = computed<boolean>(() => status.value === "pending");
const isFetching = computed<boolean>(
() => tryiesForReady.value < maxTryiesForReady,
() => tryiesForReady.value < maxTryiesForReady,
);
const isReady = computed<boolean>(() => status.value === "ready");
const isFailure = computed<boolean>(() => status.value === "failure");
const filename = computed<string>(() => {
if (null === exportGeneration.value) {
return "";
}
if (null === exportGeneration.value) {
return "";
}
return `${exportGeneration.value?.storedObject.title}-${exportGeneration.value?.createdAt?.datetime8601}`;
return `${exportGeneration.value?.storedObject.title}-${exportGeneration.value?.createdAt?.datetime8601}`;
});
const externalDownloadLink = computed<string>(
() => `/fr/main/export-generation/${exportGeneration.value?.id}/wait`,
() => `/fr/main/export-generation/${exportGeneration.value?.id}/wait`,
);
const classes = computed<Record<string, boolean>>(() => {
return {};
return {};
});
const buttonClasses = computed<Record<string, boolean>>(() => {
return { btn: true, "btn-outline-primary": true };
return { btn: true, "btn-outline-primary": true };
});
/**
@@ -76,109 +76,111 @@ let tryiesForReady = ref<number>(0);
const maxTryiesForReady = 120;
const checkForReady = function (): void {
if (
"ready" === status.value ||
"empty" === status.value ||
"failure" === status.value ||
// stop reloading if the page stays opened for a long time
tryiesForReady.value > maxTryiesForReady
) {
return;
}
if (
"ready" === status.value ||
"empty" === status.value ||
"failure" === status.value ||
// stop reloading if the page stays opened for a long time
tryiesForReady.value > maxTryiesForReady
) {
return;
}
tryiesForReady.value = tryiesForReady.value + 1;
setTimeout(
onObjectNewStatusCallback,
tryiesForReady.value < 10 ? 1500 : 5000,
);
tryiesForReady.value = tryiesForReady.value + 1;
setTimeout(
onObjectNewStatusCallback,
tryiesForReady.value < 10 ? 1500 : 5000,
);
};
const onExportGenerationSuccess = function (): void {
toast.success(trans(EXPORT_GENERATION_EXPORT_READY));
toast.success(trans(EXPORT_GENERATION_EXPORT_READY));
};
const onObjectNewStatusCallback = async function (): Promise<void> {
if (null === exportGeneration.value) {
checkForReady();
return Promise.resolve();
}
const newExportGeneration = await fetchExportGenerationStatus(
exportGeneration.value?.id,
);
if (newExportGeneration.status !== exportGeneration.value.status) {
if (newExportGeneration.status === "ready") {
onExportGenerationSuccess();
if (null === exportGeneration.value) {
checkForReady();
return Promise.resolve();
}
}
exportGeneration.value = newExportGeneration;
const newExportGeneration = await fetchExportGenerationStatus(
exportGeneration.value?.id,
);
if (newExportGeneration.status !== exportGeneration.value.status) {
if (newExportGeneration.status === "ready") {
onExportGenerationSuccess();
}
}
exportGeneration.value = newExportGeneration;
if (isPending.value) {
checkForReady();
return Promise.resolve();
}
if (isPending.value) {
checkForReady();
return Promise.resolve();
}
return Promise.resolve();
};
const onClickGenerate = async (): Promise<void> => {
emits("generate");
exportGeneration.value = await generateFromSavedExport(props.savedExportUuid);
emits("generate");
exportGeneration.value = await generateFromSavedExport(
props.savedExportUuid,
);
onObjectNewStatusCallback();
onObjectNewStatusCallback();
return Promise.resolve();
return Promise.resolve();
};
</script>
<template>
<button
v-if="isInactive"
:class="buttonClasses"
type="button"
@click="onClickGenerate"
>
<i class="fa fa-cog"></i> {{ trans(SAVED_EXPORT_EXECUTE) }}
</button>
<template v-if="isPending && isFetching">
<span class="btn">
<i class="fa fa-cog fa-spin fa-fw"></i>
<span class="pending-message">{{
trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING_SHORT)
}}</span>
<a :href="externalDownloadLink" class="externalDownloadLink">
<i class="bi bi-box-arrow-up-right"></i>
</a>
</span>
</template>
<div v-if="isPending && !isFetching" :class="buttonClasses">
<span>{{ trans(EXPORT_GENERATION_TOO_MANY_RETRIES) }}</span>
</div>
<download-button
v-else-if="isReady && storedObject?.currentVersion !== null"
:classes="buttonClasses"
:stored-object="storedObject"
:at-version="storedObject?.currentVersion"
:filename="filename"
></download-button>
<div v-else-if="isFailure" :class="classes">
<span class="btn">
<i class="bi bi-exclamation-triangle"></i>
<span class="pending-message">{{
trans(EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT)
}}</span>
</span>
</div>
<button
v-if="isInactive"
:class="buttonClasses"
type="button"
@click="onClickGenerate"
>
<i class="fa fa-cog"></i> {{ trans(SAVED_EXPORT_EXECUTE) }}
</button>
<template v-if="isPending && isFetching">
<span class="btn">
<i class="fa fa-cog fa-spin fa-fw"></i>
<span class="pending-message">{{
trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING_SHORT)
}}</span>
<a :href="externalDownloadLink" class="externalDownloadLink">
<i class="bi bi-box-arrow-up-right"></i>
</a>
</span>
</template>
<div v-if="isPending && !isFetching" :class="buttonClasses">
<span>{{ trans(EXPORT_GENERATION_TOO_MANY_RETRIES) }}</span>
</div>
<download-button
v-else-if="isReady && storedObject?.currentVersion !== null"
:classes="buttonClasses"
:stored-object="storedObject"
:at-version="storedObject?.currentVersion"
:filename="filename"
></download-button>
<div v-else-if="isFailure" :class="classes">
<span class="btn">
<i class="bi bi-exclamation-triangle"></i>
<span class="pending-message">{{
trans(EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT)
}}</span>
</span>
</div>
</template>
<style scoped lang="scss">
.pending-message {
font-style: italic;
font-style: italic;
}
.externalDownloadLink {
margin-left: 1rem;
margin-left: 1rem;
}
</style>

View File

@@ -3,11 +3,11 @@ import { createApp } from "vue";
import App from "./App.vue";
const buttons = document.querySelectorAll<HTMLDivElement>(
"[data-generate-export-button]",
"[data-generate-export-button]",
);
buttons.forEach((button) => {
const savedExportUuid = button.dataset.savedExportUuid as string;
const savedExportUuid = button.dataset.savedExportUuid as string;
createApp(App, { savedExportUuid, savedExportAlias: "" }).mount(button);
createApp(App, { savedExportUuid, savedExportAlias: "" }).mount(button);
});

View File

@@ -7,17 +7,17 @@ import AttachmentList from "ChillMainAssets/vuejs/WorkflowAttachment/Component/A
import { GenericDoc } from "ChillDocStoreAssets/types";
interface AppConfig {
workflowId: number;
accompanyingPeriodId: number;
attachments: WorkflowAttachment[];
workflowId: number;
accompanyingPeriodId: number;
attachments: WorkflowAttachment[];
}
const emit = defineEmits<{
(
e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
(e: "removeAttachment", payload: { attachment: WorkflowAttachment }): void;
(
e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
(e: "removeAttachment", payload: { attachment: WorkflowAttachment }): void;
}>();
type PickGenericModalType = InstanceType<typeof PickGenericDocModal>;
@@ -26,49 +26,49 @@ const pickDocModal = useTemplateRef<PickGenericModalType>("pickDocModal");
const props = defineProps<AppConfig>();
const attachedGenericDoc = computed<GenericDocForAccompanyingPeriod[]>(
() =>
props.attachments
.map((a: WorkflowAttachment) => a.genericDoc)
.filter(
(g: GenericDoc | null) => g !== null,
) as GenericDocForAccompanyingPeriod[],
() =>
props.attachments
.map((a: WorkflowAttachment) => a.genericDoc)
.filter(
(g: GenericDoc | null) => g !== null,
) as GenericDocForAccompanyingPeriod[],
);
const openModal = function () {
pickDocModal.value?.openModal();
pickDocModal.value?.openModal();
};
const onPickGenericDoc = ({
genericDoc,
genericDoc,
}: {
genericDoc: GenericDocForAccompanyingPeriod;
genericDoc: GenericDocForAccompanyingPeriod;
}) => {
emit("pickGenericDoc", { genericDoc });
emit("pickGenericDoc", { genericDoc });
};
const onRemoveAttachment = (payload: { attachment: WorkflowAttachment }) => {
emit("removeAttachment", payload);
emit("removeAttachment", payload);
};
</script>
<template>
<pick-generic-doc-modal
:accompanying-period-id="props.accompanyingPeriodId"
:to-remove="attachedGenericDoc"
ref="pickDocModal"
@pickGenericDoc="onPickGenericDoc"
></pick-generic-doc-modal>
<attachment-list
:attachments="props.attachments"
@removeAttachment="onRemoveAttachment"
></attachment-list>
<ul class="record_actions">
<li>
<button type="button" class="btn btn-create" @click="openModal">
Ajouter une pièce jointe
</button>
</li>
</ul>
<pick-generic-doc-modal
:accompanying-period-id="props.accompanyingPeriodId"
:to-remove="attachedGenericDoc"
ref="pickDocModal"
@pickGenericDoc="onPickGenericDoc"
></pick-generic-doc-modal>
<attachment-list
:attachments="props.attachments"
@removeAttachment="onRemoveAttachment"
></attachment-list>
<ul class="record_actions">
<li>
<button type="button" class="btn btn-create" @click="openModal">
Ajouter une pièce jointe
</button>
</li>
</ul>
</template>
<style scoped lang="scss"></style>

View File

@@ -4,49 +4,49 @@ import GenericDocItemBox from "ChillMainAssets/vuejs/WorkflowAttachment/Componen
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
interface AttachmentListProps {
attachments: WorkflowAttachment[];
attachments: WorkflowAttachment[];
}
const emit = defineEmits<{
// eslint-disable-next-line @typescript-eslint/prefer-function-type
(e: "removeAttachment", payload: { attachment: WorkflowAttachment }): void;
// eslint-disable-next-line @typescript-eslint/prefer-function-type
(e: "removeAttachment", payload: { attachment: WorkflowAttachment }): void;
}>();
const props = defineProps<AttachmentListProps>();
</script>
<template>
<p
v-if="props.attachments.length === 0"
class="chill-no-data-statement text-center"
>
Aucune pièce jointe
</p>
<!-- TODO translate -->
<div else class="flex-table">
<div v-for="a in props.attachments" :key="a.id" class="item-bloc">
<generic-doc-item-box
v-if="a.genericDoc !== null"
:generic-doc="a.genericDoc"
></generic-doc-item-box>
<div class="item-row separator">
<ul class="record_actions">
<li v-if="a.genericDoc?.storedObject !== null">
<document-action-buttons-group
:stored-object="a.genericDoc.storedObject"
></document-action-buttons-group>
</li>
<li>
<button
type="button"
class="btn btn-delete"
@click="emit('removeAttachment', { attachment: a })"
></button>
</li>
</ul>
</div>
<p
v-if="props.attachments.length === 0"
class="chill-no-data-statement text-center"
>
Aucune pièce jointe
</p>
<!-- TODO translate -->
<div else class="flex-table">
<div v-for="a in props.attachments" :key="a.id" class="item-bloc">
<generic-doc-item-box
v-if="a.genericDoc !== null"
:generic-doc="a.genericDoc"
></generic-doc-item-box>
<div class="item-row separator">
<ul class="record_actions">
<li v-if="a.genericDoc?.storedObject !== null">
<document-action-buttons-group
:stored-object="a.genericDoc.storedObject"
></document-action-buttons-group>
</li>
<li>
<button
type="button"
class="btn btn-delete"
@click="emit('removeAttachment', { attachment: a })"
></button>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -2,17 +2,17 @@
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
interface GenericDocItemBoxProps {
genericDoc: GenericDocForAccompanyingPeriod;
genericDoc: GenericDocForAccompanyingPeriod;
}
const props = defineProps<GenericDocItemBoxProps>();
</script>
<template>
<div
v-if="'html' in props.genericDoc.metadata"
v-html="props.genericDoc.metadata.html"
></div>
<div
v-if="'html' in props.genericDoc.metadata"
v-html="props.genericDoc.metadata.html"
></div>
</template>
<style scoped lang="scss"></style>

View File

@@ -1,45 +1,45 @@
<script setup lang="ts">
import {
GenericDoc,
GenericDocForAccompanyingPeriod,
GenericDoc,
GenericDocForAccompanyingPeriod,
} from "ChillDocStoreAssets/types/generic_doc";
import PickGenericDocItem from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocItem.vue";
import { fetch_generic_docs_by_accompanying_period } from "ChillDocStoreAssets/js/generic-doc-api";
import { computed, onMounted, ref } from "vue";
interface PickGenericDocProps {
accompanyingPeriodId: number;
pickedList: GenericDocForAccompanyingPeriod[];
toRemove: GenericDocForAccompanyingPeriod[];
accompanyingPeriodId: number;
pickedList: GenericDocForAccompanyingPeriod[];
toRemove: GenericDocForAccompanyingPeriod[];
}
const props = defineProps<PickGenericDocProps>();
const emit = defineEmits<{
(
e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
(
e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
(
e: "removeGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
(
e: "removeGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
}>();
const genericDocs = ref<GenericDocForAccompanyingPeriod[]>([]);
const loaded = ref(false);
const isPicked = (genericDoc: GenericDocForAccompanyingPeriod): boolean =>
props.pickedList.findIndex(
(element: GenericDocForAccompanyingPeriod) =>
element.uniqueKey === genericDoc.uniqueKey,
) !== -1;
props.pickedList.findIndex(
(element: GenericDocForAccompanyingPeriod) =>
element.uniqueKey === genericDoc.uniqueKey,
) !== -1;
onMounted(async () => {
genericDocs.value = await fetch_generic_docs_by_accompanying_period(
props.accompanyingPeriodId,
);
loaded.value = true;
genericDocs.value = await fetch_generic_docs_by_accompanying_period(
props.accompanyingPeriodId,
);
loaded.value = true;
});
const textFilter = ref<string>("");
@@ -48,213 +48,229 @@ const dateToFilter = ref<string | null>(null);
const placesFilter = ref<string[]>([]);
const availablePlaces = computed<string[]>(() => {
const places = new Set<string>(
genericDocs.value.map((genericDoc: GenericDoc) => genericDoc.key),
);
const places = new Set<string>(
genericDocs.value.map((genericDoc: GenericDoc) => genericDoc.key),
);
return Array.from(places).sort((a, b) => (a < b ? -1 : a === b ? 0 : 1));
return Array.from(places).sort((a, b) => (a < b ? -1 : a === b ? 0 : 1));
});
const placeTrans = (str: string): string => {
switch (str) {
case "accompanying_course_document":
return "Documents du parcours";
case "person_document":
return "Documents de l'usager";
case "accompanying_period_calendar_document":
return "Document des rendez-vous des parcours";
case "accompanying_period_activity_document":
return "Document des échanges des parcours";
case "accompanying_period_work_evaluation_document":
return "Document des actions d'accompagnement";
default:
return str;
}
switch (str) {
case "accompanying_course_document":
return "Documents du parcours";
case "person_document":
return "Documents de l'usager";
case "accompanying_period_calendar_document":
return "Document des rendez-vous des parcours";
case "accompanying_period_activity_document":
return "Document des échanges des parcours";
case "accompanying_period_work_evaluation_document":
return "Document des actions d'accompagnement";
default:
return str;
}
};
const onPickDocument = (payload: {
genericDoc: GenericDocForAccompanyingPeriod;
genericDoc: GenericDocForAccompanyingPeriod;
}) => emit("pickGenericDoc", payload);
const onRemoveGenericDoc = (payload: {
genericDoc: GenericDocForAccompanyingPeriod;
genericDoc: GenericDocForAccompanyingPeriod;
}) => emit("removeGenericDoc", payload);
const filteredDocuments = computed<GenericDocForAccompanyingPeriod[]>(() => {
if (false === loaded.value) {
return [];
}
if (false === loaded.value) {
return [];
}
return genericDocs.value
.filter(
(genericDoc: GenericDocForAccompanyingPeriod) =>
!props.toRemove
.map((g: GenericDocForAccompanyingPeriod) => g.uniqueKey)
.includes(genericDoc.uniqueKey),
)
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (textFilter.value === "") {
return true;
}
return genericDocs.value
.filter(
(genericDoc: GenericDocForAccompanyingPeriod) =>
!props.toRemove
.map((g: GenericDocForAccompanyingPeriod) => g.uniqueKey)
.includes(genericDoc.uniqueKey),
)
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (textFilter.value === "") {
return true;
}
const needles = textFilter.value
.trim()
.split(" ")
.map((str: string) => str.trim().toLowerCase())
.filter((str: string) => str.length > 0);
const title: string =
"title" in genericDoc.metadata
? (genericDoc.metadata.title as string)
: "";
if (title === "") {
return false;
}
const needles = textFilter.value
.trim()
.split(" ")
.map((str: string) => str.trim().toLowerCase())
.filter((str: string) => str.length > 0);
const title: string =
"title" in genericDoc.metadata
? (genericDoc.metadata.title as string)
: "";
if (title === "") {
return false;
}
return needles.every((n: string) => title.toLowerCase().includes(n));
})
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (placesFilter.value.length === 0) {
return true;
}
return needles.every((n: string) =>
title.toLowerCase().includes(n),
);
})
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (placesFilter.value.length === 0) {
return true;
}
return placesFilter.value.includes(genericDoc.key);
})
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (dateToFilter.value === null) {
return true;
}
return placesFilter.value.includes(genericDoc.key);
})
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (dateToFilter.value === null) {
return true;
}
return genericDoc.doc_date.datetime8601 < dateToFilter.value;
})
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (dateFromFilter.value === null) {
return true;
}
return genericDoc.doc_date.datetime8601 < dateToFilter.value;
})
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (dateFromFilter.value === null) {
return true;
}
return genericDoc.doc_date.datetime8601 > dateFromFilter.value;
});
return genericDoc.doc_date.datetime8601 > dateFromFilter.value;
});
});
</script>
<template>
<div v-if="loaded">
<div>
<form name="f" method="get">
<div class="accordion my-3" id="filterOrderAccordion">
<h2 class="accordion-header" id="filterOrderHeading">
<button
class="accordion-button"
type="button"
data-bs-toggle="collapse"
data-bs-target="#filterOrderCollapse"
aria-expanded="true"
aria-controls="filterOrderCollapse"
>
<strong
><i class="fa fa-fw fa-filter"></i>Filtrer la liste</strong
>
</button>
</h2>
<div
class="accordion-collapse collapse"
id="filterOrderCollapse"
aria-labelledby="filterOrderHeading"
data-bs-parent="#filterOrderAccordion"
style=""
>
<div
class="accordion-body chill_filter_order container-xxl p-5 py-2"
>
<div class="row my-2">
<div class="col-sm-12">
<div class="input-group">
<input
v-model="textFilter"
type="search"
id="f_q"
name="f[q]"
placeholder="Chercher dans la liste"
class="form-control"
/>
<button type="submit" class="btn btn-misc">
<i class="fa fa-search"></i>
</button>
</div>
</div>
</div>
<div v-if="loaded">
<div>
<form name="f" method="get">
<div class="accordion my-3" id="filterOrderAccordion">
<h2 class="accordion-header" id="filterOrderHeading">
<button
class="accordion-button"
type="button"
data-bs-toggle="collapse"
data-bs-target="#filterOrderCollapse"
aria-expanded="true"
aria-controls="filterOrderCollapse"
>
<strong
><i class="fa fa-fw fa-filter"></i>Filtrer la
liste</strong
>
</button>
</h2>
<div
class="accordion-collapse collapse"
id="filterOrderCollapse"
aria-labelledby="filterOrderHeading"
data-bs-parent="#filterOrderAccordion"
style=""
>
<div
class="accordion-body chill_filter_order container-xxl p-5 py-2"
>
<div class="row my-2">
<div class="col-sm-12">
<div class="input-group">
<input
v-model="textFilter"
type="search"
id="f_q"
name="f[q]"
placeholder="Chercher dans la liste"
class="form-control"
/>
<button
type="submit"
class="btn btn-misc"
>
<i class="fa fa-search"></i>
</button>
</div>
</div>
</div>
<div class="row my-2">
<legend class="col-form-label col-sm-4 required">
Date du document
</legend>
<div class="col-sm-8 pt-1">
<div class="input-group">
<span class="input-group-text">Du</span>
<input
v-model="dateFromFilter"
type="date"
id="f_dateRanges_dateRange_from"
name="f[dateRanges][dateRange][from]"
class="form-control"
/>
<span class="input-group-text">Au</span>
<input
v-model="dateToFilter"
type="date"
id="f_dateRanges_dateRange_to"
name="f[dateRanges][dateRange][to]"
class="form-control"
/>
</div>
</div>
</div>
<div class="row my-2">
<legend
class="col-form-label col-sm-4 required"
>
Date du document
</legend>
<div class="col-sm-8 pt-1">
<div class="input-group">
<span class="input-group-text">Du</span>
<input
v-model="dateFromFilter"
type="date"
id="f_dateRanges_dateRange_from"
name="f[dateRanges][dateRange][from]"
class="form-control"
/>
<span class="input-group-text">Au</span>
<input
v-model="dateToFilter"
type="date"
id="f_dateRanges_dateRange_to"
name="f[dateRanges][dateRange][to]"
class="form-control"
/>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-sm-4 col-form-label">Filtrer par</div>
<div class="col-sm-8 pt-2">
<div class="form-check" v-for="p in availablePlaces" :key="p">
<input
type="checkbox"
v-model="placesFilter"
name="f[checkboxes][places][]"
class="form-check-input"
:value="p"
/>
<label class="form-check-label">{{ placeTrans(p) }}</label>
</div>
<div class="row my-2">
<div class="col-sm-4 col-form-label">
Filtrer par
</div>
<div class="col-sm-8 pt-2">
<div
class="form-check"
v-for="p in availablePlaces"
:key="p"
>
<input
type="checkbox"
v-model="placesFilter"
name="f[checkboxes][places][]"
class="form-check-input"
:value="p"
/>
<label class="form-check-label">{{
placeTrans(p)
}}</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</form>
</div>
<div v-if="genericDocs.length > 0" class="flex-table chill-task-list">
<pick-generic-doc-item
v-for="g in filteredDocuments"
:key="g.uniqueKey"
:accompanying-period-id="accompanyingPeriodId"
:genericDoc="g"
:is-picked="isPicked(g)"
@pickGenericDoc="onPickDocument"
@removeGenericDoc="onRemoveGenericDoc"
></pick-generic-doc-item>
<div v-if="genericDocs.length > 0" class="flex-table chill-task-list">
<pick-generic-doc-item
v-for="g in filteredDocuments"
:key="g.uniqueKey"
:accompanying-period-id="accompanyingPeriodId"
:genericDoc="g"
:is-picked="isPicked(g)"
@pickGenericDoc="onPickDocument"
@removeGenericDoc="onRemoveGenericDoc"
></pick-generic-doc-item>
</div>
<div v-else class="text-center chill-no-data-statement">
Aucun document dans ce parcours
</div>
</div>
<div v-else class="text-center chill-no-data-statement">
Aucun document dans ce parcours
<div v-else>
<div class="d-flex align-items-center">
<strong>Chargement</strong>
<div
class="spinner-border ms-auto"
role="status"
aria-hidden="true"
></div>
</div>
</div>
</div>
<div v-else>
<div class="d-flex align-items-center">
<strong>Chargement</strong>
<div
class="spinner-border ms-auto"
role="status"
aria-hidden="true"
></div>
</div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -4,85 +4,93 @@ import GenericDocItemBox from "ChillMainAssets/vuejs/WorkflowAttachment/Componen
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
interface PickGenericDocItemProps {
genericDoc: GenericDocForAccompanyingPeriod;
accompanyingPeriodId: number;
isPicked: boolean;
genericDoc: GenericDocForAccompanyingPeriod;
accompanyingPeriodId: number;
isPicked: boolean;
}
const props = defineProps<PickGenericDocItemProps>();
const emit = defineEmits<{
(
e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
(
e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
(
e: "removeGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
(
e: "removeGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
}>();
const clickOnAddButton = () => {
emit("pickGenericDoc", { genericDoc: props.genericDoc });
emit("pickGenericDoc", { genericDoc: props.genericDoc });
};
</script>
<template>
<div class="item-bloc" :class="{ isPicked: isPicked }">
<generic-doc-item-box
:generic-doc="props.genericDoc"
></generic-doc-item-box>
<div class="item-bloc" :class="{ isPicked: isPicked }">
<generic-doc-item-box
:generic-doc="props.genericDoc"
></generic-doc-item-box>
<div class="item-row separator">
<ul class="record_actions">
<li v-if="props.genericDoc.storedObject !== null">
<document-action-buttons-group
:stored-object="props.genericDoc.storedObject"
></document-action-buttons-group>
</li>
<li>
<button
v-if="!isPicked"
type="button"
class="btn btn-chill-green text-white"
@click="clickOnAddButton"
>
<i class="bi bi-cart-plus"></i>
</button>
<button
v-else
type="button"
class="btn btn-chill-red text-white"
@click="
emit('removeGenericDoc', {
genericDoc: props.genericDoc,
})
"
>
<i class="bi bi-cart-dash"></i>
</button>
</li>
</ul>
<div class="item-row separator">
<ul class="record_actions">
<li v-if="props.genericDoc.storedObject !== null">
<document-action-buttons-group
:stored-object="props.genericDoc.storedObject"
></document-action-buttons-group>
</li>
<li>
<button
v-if="!isPicked"
type="button"
class="btn btn-chill-green text-white"
@click="clickOnAddButton"
>
<i class="bi bi-cart-plus"></i>
</button>
<button
v-else
type="button"
class="btn btn-chill-red text-white"
@click="
emit('removeGenericDoc', {
genericDoc: props.genericDoc,
})
"
>
<i class="bi bi-cart-dash"></i>
</button>
</li>
</ul>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.item-bloc {
&.isPicked {
background:
linear-gradient(
180deg,
rgba(25, 135, 84, 1) 0px,
rgba(25, 135, 84, 0) 9px
),
linear-gradient(
270deg,
rgba(25, 135, 84, 1) 0px,
rgba(25, 135, 84, 0) 9px
),
linear-gradient(0deg, rgba(25, 135, 84, 1) 0px, rgba(25, 135, 84, 0) 9px),
linear-gradient(90deg, rgba(25, 135, 84, 1) 0px, rgba(25, 135, 84, 0) 9px);
}
&.isPicked {
background:
linear-gradient(
180deg,
rgba(25, 135, 84, 1) 0px,
rgba(25, 135, 84, 0) 9px
),
linear-gradient(
270deg,
rgba(25, 135, 84, 1) 0px,
rgba(25, 135, 84, 0) 9px
),
linear-gradient(
0deg,
rgba(25, 135, 84, 1) 0px,
rgba(25, 135, 84, 0) 9px
),
linear-gradient(
90deg,
rgba(25, 135, 84, 1) 0px,
rgba(25, 135, 84, 0) 9px
);
}
}
</style>

View File

@@ -5,19 +5,19 @@ import PickGenericDoc from "ChillMainAssets/vuejs/WorkflowAttachment/Component/P
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
interface PickGenericDocModalProps {
accompanyingPeriodId: number;
toRemove: GenericDocForAccompanyingPeriod[];
accompanyingPeriodId: number;
toRemove: GenericDocForAccompanyingPeriod[];
}
type PickGenericDocType = InstanceType<typeof PickGenericDoc>;
const props = defineProps<PickGenericDocModalProps>();
const emit = defineEmits<{
// eslint-disable-next-line @typescript-eslint/prefer-function-type
(
e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
// eslint-disable-next-line @typescript-eslint/prefer-function-type
(
e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void;
}>();
const picker = useTemplateRef<PickGenericDocType>("picker");
const modalOpened = ref<boolean>(false);
@@ -27,87 +27,87 @@ const modalClasses = { "modal-xl": true, "modal-dialog-scrollable": true };
const numberOfPicked = computed<number>(() => pickeds.value.length);
const onPicked = ({
genericDoc,
genericDoc,
}: {
genericDoc: GenericDocForAccompanyingPeriod;
genericDoc: GenericDocForAccompanyingPeriod;
}) => {
pickeds.value.push(genericDoc);
pickeds.value.push(genericDoc);
};
const onRemove = ({
genericDoc,
genericDoc,
}: {
genericDoc: GenericDocForAccompanyingPeriod;
genericDoc: GenericDocForAccompanyingPeriod;
}) => {
const index = pickeds.value.findIndex(
(item) => item.uniqueKey === genericDoc.uniqueKey,
);
const index = pickeds.value.findIndex(
(item) => item.uniqueKey === genericDoc.uniqueKey,
);
if (index === -1) {
throw new Error("Remove generic doc that doesn't exist");
}
if (index === -1) {
throw new Error("Remove generic doc that doesn't exist");
}
pickeds.value.splice(index, 1);
pickeds.value.splice(index, 1);
};
const onConfirm = () => {
for (let genericDoc of pickeds.value) {
emit("pickGenericDoc", { genericDoc });
}
pickeds.value = [];
closeModal();
for (let genericDoc of pickeds.value) {
emit("pickGenericDoc", { genericDoc });
}
pickeds.value = [];
closeModal();
};
const closeModal = function () {
modalOpened.value = false;
modalOpened.value = false;
};
const openModal = function () {
modalOpened.value = true;
modalOpened.value = true;
};
defineExpose({ openModal, closeModal });
</script>
<template>
<modal
v-if="modalOpened"
@close="closeModal"
:modal-dialog-class="modalClasses"
>
<template v-slot:header>
<h2 class="modal-title">Ajouter une pièce jointe</h2>
</template>
<template v-slot:body>
<pick-generic-doc
:accompanying-period-id="props.accompanyingPeriodId"
:to-remove="props.toRemove"
:picked-list="pickeds"
ref="picker"
@pickGenericDoc="onPicked"
@removeGenericDoc="onRemove"
></pick-generic-doc>
</template>
<template v-slot:footer>
<ul v-if="numberOfPicked > 0" class="record_actions">
<li>
<button
type="button"
class="btn btn-chill-green text-white"
@click="onConfirm"
>
<template v-if="numberOfPicked > 1">
<i class="fa fa-plus"></i> Ajouter {{ numberOfPicked }} pièces
jointes
</template>
<template v-else>
<i class="fa fa-plus"></i> Ajouter une pièce jointe
</template>
</button>
</li>
</ul>
</template>
</modal>
<modal
v-if="modalOpened"
@close="closeModal"
:modal-dialog-class="modalClasses"
>
<template v-slot:header>
<h2 class="modal-title">Ajouter une pièce jointe</h2>
</template>
<template v-slot:body>
<pick-generic-doc
:accompanying-period-id="props.accompanyingPeriodId"
:to-remove="props.toRemove"
:picked-list="pickeds"
ref="picker"
@pickGenericDoc="onPicked"
@removeGenericDoc="onRemove"
></pick-generic-doc>
</template>
<template v-slot:footer>
<ul v-if="numberOfPicked > 0" class="record_actions">
<li>
<button
type="button"
class="btn btn-chill-green text-white"
@click="onConfirm"
>
<template v-if="numberOfPicked > 1">
<i class="fa fa-plus"></i> Ajouter
{{ numberOfPicked }} pièces jointes
</template>
<template v-else>
<i class="fa fa-plus"></i> Ajouter une pièce jointe
</template>
</button>
</li>
</ul>
</template>
</modal>
</template>
<style scoped lang="scss"></style>

View File

@@ -3,102 +3,107 @@ import App from "./App.vue";
import { _createI18n } from "../_js/i18n";
import { WorkflowAttachment } from "ChillMainAssets/types";
import {
create_attachment,
delete_attachment,
find_attachments_by_workflow,
create_attachment,
delete_attachment,
find_attachments_by_workflow,
} from "ChillMainAssets/lib/workflow/attachments";
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
import ToastPlugin from "vue-toast-notification";
import "vue-toast-notification/dist/theme-bootstrap.css";
window.addEventListener("DOMContentLoaded", () => {
const attachments = document.querySelectorAll<HTMLDivElement>(
'div[data-app="workflow_attachments"]',
);
attachments.forEach(async (el) => {
const workflowId = parseInt(el.dataset.entityWorkflowId || "");
const accompanyingPeriodId = parseInt(
el.dataset.relatedAccompanyingPeriodId || "",
const attachments = document.querySelectorAll<HTMLDivElement>(
'div[data-app="workflow_attachments"]',
);
const attachments = await find_attachments_by_workflow(workflowId);
const app = createApp({
template:
'<app :workflowId="workflowId" :accompanyingPeriodId="accompanyingPeriodId" :attachments="attachments" @pickGenericDoc="onPickGenericDoc" @removeAttachment="onRemoveAttachment"></app>',
components: { App },
data: function () {
return { workflowId, accompanyingPeriodId, attachments };
},
methods: {
onRemoveAttachment: async function ({
attachment,
}: {
attachment: WorkflowAttachment;
}): Promise<void> {
const index = this.$data.attachments.findIndex(
(el: WorkflowAttachment) => el.id === attachment.id,
);
if (-1 === index) {
console.warn(
"this attachment is not associated with the workflow",
attachment,
);
this.$toast.error(
"This attachment is not associated with the workflow",
);
return;
}
attachments.forEach(async (el) => {
const workflowId = parseInt(el.dataset.entityWorkflowId || "");
const accompanyingPeriodId = parseInt(
el.dataset.relatedAccompanyingPeriodId || "",
);
const attachments = await find_attachments_by_workflow(workflowId);
try {
await delete_attachment(attachment);
} catch (error) {
console.error(error);
this.$toast.error("Error while removing element");
throw error;
}
this.$data.attachments.splice(index, 1);
this.$toast.success("Pièce jointe supprimée");
},
onPickGenericDoc: async function ({
genericDoc,
}: {
genericDoc: GenericDocForAccompanyingPeriod;
}): Promise<void> {
console.log("picked generic doc", genericDoc);
const app = createApp({
template:
'<app :workflowId="workflowId" :accompanyingPeriodId="accompanyingPeriodId" :attachments="attachments" @pickGenericDoc="onPickGenericDoc" @removeAttachment="onRemoveAttachment"></app>',
components: { App },
data: function () {
return { workflowId, accompanyingPeriodId, attachments };
},
methods: {
onRemoveAttachment: async function ({
attachment,
}: {
attachment: WorkflowAttachment;
}): Promise<void> {
const index = this.$data.attachments.findIndex(
(el: WorkflowAttachment) => el.id === attachment.id,
);
if (-1 === index) {
console.warn(
"this attachment is not associated with the workflow",
attachment,
);
this.$toast.error(
"This attachment is not associated with the workflow",
);
return;
}
// prevent to create double attachment:
if (
-1 !==
this.$data.attachments.findIndex(
(el: WorkflowAttachment) =>
el.genericDoc?.key === genericDoc.key &&
JSON.stringify(el.genericDoc?.identifiers) ==
JSON.stringify(genericDoc.identifiers),
)
) {
console.warn(
"this document is already attached to the workflow",
genericDoc,
);
this.$toast.error("Ce document est déjà attaché au workflow");
return;
}
try {
await delete_attachment(attachment);
} catch (error) {
console.error(error);
this.$toast.error("Error while removing element");
throw error;
}
this.$data.attachments.splice(index, 1);
this.$toast.success("Pièce jointe supprimée");
},
onPickGenericDoc: async function ({
genericDoc,
}: {
genericDoc: GenericDocForAccompanyingPeriod;
}): Promise<void> {
console.log("picked generic doc", genericDoc);
try {
const attachment = await create_attachment(workflowId, genericDoc);
this.$data.attachments.push(attachment);
} catch (error) {
console.error(error);
throw error;
}
},
},
// prevent to create double attachment:
if (
-1 !==
this.$data.attachments.findIndex(
(el: WorkflowAttachment) =>
el.genericDoc?.key === genericDoc.key &&
JSON.stringify(el.genericDoc?.identifiers) ==
JSON.stringify(genericDoc.identifiers),
)
) {
console.warn(
"this document is already attached to the workflow",
genericDoc,
);
this.$toast.error(
"Ce document est déjà attaché au workflow",
);
return;
}
try {
const attachment = await create_attachment(
workflowId,
genericDoc,
);
this.$data.attachments.push(attachment);
} catch (error) {
console.error(error);
throw error;
}
},
},
});
const i18n = _createI18n({});
app.use(i18n);
app.use(ToastPlugin);
app.mount(el);
});
const i18n = _createI18n({});
app.use(i18n);
app.use(ToastPlugin);
app.mount(el);
});
});

View File

@@ -1,23 +1,23 @@
<template>
<span
v-if="data.working_ref_status === 'to_review'"
class="badge bg-danger address-details-button-warning"
>L'adresse de référence a été modifiée</span
>
<a
v-if="data.loading === false"
@click.prevent="clickOrOpen"
class="btn btn-sm address-details-button"
title="Plus de détails"
>
<span class="fa fa-map-o"></span>
</a>
<span v-if="data.loading" class="fa fa-spin fa-spinner"></span>
<AddressModal
:address="data.working_address"
@update-address="onUpdateAddress"
ref="address_modal"
></AddressModal>
<span
v-if="data.working_ref_status === 'to_review'"
class="badge bg-danger address-details-button-warning"
>L'adresse de référence a été modifiée</span
>
<a
v-if="data.loading === false"
@click.prevent="clickOrOpen"
class="btn btn-sm address-details-button"
title="Plus de détails"
>
<span class="fa fa-map-o"></span>
</a>
<span v-if="data.loading" class="fa fa-spin fa-spinner"></span>
<AddressModal
:address="data.working_address"
@update-address="onUpdateAddress"
ref="address_modal"
></AddressModal>
</template>
<script lang="ts" setup>
@@ -27,20 +27,20 @@ import { getAddressById } from "../../../lib/api/address";
import AddressModal from "./AddressModal.vue";
export interface AddressModalContentProps {
address_id: number;
address_ref_status: AddressRefStatus;
address_id: number;
address_ref_status: AddressRefStatus;
}
interface AddressModalData {
loading: boolean;
working_address: Address | null;
working_ref_status: AddressRefStatus | null;
loading: boolean;
working_address: Address | null;
working_ref_status: AddressRefStatus | null;
}
const data: AddressModalData = reactive({
loading: false,
working_address: null,
working_ref_status: null,
loading: false,
working_address: null,
working_ref_status: null,
} as AddressModalData);
const props = defineProps<AddressModalContentProps>();
@@ -50,31 +50,31 @@ const emit = defineEmits<(e: "update-address", value: Address) => void>();
const address_modal = ref<InstanceType<typeof AddressModal> | null>(null);
onMounted(() => {
data.working_ref_status = props.address_ref_status;
data.working_ref_status = props.address_ref_status;
});
async function clickOrOpen(): Promise<void> {
if (data.working_address === null) {
data.loading = true;
data.working_address = await getAddressById(props.address_id);
data.working_ref_status = data.working_address.refStatus;
data.loading = false;
}
if (data.working_address === null) {
data.loading = true;
data.working_address = await getAddressById(props.address_id);
data.working_ref_status = data.working_address.refStatus;
data.loading = false;
}
// open the modal
address_modal.value?.open();
// open the modal
address_modal.value?.open();
}
const onUpdateAddress = (address: Address): void => {
data.working_address = address;
data.working_ref_status = address.refStatus;
emit("update-address", address);
data.working_address = address;
data.working_ref_status = address.refStatus;
emit("update-address", address);
};
</script>
<style scoped lang="scss">
.address-details-button-warning {
display: inline-block;
margin-right: 0.3rem;
display: inline-block;
margin-right: 0.3rem;
}
</style>

View File

@@ -1,16 +1,16 @@
<template>
<address-render-box
:address="props.address"
:show-button-details="false"
></address-render-box>
<address-details-ref-matching
:address="props.address"
@update-address="onUpdateAddress"
></address-details-ref-matching>
<address-details-map :address="props.address"></address-details-map>
<address-details-geographical-layers
:address="props.address"
></address-details-geographical-layers>
<address-render-box
:address="props.address"
:show-button-details="false"
></address-render-box>
<address-details-ref-matching
:address="props.address"
@update-address="onUpdateAddress"
></address-details-ref-matching>
<address-details-map :address="props.address"></address-details-map>
<address-details-geographical-layers
:address="props.address"
></address-details-geographical-layers>
</template>
<script lang="ts" setup>
@@ -21,7 +21,7 @@ import AddressDetailsGeographicalLayers from "./Parts/AddressDetailsGeographical
import AddressDetailsRefMatching from "./Parts/AddressDetailsRefMatching.vue";
interface AddressModalContentProps {
address: Address;
address: Address;
}
const props = defineProps<AddressModalContentProps>();
@@ -29,7 +29,7 @@ const props = defineProps<AddressModalContentProps>();
const emit = defineEmits<(e: "update-address", value: Address) => void>();
const onUpdateAddress = (address: Address): void => {
emit("update-address", address);
emit("update-address", address);
};
</script>

View File

@@ -1,17 +1,17 @@
<template>
<teleport to="body">
<modal v-if="state.show_modal" @close="close">
<template v-slot:header>
<h2>Détails d'une adresse</h2>
</template>
<template v-slot:body>
<address-details-content
:address="props.address"
@update-address="onUpdateAddress"
></address-details-content>
</template>
</modal>
</teleport>
<teleport to="body">
<modal v-if="state.show_modal" @close="close">
<template v-slot:header>
<h2>Détails d'une adresse</h2>
</template>
<template v-slot:body>
<address-details-content
:address="props.address"
@update-address="onUpdateAddress"
></address-details-content>
</template>
</modal>
</teleport>
</template>
<script lang="ts" setup>
@@ -21,11 +21,11 @@ import { Address } from "../../../types";
import AddressDetailsContent from "./AddressDetailsContent.vue";
interface AddressModalProps {
address: Address;
address: Address;
}
interface AddressModalState {
show_modal: boolean;
show_modal: boolean;
}
const props = defineProps<AddressModalProps>();
@@ -35,20 +35,20 @@ const emit = defineEmits<(e: "update-address", value: Address) => void>();
const state: AddressModalState = reactive({ show_modal: false });
const open = (): void => {
state.show_modal = true;
state.show_modal = true;
};
const close = (): void => {
state.show_modal = false;
state.show_modal = false;
};
const onUpdateAddress = (address: Address): void => {
emit("update-address", address);
emit("update-address", address);
};
defineExpose({
close,
open,
close,
open,
});
</script>

View File

@@ -1,59 +1,59 @@
<template>
<template v-for="(container, index) in data.containers" :key="index">
<h4>{{ localizeString(container.layer.name) }}</h4>
<ul>
<li v-for="(unit, index) in container.units" :key="index">
{{ unit.unitName }} ({{ unit.unitRefId }})
</li>
</ul>
</template>
<template v-for="(container, index) in data.containers" :key="index">
<h4>{{ localizeString(container.layer.name) }}</h4>
<ul>
<li v-for="(unit, index) in container.units" :key="index">
{{ unit.unitName }} ({{ unit.unitRefId }})
</li>
</ul>
</template>
</template>
<script lang="ts" setup>
import {
Address,
GeographicalUnitLayer,
SimpleGeographicalUnit,
Address,
GeographicalUnitLayer,
SimpleGeographicalUnit,
} from "../../../../types";
import {
getGeographicalUnitsByAddress,
getAllGeographicalUnitLayers,
getGeographicalUnitsByAddress,
getAllGeographicalUnitLayers,
} from "../../../../lib/api/address";
import { onMounted, reactive } from "vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export interface AddressDetailsGeographicalLayersProp {
address: Address;
address: Address;
}
interface GeographicalUnitContainer {
layer: GeographicalUnitLayer;
units: SimpleGeographicalUnit[];
layer: GeographicalUnitLayer;
units: SimpleGeographicalUnit[];
}
const props = defineProps<AddressDetailsGeographicalLayersProp>();
const data: {
containers: GeographicalUnitContainer[];
containers: GeographicalUnitContainer[];
} = reactive({
containers: [],
containers: [],
});
onMounted(async () => {
const [units, layers] = (await Promise.all([
getGeographicalUnitsByAddress(props.address),
getAllGeographicalUnitLayers(),
])) as [SimpleGeographicalUnit[], GeographicalUnitLayer[]];
const [units, layers] = (await Promise.all([
getGeographicalUnitsByAddress(props.address),
getAllGeographicalUnitLayers(),
])) as [SimpleGeographicalUnit[], GeographicalUnitLayer[]];
for (let layer of layers) {
let us = units.filter((u) => u.layerId === layer.id);
if (us.length > 0) {
data.containers.push({
layer,
units: us,
});
for (let layer of layers) {
let us = units.filter((u) => u.layerId === layer.id);
if (us.length > 0) {
data.containers.push({
layer,
units: us,
});
}
}
}
});
</script>

View File

@@ -1,17 +1,20 @@
<template>
<div v-if="props.address.isNoAddress" class="alert alert-info">
Cette adresse est incomplète. La position géographique est approximative.
</div>
<div
v-if="props.address.point !== null"
class="address_details_map"
ref="map_div"
></div>
<p>
Voir sur
<a :href="makeUrlGoogleMap(props.address)" target="_blank">Google Maps</a>
<a :href="makeUrlOsm(props.address)" target="_blank">OSM</a>
</p>
<div v-if="props.address.isNoAddress" class="alert alert-info">
Cette adresse est incomplète. La position géographique est
approximative.
</div>
<div
v-if="props.address.point !== null"
class="address_details_map"
ref="map_div"
></div>
<p>
Voir sur
<a :href="makeUrlGoogleMap(props.address)" target="_blank"
>Google Maps</a
>
<a :href="makeUrlOsm(props.address)" target="_blank">OSM</a>
</p>
</template>
<script lang="ts" setup>
@@ -22,11 +25,11 @@ import L, { LatLngExpression, LatLngTuple } from "leaflet";
import { Address, Point } from "../../../../types";
const lonLatForLeaflet = (point: Point): LatLngTuple => {
return [point.coordinates[1], point.coordinates[0]];
return [point.coordinates[1], point.coordinates[0]];
};
export interface MapProps {
address: Address;
address: Address;
}
const props = defineProps<MapProps>();
@@ -36,70 +39,70 @@ let map: L.Map | null = null;
let marker: L.Marker | null = null;
onMounted(() => {
if (map_div.value === null) {
// there is no map div when the address does not have any Point
return;
}
if (map_div.value === null) {
// there is no map div when the address does not have any Point
return;
}
if (props.address.point !== null) {
map = L.map(map_div.value);
map.setView(lonLatForLeaflet(props.address.point), 18);
if (props.address.point !== null) {
map = L.map(map_div.value);
map.setView(lonLatForLeaflet(props.address.point), 18);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
const markerIcon = L.icon({
iconUrl: markerIconPng,
iconAnchor: [12, 41],
});
const markerIcon = L.icon({
iconUrl: markerIconPng,
iconAnchor: [12, 41],
});
marker = L.marker(lonLatForLeaflet(props.address.point), {
icon: markerIcon,
});
marker.addTo(map);
}
marker = L.marker(lonLatForLeaflet(props.address.point), {
icon: markerIcon,
});
marker.addTo(map);
}
});
const makeUrlGoogleMap = (address: Address): string => {
const params = new URLSearchParams();
params.append("api", "1");
if (address.point !== null && address.addressReference !== null) {
params.append(
"query",
`${address.point.coordinates[1]} ${address.point.coordinates[0]}`,
);
} else {
params.append("query", address.lines.join(", "));
}
const params = new URLSearchParams();
params.append("api", "1");
if (address.point !== null && address.addressReference !== null) {
params.append(
"query",
`${address.point.coordinates[1]} ${address.point.coordinates[0]}`,
);
} else {
params.append("query", address.lines.join(", "));
}
return `https://www.google.com/maps/search/?${params.toString()}`;
return `https://www.google.com/maps/search/?${params.toString()}`;
};
const makeUrlOsm = (address: Address): string => {
if (address.point !== null && address.addressReference !== null) {
if (address.point !== null && address.addressReference !== null) {
const params = new URLSearchParams();
params.append("mlat", `${address.point.coordinates[1]}`);
params.append("mlon", `${address.point.coordinates[0]}`);
const hashParams = new URLSearchParams();
hashParams.append(
"map",
`18/${address.point.coordinates[1]}/${address.point.coordinates[0]}`,
);
return `https://www.openstreetmap.org/?${params.toString()}#${hashParams.toString()}`;
}
const params = new URLSearchParams();
params.append("mlat", `${address.point.coordinates[1]}`);
params.append("mlon", `${address.point.coordinates[0]}`);
const hashParams = new URLSearchParams();
hashParams.append(
"map",
`18/${address.point.coordinates[1]}/${address.point.coordinates[0]}`,
);
params.append("query", address.lines.join(", "));
return `https://www.openstreetmap.org/?${params.toString()}#${hashParams.toString()}`;
}
const params = new URLSearchParams();
params.append("query", address.lines.join(", "));
return `https://www.openstreetmap.org/search?${params.toString()}`;
return `https://www.openstreetmap.org/search?${params.toString()}`;
};
</script>
<style scoped>
div.address_details_map {
height: 250px;
height: 250px;
}
</style>

View File

@@ -1,124 +1,140 @@
<template>
<template v-if="props.address.refStatus !== 'match'">
<div
v-if="
props.address.refStatus === 'to_review' ||
props.address.refStatus === 'reviewed'
"
:class="{
alert: true,
'alert-danger': props.address.refStatus === 'to_review',
'alert-warning': props.address.refStatus === 'reviewed',
}"
>
<p v-if="props.address.refStatus === 'to_review'">
<i class="fa fa-warning"></i> L'adresse de référence a été modifiée.
</p>
<p v-if="props.address.refStatus === 'reviewed'">
L'adresse est conservée, mais diffère de l'adresse de référence.
</p>
<template v-if="props.address.refStatus !== 'match'">
<div
v-if="
props.address.refStatus === 'to_review' ||
props.address.refStatus === 'reviewed'
"
:class="{
alert: true,
'alert-danger': props.address.refStatus === 'to_review',
'alert-warning': props.address.refStatus === 'reviewed',
}"
>
<p v-if="props.address.refStatus === 'to_review'">
<i class="fa fa-warning"></i> L'adresse de référence a été
modifiée.
</p>
<p v-if="props.address.refStatus === 'reviewed'">
L'adresse est conservée, mais diffère de l'adresse de référence.
</p>
<template
v-if="
props.address.addressReference.street !== props.address.street ||
props.address.addressReference.streetNumber !==
props.address.streetNumber
"
>
<template v-if="props.address.country.code === 'BE'">
<div class="difference">
<span class="old"
>{{ props.address.street }} {{ props.address.streetNumber }}</span
<template
v-if="
props.address.addressReference.street !==
props.address.street ||
props.address.addressReference.streetNumber !==
props.address.streetNumber
"
>
<span class="new"
>{{ props.address.addressReference.street }}
{{ props.address.addressReference.streetNumber }}</span
>
</div>
</template>
<template v-else>
<div class="difference">
<span class="old"
>{{ props.address.streetNumber }} {{ props.address.street }}</span
>
<span class="new"
>{{ props.address.addressReference.streetNumber }}
{{ props.address.addressReference.street }}</span
>
</div>
</template>
</template>
<template v-if="props.address.country.code === 'BE'">
<div class="difference">
<span class="old"
>{{ props.address.street }}
{{ props.address.streetNumber }}</span
>
<span class="new"
>{{ props.address.addressReference.street }}
{{
props.address.addressReference.streetNumber
}}</span
>
</div>
</template>
<template v-else>
<div class="difference">
<span class="old"
>{{ props.address.streetNumber }}
{{ props.address.street }}</span
>
<span class="new"
>{{ props.address.addressReference.streetNumber }}
{{ props.address.addressReference.street }}</span
>
</div>
</template>
</template>
<template
v-if="
props.address.addressReference.postcode.id !==
props.address.postcode.id
"
>
<div class="difference">
<span class="old"
>{{ props.address.postcode.code }}
{{ props.address.postcode.name }}</span
>
<span class="new"
>{{ props.address.addressReference.postcode.code }}
{{ props.address.addressReference.postcode.name }}</span
>
<template
v-if="
props.address.addressReference.postcode.id !==
props.address.postcode.id
"
>
<div class="difference">
<span class="old"
>{{ props.address.postcode.code }}
{{ props.address.postcode.name }}</span
>
<span class="new"
>{{ props.address.addressReference.postcode.code }}
{{ props.address.addressReference.postcode.name }}</span
>
</div>
</template>
<template
v-if="
props.address.point !== null &&
(props.address.point.coordinates[0] !==
props.address.addressReference.point.coordinates[0] ||
props.address.point.coordinates[1] !==
props.address.addressReference.point.coordinates[1])
"
>
<div class="difference">
<span class="old"
>{{ props.address.point.coordinates[0] }}
{{ props.address.point.coordinates[1] }}</span
>
<span class="new"
>{{
props.address.addressReference.point.coordinates[0]
}}
{{
props.address.addressReference.point.coordinates[1]
}}</span
>
</div>
</template>
<ul class="record_actions">
<li v-if="props.address.refStatus === 'to_review'">
<button class="btn btn-sm btn-update" @click="applyUpdate">
Appliquer les modifications
</button>
</li>
<li v-if="props.address.refStatus === 'to_review'">
<button
class="btn btn-sm btn-primary"
@click="keepCurrentAddress"
>
Conserver
</button>
</li>
<li v-if="props.address.refStatus === 'reviewed'">
<button
class="btn btn-sm btn-primary"
@click="backToReview"
>
Ré-examiner
</button>
</li>
</ul>
</div>
</template>
<template
v-if="
props.address.point !== null &&
(props.address.point.coordinates[0] !==
props.address.addressReference.point.coordinates[0] ||
props.address.point.coordinates[1] !==
props.address.addressReference.point.coordinates[1])
"
>
<div class="difference">
<span class="old"
>{{ props.address.point.coordinates[0] }}
{{ props.address.point.coordinates[1] }}</span
>
<span class="new"
>{{ props.address.addressReference.point.coordinates[0] }}
{{ props.address.addressReference.point.coordinates[1] }}</span
>
</div>
</template>
<ul class="record_actions">
<li v-if="props.address.refStatus === 'to_review'">
<button class="btn btn-sm btn-update" @click="applyUpdate">
Appliquer les modifications
</button>
</li>
<li v-if="props.address.refStatus === 'to_review'">
<button class="btn btn-sm btn-primary" @click="keepCurrentAddress">
Conserver
</button>
</li>
<li v-if="props.address.refStatus === 'reviewed'">
<button class="btn btn-sm btn-primary" @click="backToReview">
Ré-examiner
</button>
</li>
</ul>
</div>
</template>
</template>
</template>
<script lang="ts" setup>
import { Address } from "../../../../types";
import {
markAddressReviewed,
markAddressToReview,
syncAddressWithReference,
markAddressReviewed,
markAddressToReview,
syncAddressWithReference,
} from "../../../../lib/api/address";
export interface AddressDetailsRefMatchingProps {
address: Address;
address: Address;
}
const props = defineProps<AddressDetailsRefMatchingProps>();
@@ -126,38 +142,38 @@ const props = defineProps<AddressDetailsRefMatchingProps>();
const emit = defineEmits<(e: "update-address", value: Address) => void>();
const applyUpdate = async () => {
const new_address = await syncAddressWithReference(props.address);
const new_address = await syncAddressWithReference(props.address);
emit("update-address", new_address);
emit("update-address", new_address);
};
const keepCurrentAddress = async () => {
const new_address = await markAddressReviewed(props.address);
const new_address = await markAddressReviewed(props.address);
emit("update-address", new_address);
emit("update-address", new_address);
};
const backToReview = async () => {
const new_address = await markAddressToReview(props.address);
const new_address = await markAddressToReview(props.address);
emit("update-address", new_address);
emit("update-address", new_address);
};
</script>
<style scoped lang="scss">
.difference {
margin-bottom: 0.5rem;
margin-bottom: 0.5rem;
span {
display: block;
}
span {
display: block;
}
.old {
text-decoration: red line-through;
}
.new {
font-weight: bold;
color: green;
}
.old {
text-decoration: red line-through;
}
.new {
font-weight: bold;
color: green;
}
}
</style>

View File

@@ -1,63 +1,66 @@
<template>
<span
v-if="props.entity.type === 'person'"
class="badge rounded-pill bg-person"
>
{{ trans(PERSON) }}
</span>
<span
v-if="props.entity.type === 'person'"
class="badge rounded-pill bg-person"
>
{{ trans(PERSON) }}
</span>
<span
v-if="props.entity.type === 'thirdparty'"
class="badge rounded-pill bg-thirdparty"
>
<template v-if="props.options.displayLong !== true">
{{ trans(THIRDPARTY) }}
</template>
<span
v-if="props.entity.type === 'thirdparty'"
class="badge rounded-pill bg-thirdparty"
>
<template v-if="props.options.displayLong !== true">
{{ trans(THIRDPARTY) }}
</template>
<i class="fa fa-fw fa-user" v-if="props.entity.kind === 'child'" />
<i
class="fa fa-fw fa-hospital-o"
v-else-if="props.entity.kind === 'company'"
/>
<i class="fa fa-fw fa-user-md" v-else />
<i class="fa fa-fw fa-user" v-if="props.entity.kind === 'child'" />
<i
class="fa fa-fw fa-hospital-o"
v-else-if="props.entity.kind === 'company'"
/>
<i class="fa fa-fw fa-user-md" v-else />
<template v-if="props.options.displayLong === true">
<span v-if="props.entity.kind === 'child'">{{
trans(THIRDPARTY_CONTACT_OF)
}}</span>
<span v-else-if="props.entity.kind === 'company'">{{
trans(THIRDPARTY_A_COMPANY)
}}</span>
<span v-else>{{ trans(THIRDPARTY_A_CONTACT) }}</span>
</template>
</span>
<template v-if="props.options.displayLong === true">
<span v-if="props.entity.kind === 'child'">{{
trans(THIRDPARTY_CONTACT_OF)
}}</span>
<span v-else-if="props.entity.kind === 'company'">{{
trans(THIRDPARTY_A_COMPANY)
}}</span>
<span v-else>{{ trans(THIRDPARTY_A_CONTACT) }}</span>
</template>
</span>
<span v-if="props.entity.type === 'user'" class="badge rounded-pill bg-user">
{{ trans(ACCEPTED_USERS) }}
</span>
<span
v-if="props.entity.type === 'user'"
class="badge rounded-pill bg-user"
>
{{ trans(ACCEPTED_USERS) }}
</span>
<span
v-if="props.entity.type === 'household'"
class="badge rounded-pill bg-user"
>
{{ trans(HOUSEHOLD) }}
</span>
<span
v-if="props.entity.type === 'household'"
class="badge rounded-pill bg-user"
>
{{ trans(HOUSEHOLD) }}
</span>
</template>
<script setup>
import {
trans,
HOUSEHOLD,
ACCEPTED_USERS,
THIRDPARTY_A_CONTACT,
THIRDPARTY_CONTACT_OF,
THIRDPARTY_A_COMPANY,
PERSON,
THIRDPARTY,
trans,
HOUSEHOLD,
ACCEPTED_USERS,
THIRDPARTY_A_CONTACT,
THIRDPARTY_CONTACT_OF,
THIRDPARTY_A_COMPANY,
PERSON,
THIRDPARTY,
} from "translator";
const props = defineProps({
options: Object,
entity: Object,
options: Object,
entity: Object,
});
</script>

View File

@@ -4,9 +4,9 @@ import { Ckeditor } from "@ckeditor/ckeditor5-vue";
import { ClassicEditor } from "ckeditor5";
import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config";
import {
trans,
EDITOR_SWITCH_TO_SIMPLE,
EDITOR_SWITCH_TO_COMPLEX,
trans,
EDITOR_SWITCH_TO_SIMPLE,
EDITOR_SWITCH_TO_COMPLEX,
} from "translator";
const EDITOR_MODE_KEY = "editorMode";
@@ -16,101 +16,107 @@ const value = defineModel({ required: true });
const isSimple = computed(() => kind.value === "simple");
const toggleButtonClass = computed(() => {
return {
["toggle-button"]: true,
onEditor: !isSimple.value,
onSimple: isSimple.value,
};
return {
["toggle-button"]: true,
onEditor: !isSimple.value,
onSimple: isSimple.value,
};
});
const toggleEditor = () => {
let newValue;
let newValue;
newValue = kind.value === "simple" ? "rich" : "simple";
kind.value = "rich";
window.localStorage.setItem(EDITOR_MODE_KEY, newValue);
newValue = kind.value === "simple" ? "rich" : "simple";
kind.value = "rich";
window.localStorage.setItem(EDITOR_MODE_KEY, newValue);
window.dispatchEvent(new Event("toggleEditorKind"));
window.dispatchEvent(new Event("toggleEditorKind"));
};
const onKindChange = function (/* event: StorageEvent | Event */) {
const newValue = window.localStorage.getItem(EDITOR_MODE_KEY);
const newValue = window.localStorage.getItem(EDITOR_MODE_KEY);
if (null === newValue || !(newValue === "rich" || newValue === "simple")) {
throw "invalid new value: " + newValue;
}
if (null === newValue || !(newValue === "rich" || newValue === "simple")) {
throw "invalid new value: " + newValue;
}
if (kind.value !== newValue) {
kind.value = newValue;
}
if (kind.value !== newValue) {
kind.value = newValue;
}
};
onMounted(function () {
const storage = window.localStorage;
const savedKind = storage.getItem(EDITOR_MODE_KEY);
const storage = window.localStorage;
const savedKind = storage.getItem(EDITOR_MODE_KEY);
if (null !== kind.value && (savedKind === "simple" || savedKind === "rich")) {
kind.value = savedKind;
}
if (
null !== kind.value &&
(savedKind === "simple" || savedKind === "rich")
) {
kind.value = savedKind;
}
window.addEventListener("storage", onKindChange);
window.addEventListener("toggleEditorKind", onKindChange);
window.addEventListener("storage", onKindChange);
window.addEventListener("toggleEditorKind", onKindChange);
});
onUnmounted(function () {
window.removeEventListener("storage", onKindChange);
window.removeEventListener("toggleEditorKind", onKindChange);
window.removeEventListener("storage", onKindChange);
window.removeEventListener("toggleEditorKind", onKindChange);
});
</script>
<template>
<div>
<div v-if="'rich' === kind">
<Ckeditor
:editor="ClassicEditor"
:config="classicEditorConfig"
v-model="value"
/>
</div>
<div v-else>
<textarea v-model="value" class="form-control simple-editor"></textarea>
</div>
<div>
<div v-if="'rich' === kind">
<Ckeditor
:editor="ClassicEditor"
:config="classicEditorConfig"
v-model="value"
/>
</div>
<div v-else>
<textarea
v-model="value"
class="form-control simple-editor"
></textarea>
</div>
<button :class="toggleButtonClass" type="button" @click="toggleEditor">
{{
isSimple
? trans(EDITOR_SWITCH_TO_COMPLEX)
: trans(EDITOR_SWITCH_TO_SIMPLE)
}}
</button>
</div>
<button :class="toggleButtonClass" type="button" @click="toggleEditor">
{{
isSimple
? trans(EDITOR_SWITCH_TO_COMPLEX)
: trans(EDITOR_SWITCH_TO_SIMPLE)
}}
</button>
</div>
</template>
<style scoped lang="scss">
@import "ChillMainAssets/module/bootstrap/bootstrap";
.toggle-button {
background: white;
border: none;
padding: 0.15rem;
color: #069;
cursor: pointer;
background: white;
border: none;
padding: 0.15rem;
color: #069;
cursor: pointer;
@include media-breakpoint-up(md) {
&.onEditor {
position: relative;
left: 1rem;
top: -1.5rem;
@include media-breakpoint-up(md) {
&.onEditor {
position: relative;
left: 1rem;
top: -1.5rem;
}
&.onSimple {
position: relative;
top: -0.75rem;
left: 1rem;
}
}
&.onSimple {
position: relative;
top: -0.75rem;
left: 1rem;
}
}
}
.simple-editor {
min-height: 190px;
min-height: 190px;
}
</style>

View File

@@ -1,40 +1,40 @@
<template>
<div class="confidential">
<div :class="{ blur: isBlurred }">
<slot name="confidential-content" />
<div class="confidential">
<div :class="{ blur: isBlurred }">
<slot name="confidential-content" />
</div>
<div class="toggle-container">
<i
class="fa toggle"
:class="toggleIcon"
aria-hidden="true"
@click="toggleBlur"
/>
</div>
</div>
<div class="toggle-container">
<i
class="fa toggle"
:class="toggleIcon"
aria-hidden="true"
@click="toggleBlur"
/>
</div>
</div>
</template>
<script>
export default {
name: "Confidential",
data() {
return {
isBlurred: true,
toggleIcon: "fa-eye",
};
},
methods: {
toggleBlur() {
console.log(this.positionBtnFar);
this.isBlurred = !this.isBlurred;
this.toggleIcon = this.isBlurred ? "fa-eye" : "fa-eye-slash";
name: "Confidential",
data() {
return {
isBlurred: true,
toggleIcon: "fa-eye",
};
},
methods: {
toggleBlur() {
console.log(this.positionBtnFar);
this.isBlurred = !this.isBlurred;
this.toggleIcon = this.isBlurred ? "fa-eye" : "fa-eye-slash";
},
},
},
};
</script>
<style scoped lang="scss">
.confidential {
align-content: center;
align-content: center;
}
</style>

View File

@@ -1,77 +1,83 @@
<template>
<component :is="component" class="chill-entity entity-address">
<component :is="component" class="address" :class="multiline">
<div v-if="isConfidential">
<confidential :position-btn-far="true">
<template #confidential-content>
<div v-if="isMultiline === true">
<p v-for="(l, i) in address.lines" :key="`line-${i}`">
{{ l }}
</p>
<p v-if="showButtonDetails">
<address-details-button
:address_id="address.address_id"
:address_ref_status="address.refStatus"
/>
</p>
<component :is="component" class="chill-entity entity-address">
<component :is="component" class="address" :class="multiline">
<div v-if="isConfidential">
<confidential :position-btn-far="true">
<template #confidential-content>
<div v-if="isMultiline === true">
<p
v-for="(l, i) in address.lines"
:key="`line-${i}`"
>
{{ l }}
</p>
<p v-if="showButtonDetails">
<address-details-button
:address_id="address.address_id"
:address_ref_status="address.refStatus"
/>
</p>
</div>
<div v-else>
<p v-if="'' !== address.text" class="street">
{{ address.text }}
</p>
<p
v-if="null !== address.postcode"
class="postcode"
>
{{ address.postcode.code }}
{{ address.postcode.name }}
</p>
<p v-if="null !== address.country" class="country">
{{ localizeString(address.country.name) }}
</p>
</div>
</template>
</confidential>
</div>
<div v-else>
<p v-if="'' !== address.text" class="street">
{{ address.text }}
</p>
<p v-if="null !== address.postcode" class="postcode">
{{ address.postcode.code }}
{{ address.postcode.name }}
</p>
<p v-if="null !== address.country" class="country">
{{ localizeString(address.country.name) }}
</p>
</div>
</template>
</confidential>
</div>
<div v-if="!isConfidential">
<div v-if="isMultiline === true">
<p v-for="(l, i) in address.lines" :key="`line-${i}`">
{{ l }}
</p>
<p v-if="showButtonDetails">
<address-details-button
:address_id="address.address_id"
:address_ref_status="address.refStatus"
/>
</p>
<div v-if="!isConfidential">
<div v-if="isMultiline === true">
<p v-for="(l, i) in address.lines" :key="`line-${i}`">
{{ l }}
</p>
<p v-if="showButtonDetails">
<address-details-button
:address_id="address.address_id"
:address_ref_status="address.refStatus"
/>
</p>
</div>
<div v-else>
<p v-if="address.text" class="street">
{{ address.text }}
<template v-if="showButtonDetails">
<address-details-button
:address_id="address.address_id"
:address_ref_status="address.refStatus"
/>
</template>
</p>
</div>
</div>
</component>
<div v-if="useDatePane === true" class="address-more">
<div v-if="address.validFrom">
<span class="validFrom">
<b>{{ trans(ADDRESS_VALID_FROM) }}</b
>: {{ $d(address.validFrom.date) }}
</span>
</div>
<div v-if="address.validTo">
<span class="validTo">
<b>{{ trans(ADDRESS_VALID_TO) }}</b
>: {{ $d(address.validTo.date) }}
</span>
</div>
</div>
<div v-else>
<p v-if="address.text" class="street">
{{ address.text }}
<template v-if="showButtonDetails">
<address-details-button
:address_id="address.address_id"
:address_ref_status="address.refStatus"
/>
</template>
</p>
</div>
</div>
</component>
<div v-if="useDatePane === true" class="address-more">
<div v-if="address.validFrom">
<span class="validFrom">
<b>{{ trans(ADDRESS_VALID_FROM) }}</b
>: {{ $d(address.validFrom.date) }}
</span>
</div>
<div v-if="address.validTo">
<span class="validTo">
<b>{{ trans(ADDRESS_VALID_TO) }}</b
>: {{ $d(address.validTo.date) }}
</span>
</div>
</div>
</component>
</template>
<script>
@@ -81,51 +87,51 @@ import { trans, ADDRESS_VALID_FROM, ADDRESS_VALID_TO } from "translator";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "AddressRenderBox",
methods: { localizeString },
components: {
Confidential,
AddressDetailsButton,
},
props: {
address: {
type: Object,
name: "AddressRenderBox",
methods: { localizeString },
components: {
Confidential,
AddressDetailsButton,
},
isMultiline: {
default: true,
type: Boolean,
props: {
address: {
type: Object,
},
isMultiline: {
default: true,
type: Boolean,
},
useDatePane: {
default: false,
type: Boolean,
},
showButtonDetails: {
default: true,
type: Boolean,
},
},
useDatePane: {
default: false,
type: Boolean,
setup() {
return { trans, ADDRESS_VALID_FROM, ADDRESS_VALID_TO };
},
showButtonDetails: {
default: true,
type: Boolean,
computed: {
component() {
return this.isMultiline === true ? "div" : "span";
},
multiline() {
return this.isMultiline === true ? "multiline" : "";
},
isConfidential() {
return this.address.confidential;
},
},
},
setup() {
return { trans, ADDRESS_VALID_FROM, ADDRESS_VALID_TO };
},
computed: {
component() {
return this.isMultiline === true ? "div" : "span";
},
multiline() {
return this.isMultiline === true ? "multiline" : "";
},
isConfidential() {
return this.address.confidential;
},
},
};
</script>
<style lang="scss" scoped>
p {
&:after {
content: " ";
margin-right: 0.3em;
}
&:after {
content: " ";
margin-right: 0.3em;
}
}
</style>

View File

@@ -1,28 +1,9 @@
<template>
<i :class="['fa', genderClass, 'px-1']" />
<i :class="gender.icon" />
</template>
<script setup>
import { computed } from "vue";
const props = defineProps({
gender: {
type: Object,
required: true,
},
});
const genderClass = computed(() => {
switch (props.gender.genderTranslation) {
case "woman":
return "fa-venus";
case "man":
return "fa-mars";
case "both":
return "fa-neuter";
case "unknown":
return "fa-genderless";
default:
return "fa-genderless";
}
gender: Object,
});
</script>

View File

@@ -4,23 +4,23 @@ import { computed } from "vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
interface UserGroupRenderBoxProps {
userGroup: UserGroup;
userGroup: UserGroup;
}
const props = defineProps<UserGroupRenderBoxProps>();
const styles = computed<{ color: string; "background-color": string }>(() => {
return {
color: props.userGroup.foregroundColor,
"background-color": props.userGroup.backgroundColor,
};
return {
color: props.userGroup.foregroundColor,
"background-color": props.userGroup.backgroundColor,
};
});
</script>
<template>
<span class="badge-user-group" :style="styles">{{
localizeString(userGroup.label)
}}</span>
<span class="badge-user-group" :style="styles">{{
localizeString(userGroup.label)
}}</span>
</template>
<style scoped lang="scss"></style>

View File

@@ -1,31 +1,31 @@
<template>
<span class="chill-entity entity-user">
{{ user.label }}
<span class="user-job" v-if="user.user_job !== null">
({{ localizeString(user.user_job.label) }})</span
>
<span class="main-scope" v-if="user.main_scope !== null">
({{ localizeString(user.main_scope.name) }})</span
>
<span
v-if="user.isAbsent"
class="badge bg-danger rounded-pill"
:title="Absent"
>A</span
>
</span>
<span class="chill-entity entity-user">
{{ user.label }}
<span class="user-job" v-if="user.user_job !== null">
({{ localizeString(user.user_job.label) }})</span
>
<span class="main-scope" v-if="user.main_scope !== null">
({{ localizeString(user.main_scope.name) }})</span
>
<span
v-if="user.isAbsent"
class="badge bg-danger rounded-pill"
:title="Absent"
>A</span
>
</span>
</template>
<script>
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
export default {
name: "UserRenderBoxBadge",
methods: {
localizeString(label) {
return localizeString(label);
name: "UserRenderBoxBadge",
methods: {
localizeString(label) {
return localizeString(label);
},
},
},
props: ["user"],
props: ["user"],
};
</script>

View File

@@ -1,83 +1,83 @@
<template>
<div class="d-grid gap-2 my-3">
<button
class="btn btn-misc"
type="button"
v-if="!subscriberFinal"
@click="subscribeTo('subscribe', 'final')"
>
<i class="fa fa-check fa-fw"></i>
{{ trans(WORKFLOW_SUBSCRIBE_FINAL) }}
</button>
<button
class="btn btn-misc"
type="button"
v-if="subscriberFinal"
@click="subscribeTo('unsubscribe', 'final')"
>
<i class="fa fa-times fa-fw"></i>
{{ trans(WORKFLOW_UNSUBSCRIBE_FINAL) }}
</button>
<button
class="btn btn-misc"
type="button"
v-if="!subscriberStep"
@click="subscribeTo('subscribe', 'step')"
>
<i class="fa fa-check fa-fw"></i>
{{ trans(WORKFLOW_SUBSCRIBE_ALL_STEPS) }}
</button>
<button
class="btn btn-misc"
type="button"
v-if="subscriberStep"
@click="subscribeTo('unsubscribe', 'step')"
>
<i class="fa fa-times fa-fw"></i>
{{ trans(WORKFLOW_UNSUBSCRIBE_ALL_STEPS) }}
</button>
</div>
<div class="d-grid gap-2 my-3">
<button
class="btn btn-misc"
type="button"
v-if="!subscriberFinal"
@click="subscribeTo('subscribe', 'final')"
>
<i class="fa fa-check fa-fw"></i>
{{ trans(WORKFLOW_SUBSCRIBE_FINAL) }}
</button>
<button
class="btn btn-misc"
type="button"
v-if="subscriberFinal"
@click="subscribeTo('unsubscribe', 'final')"
>
<i class="fa fa-times fa-fw"></i>
{{ trans(WORKFLOW_UNSUBSCRIBE_FINAL) }}
</button>
<button
class="btn btn-misc"
type="button"
v-if="!subscriberStep"
@click="subscribeTo('subscribe', 'step')"
>
<i class="fa fa-check fa-fw"></i>
{{ trans(WORKFLOW_SUBSCRIBE_ALL_STEPS) }}
</button>
<button
class="btn btn-misc"
type="button"
v-if="subscriberStep"
@click="subscribeTo('unsubscribe', 'step')"
>
<i class="fa fa-times fa-fw"></i>
{{ trans(WORKFLOW_UNSUBSCRIBE_ALL_STEPS) }}
</button>
</div>
</template>
<script setup>
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
import { defineProps, defineEmits } from "vue";
import {
trans,
WORKFLOW_SUBSCRIBE_FINAL,
WORKFLOW_UNSUBSCRIBE_FINAL,
WORKFLOW_SUBSCRIBE_ALL_STEPS,
WORKFLOW_UNSUBSCRIBE_ALL_STEPS,
trans,
WORKFLOW_SUBSCRIBE_FINAL,
WORKFLOW_UNSUBSCRIBE_FINAL,
WORKFLOW_SUBSCRIBE_ALL_STEPS,
WORKFLOW_UNSUBSCRIBE_ALL_STEPS,
} from "translator";
// props
const props = defineProps({
entityWorkflowId: {
type: Number,
required: true,
},
subscriberStep: {
type: Boolean,
required: true,
},
subscriberFinal: {
type: Boolean,
required: true,
},
entityWorkflowId: {
type: Number,
required: true,
},
subscriberStep: {
type: Boolean,
required: true,
},
subscriberFinal: {
type: Boolean,
required: true,
},
});
//methods
const subscribeTo = (step, to) => {
let params = new URLSearchParams();
params.set("subscribe", to);
let params = new URLSearchParams();
params.set("subscribe", to);
const url =
`/api/1.0/main/workflow/${props.entityWorkflowId}/${step}?` +
params.toString();
const url =
`/api/1.0/main/workflow/${props.entityWorkflowId}/${step}?` +
params.toString();
makeFetch("POST", url).then((response) => {
emit("subscriptionUpdated", response);
});
makeFetch("POST", url).then((response) => {
emit("subscriptionUpdated", response);
});
};
// emit

View File

@@ -1,138 +1,143 @@
<template>
<div class="flex-table workflow" id="workflow-list">
<div
v-for="(w, i) in props.workflows"
:key="`workflow-${i}`"
class="item-bloc"
>
<div>
<div class="item-row col">
<h2>{{ w.title }}</h2>
<div class="flex-grow-1 ms-3 h3">
<div class="visually-hidden">
{{ w.relatedEntityClass }}
{{ w.relatedEntityId }}
</div>
</div>
</div>
<div class="breadcrumb">
<template v-for="(step, j) in w.steps" :key="`step-${j}`">
<span
class="mx-2"
tabindex="0"
data-bs-trigger="focus hover"
data-bs-toggle="popover"
data-bs-placement="bottom"
data-bs-custom-class="workflow-transition"
:title="getPopTitle(step)"
:data-bs-content="getPopContent(step)"
>
<i
v-if="step.currentStep.name === 'initial'"
class="fa fa-circle me-1 text-chill-yellow"
>
</i>
<i v-if="step.isFreezed" class="fa fa-snowflake-o fa-sm me-1">
</i>
{{ step.currentStep.text }}
</span>
<span v-if="j !== Object.keys(w.steps).length - 1"> </span>
</template>
</div>
<span
v-if="w.isOnHoldAtCurrentStep"
class="badge bg-success rounded-pill"
>{{ trans(WORKFLOW_ON_HOLD) }}</span
<div class="flex-table workflow" id="workflow-list">
<div
v-for="(w, i) in props.workflows"
:key="`workflow-${i}`"
class="item-bloc"
>
</div>
<div>
<div class="item-row col">
<h2>{{ w.title }}</h2>
<div class="flex-grow-1 ms-3 h3">
<div class="visually-hidden">
{{ w.relatedEntityClass }}
{{ w.relatedEntityId }}
</div>
</div>
</div>
<div class="item-row">
<div class="item-col flex-grow-1">
<p v-if="isUserSubscribedToStep(w)">
<i class="fa fa-check fa-fw"></i>
{{ trans(WORKFLOW_YOU_SUBSCRIBED_TO_ALL_STEPS) }}
</p>
<p v-if="isUserSubscribedToFinal(w)">
<i class="fa fa-check fa-fw"></i>
{{ trans(WORKFLOW_YOU_SUBSCRIBED_TO_FINAL_STEP) }}
</p>
<div class="breadcrumb">
<template v-for="(step, j) in w.steps" :key="`step-${j}`">
<span
class="mx-2"
tabindex="0"
data-bs-trigger="focus hover"
data-bs-toggle="popover"
data-bs-placement="bottom"
data-bs-custom-class="workflow-transition"
:title="getPopTitle(step)"
:data-bs-content="getPopContent(step)"
>
<i
v-if="step.currentStep.name === 'initial'"
class="fa fa-circle me-1 text-chill-yellow"
>
</i>
<i
v-if="step.isFreezed"
class="fa fa-snowflake-o fa-sm me-1"
>
</i>
{{ step.currentStep.text }}
</span>
<span v-if="j !== Object.keys(w.steps).length - 1">
</span>
</template>
</div>
<span
v-if="w.isOnHoldAtCurrentStep"
class="badge bg-success rounded-pill"
>{{ trans(WORKFLOW_ON_HOLD) }}</span
>
</div>
<div class="item-row">
<div class="item-col flex-grow-1">
<p v-if="isUserSubscribedToStep(w)">
<i class="fa fa-check fa-fw"></i>
{{ trans(WORKFLOW_YOU_SUBSCRIBED_TO_ALL_STEPS) }}
</p>
<p v-if="isUserSubscribedToFinal(w)">
<i class="fa fa-check fa-fw"></i>
{{ trans(WORKFLOW_YOU_SUBSCRIBED_TO_FINAL_STEP) }}
</p>
</div>
<div class="item-col">
<ul class="record_actions">
<li>
<a
:href="goToUrl(w)"
class="btn btn-sm btn-show"
:title="trans(SEE)"
></a>
</li>
</ul>
</div>
</div>
</div>
<div class="item-col">
<ul class="record_actions">
<li>
<a
:href="goToUrl(w)"
class="btn btn-sm btn-show"
:title="trans(SEE)"
></a>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script setup>
import Popover from "bootstrap/js/src/popover";
import { onMounted } from "vue";
import {
trans,
BY_USER,
SEE,
WORKFLOW_YOU_SUBSCRIBED_TO_ALL_STEPS,
WORKFLOW_YOU_SUBSCRIBED_TO_FINAL_STEP,
WORKFLOW_ON_HOLD,
WORKFLOW_AT,
trans,
BY_USER,
SEE,
WORKFLOW_YOU_SUBSCRIBED_TO_ALL_STEPS,
WORKFLOW_YOU_SUBSCRIBED_TO_FINAL_STEP,
WORKFLOW_ON_HOLD,
WORKFLOW_AT,
} from "translator";
// props
const props = defineProps({
workflows: {
type: Array,
required: true,
},
workflows: {
type: Array,
required: true,
},
});
// methods
const goToUrl = (w) => `/fr/main/workflow/${w.id}/show`;
const getPopTitle = (step) => {
if (step.transitionPrevious != null) {
//console.log(step.transitionPrevious.text);
let freezed = step.isFreezed
? `<i class="fa fa-snowflake-o fa-sm me-1"></i>`
: ``;
return `${freezed}${step.transitionPrevious.text}`;
}
if (step.transitionPrevious != null) {
//console.log(step.transitionPrevious.text);
let freezed = step.isFreezed
? `<i class="fa fa-snowflake-o fa-sm me-1"></i>`
: ``;
return `${freezed}${step.transitionPrevious.text}`;
}
};
const getPopContent = (step) => {
if (step.transitionPrevious != null) {
if (step.transitionPreviousBy !== null) {
return `<ul class="small_in_title">
if (step.transitionPrevious != null) {
if (step.transitionPreviousBy !== null) {
return `<ul class="small_in_title">
<li><span class="item-key">${trans(BY_USER)} : </span><b>${step.transitionPreviousBy.text}</b></li>
<li><span class="item-key">${trans(WORKFLOW_AT)} : </span><b>${formatDate(step.transitionPreviousAt.datetime)}</b></li>
</ul>`;
} else {
return `<ul class="small_in_title">
} else {
return `<ul class="small_in_title">
<li><span class="item-key">${trans(WORKFLOW_AT)} : </span><b>${formatDate(step.transitionPreviousAt.datetime)}</b></li>
</ul>`;
}
}
}
};
const formatDate = (datetime) =>
datetime.split("T")[0] + " " + datetime.split("T")[1].substring(0, 5);
datetime.split("T")[0] + " " + datetime.split("T")[1].substring(0, 5);
const isUserSubscribedToStep = () => false;
const isUserSubscribedToFinal = () => false;
onMounted(() => {
const triggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="popover"]'),
);
triggerList.map(function (el) {
return new Popover(el, {
html: true,
const triggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="popover"]'),
);
triggerList.map(function (el) {
return new Popover(el, {
html: true,
});
});
});
});
</script>

View File

@@ -1,45 +1,49 @@
<template>
<Pick-workflow
:relatedEntityClass="props.relatedEntityClass"
:relatedEntityId="props.relatedEntityId"
:workflowsAvailables="props.workflowsAvailables"
:preventDefaultMoveToGenerate="props.preventDefaultMoveToGenerate"
:goToGenerateWorkflowPayload="props.goToGenerateWorkflowPayload"
:countExistingWorkflows="countWorkflows"
:embedded-within-list-modal="false"
@go-to-generate-workflow="goToGenerateWorkflow"
@click-open-list="openModal"
></Pick-workflow>
<Pick-workflow
:relatedEntityClass="props.relatedEntityClass"
:relatedEntityId="props.relatedEntityId"
:workflowsAvailables="props.workflowsAvailables"
:preventDefaultMoveToGenerate="props.preventDefaultMoveToGenerate"
:goToGenerateWorkflowPayload="props.goToGenerateWorkflowPayload"
:countExistingWorkflows="countWorkflows"
:embedded-within-list-modal="false"
@go-to-generate-workflow="goToGenerateWorkflow"
@click-open-list="openModal"
></Pick-workflow>
<teleport to="body">
<Modal
v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false"
>
<template v-slot:header>
<h2 class="modal-title">{{ trans(WORKFLOW_LIST) }}</h2>
</template>
<teleport to="body">
<Modal
v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false"
>
<template v-slot:header>
<h2 class="modal-title">{{ trans(WORKFLOW_LIST) }}</h2>
</template>
<template v-slot:body>
<list-workflow-vue :workflows="workflows"></list-workflow-vue>
</template>
<template v-slot:body>
<list-workflow-vue :workflows="workflows"></list-workflow-vue>
</template>
<template v-slot:footer>
<pick-workflow
v-if="allowCreate"
:relatedEntityClass="props.relatedEntityClass"
:relatedEntityId="props.relatedEntityId"
:workflowsAvailables="props.workflowsAvailables"
:preventDefaultMoveToGenerate="props.preventDefaultMoveToGenerate"
:goToGenerateWorkflowPayload="props.goToGenerateWorkflowPayload"
:countExistingWorkflows="countWorkflows"
:embedded-within-list-modal="true"
@go-to-generate-workflow="goToGenerateWorkflow"
></pick-workflow>
</template>
</Modal>
</teleport>
<template v-slot:footer>
<pick-workflow
v-if="allowCreate"
:relatedEntityClass="props.relatedEntityClass"
:relatedEntityId="props.relatedEntityId"
:workflowsAvailables="props.workflowsAvailables"
:preventDefaultMoveToGenerate="
props.preventDefaultMoveToGenerate
"
:goToGenerateWorkflowPayload="
props.goToGenerateWorkflowPayload
"
:countExistingWorkflows="countWorkflows"
:embedded-within-list-modal="true"
@go-to-generate-workflow="goToGenerateWorkflow"
></pick-workflow>
</template>
</Modal>
</teleport>
</template>
<script setup>
@@ -51,35 +55,35 @@ import { trans, WORKFLOW_LIST } from "translator";
// Define props
const props = defineProps({
workflows: {
type: Array,
required: true,
},
allowCreate: {
type: Boolean,
required: true,
},
relatedEntityClass: {
type: String,
required: true,
},
relatedEntityId: {
type: Number,
required: false,
},
workflowsAvailables: {
type: Array,
required: true,
},
preventDefaultMoveToGenerate: {
type: Boolean,
required: false,
default: false,
},
goToGenerateWorkflowPayload: {
required: false,
default: () => ({}),
},
workflows: {
type: Array,
required: true,
},
allowCreate: {
type: Boolean,
required: true,
},
relatedEntityClass: {
type: String,
required: true,
},
relatedEntityId: {
type: Number,
required: false,
},
workflowsAvailables: {
type: Array,
required: true,
},
preventDefaultMoveToGenerate: {
type: Boolean,
required: false,
default: false,
},
goToGenerateWorkflowPayload: {
required: false,
default: () => ({}),
},
});
// Define emits
@@ -87,8 +91,8 @@ const emit = defineEmits(["goToGenerateWorkflow"]);
// Reactive data
const modal = ref({
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl",
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl",
});
// Computed properties

View File

@@ -1,82 +1,89 @@
<template>
<template v-if="props.workflowsAvailables.length >= 1">
<div
v-if="countExistingWorkflows == 0 || embeddedWithinListModal"
class="dropdown d-grid gap-2"
>
<button
class="btn btn-primary dropdown-toggle"
type="button"
id="createWorkflowButton"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Créer un workflow
</button>
<ul class="dropdown-menu" aria-labelledby="createWorkflowButton">
<li v-for="w in props.workflowsAvailables" :key="w.name">
<button
class="dropdown-item"
type="button"
@click.prevent="goToGenerateWorkflow($event, w.name)"
>
{{ w.text }}
</button>
</li>
</ul>
</div>
<div v-else>
<div class="btn-group">
<button @click="emit('clickOpenList')" class="btn btn-primary">
<template v-if="countExistingWorkflows === 1">
1 workflow associé
</template>
<template v-else>
{{ countExistingWorkflows }} workflows associés
</template>
</button>
<button
class="btn btn-primary dropdown-toggle dropdown-toggle-split"
type="button"
id="createWorkflowButton"
data-bs-toggle="dropdown"
aria-expanded="false"
<template v-if="props.workflowsAvailables.length >= 1">
<div
v-if="countExistingWorkflows == 0 || embeddedWithinListModal"
class="dropdown d-grid gap-2"
>
<span class="visually-hidden">Liste des workflows disponibles</span>
</button>
<ul class="dropdown-menu" aria-labelledby="createWorkflowButton">
<li v-for="w in props.workflowsAvailables" :key="w.name">
<button
class="dropdown-item"
type="button"
@click.prevent="goToGenerateWorkflow($event, w.name)"
class="btn btn-primary dropdown-toggle"
type="button"
id="createWorkflowButton"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{{ w.text }}
Créer un workflow
</button>
</li>
</ul>
</div>
</div>
</template>
<template v-else>
<div v-if="countExistingWorkflows > 0" class="dropdown d-grid gap-2">
<button
@click="emit('clickOpenList')"
class="btn btn-primary"
type="button"
id="createWorkflowButton"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<template v-if="countExistingWorkflows === 1">
1 workflow associé
</template>
<template v-else>
{{ countExistingWorkflows }} workflows associés
</template>
</button>
</div>
</template>
<ul class="dropdown-menu" aria-labelledby="createWorkflowButton">
<li v-for="w in props.workflowsAvailables" :key="w.name">
<button
class="dropdown-item"
type="button"
@click.prevent="goToGenerateWorkflow($event, w.name)"
>
{{ w.text }}
</button>
</li>
</ul>
</div>
<div v-else>
<div class="btn-group">
<button @click="emit('clickOpenList')" class="btn btn-primary">
<template v-if="countExistingWorkflows === 1">
1 workflow associé
</template>
<template v-else>
{{ countExistingWorkflows }} workflows associés
</template>
</button>
<button
class="btn btn-primary dropdown-toggle dropdown-toggle-split"
type="button"
id="createWorkflowButton"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span class="visually-hidden"
>Liste des workflows disponibles</span
>
</button>
<ul
class="dropdown-menu"
aria-labelledby="createWorkflowButton"
>
<li v-for="w in props.workflowsAvailables" :key="w.name">
<button
class="dropdown-item"
type="button"
@click.prevent="
goToGenerateWorkflow($event, w.name)
"
>
{{ w.text }}
</button>
</li>
</ul>
</div>
</div>
</template>
<template v-else>
<div v-if="countExistingWorkflows > 0" class="dropdown d-grid gap-2">
<button
@click="emit('clickOpenList')"
class="btn btn-primary"
type="button"
id="createWorkflowButton"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<template v-if="countExistingWorkflows === 1">
1 workflow associé
</template>
<template v-else>
{{ countExistingWorkflows }} workflows associés
</template>
</button>
</div>
</template>
</template>
<script setup lang="ts">
@@ -84,85 +91,85 @@ import { buildLinkCreate } from "../../../lib/entity-workflow/api";
import { WorkflowAvailable } from "../../../types";
interface PickWorkflowConfig {
relatedEntityClass: string;
/**
* Represents the identifier of a related entity.
* This variable can store either a numerical value representing the entity's ID or an undefined value
* if the related entity is not specified or available, for instance when the entity is created within
* the interface and the id will be available later
*/
relatedEntityId: number | undefined;
workflowsAvailables: WorkflowAvailable[];
preventDefaultMoveToGenerate: boolean;
goToGenerateWorkflowPayload: object;
countExistingWorkflows: number;
/**
* if true, this button will not present a splitted button
*/
embeddedWithinListModal: boolean;
relatedEntityClass: string;
/**
* Represents the identifier of a related entity.
* This variable can store either a numerical value representing the entity's ID or an undefined value
* if the related entity is not specified or available, for instance when the entity is created within
* the interface and the id will be available later
*/
relatedEntityId: number | undefined;
workflowsAvailables: WorkflowAvailable[];
preventDefaultMoveToGenerate: boolean;
goToGenerateWorkflowPayload: object;
countExistingWorkflows: number;
/**
* if true, this button will not present a splitted button
*/
embeddedWithinListModal: boolean;
}
const props = withDefaults(defineProps<PickWorkflowConfig>(), {
preventDefaultMoveToGenerate: false,
goToGenerateWorkflowPayload: {},
allowCreateWorkflow: false,
preventDefaultMoveToGenerate: false,
goToGenerateWorkflowPayload: {},
allowCreateWorkflow: false,
});
const emit = defineEmits<{
(
e: "goToGenerateWorkflow",
{
event: MouseEvent,
workflowName: string,
isLinkValid: boolean,
link: string,
payload: object,
},
): void;
(e: "clickOpenList"): void;
(
e: "goToGenerateWorkflow",
{
event: MouseEvent,
workflowName: string,
isLinkValid: boolean,
link: string,
payload: object,
},
): void;
(e: "clickOpenList"): void;
}>();
const makeLink = (workflowName: string): string =>
buildLinkCreate(
workflowName,
props.relatedEntityClass,
props.relatedEntityId,
);
buildLinkCreate(
workflowName,
props.relatedEntityClass,
props.relatedEntityId,
);
const goToGenerateWorkflow = (
event: MouseEvent,
workflowName: string,
event: MouseEvent,
workflowName: string,
): void => {
console.log("goToGenerateWorkflow", event, workflowName);
let link = "";
let isLinkValid = false;
console.log("goToGenerateWorkflow", event, workflowName);
let link = "";
let isLinkValid = false;
try {
link = makeLink(workflowName);
isLinkValid = true;
} catch (e) {
console.info(
"could not generate link to create workflow, maybe the relatedEntityId is not yet known",
e,
);
}
try {
link = makeLink(workflowName);
isLinkValid = true;
} catch (e) {
console.info(
"could not generate link to create workflow, maybe the relatedEntityId is not yet known",
e,
);
}
if (!props.preventDefaultMoveToGenerate) {
window.location.assign(link);
}
if (!props.preventDefaultMoveToGenerate) {
window.location.assign(link);
}
emit("goToGenerateWorkflow", {
event,
workflowName,
link,
isLinkValid,
payload: props.goToGenerateWorkflowPayload,
});
emit("goToGenerateWorkflow", {
event,
workflowName,
link,
isLinkValid,
payload: props.goToGenerateWorkflowPayload,
});
};
const goToDuplicateRelatedEntity = (
event: MouseEvent,
workflowName: string,
event: MouseEvent,
workflowName: string,
): void => {};
</script>

View File

@@ -1,39 +1,42 @@
<template>
<transition name="modal">
<div class="modal-mask" v-if="show">
<!-- :: styles bootstrap :: -->
<div
class="modal fade show"
style="display: block"
aria-modal="true"
role="dialog"
>
<div class="modal-dialog" :class="modalDialogClass || {}">
<div class="modal-content">
<div class="modal-header">
<slot name="header"></slot>
<button class="close btn" @click="emits('close')">
<i class="fa fa-times" aria-hidden="true"></i>
</button>
<transition name="modal">
<div class="modal-mask">
<!-- :: styles bootstrap :: -->
<div
class="modal fade show"
style="display: block"
aria-modal="true"
role="dialog"
>
<div class="modal-dialog" :class="props.modalDialogClass || {}">
<div class="modal-content">
<div class="modal-header">
<slot name="header"></slot>
<button class="close btn" @click="emits('close')">
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</div>
<div class="modal-body">
<div class="body-head">
<slot name="body-head"></slot>
</div>
<slot name="body"></slot>
</div>
<div class="modal-footer" v-if="!hideFooter">
<button
class="btn btn-cancel"
@click="emits('close')"
>
{{ trans(MODAL_ACTION_CLOSE) }}
</button>
<slot name="footer"></slot>
</div>
</div>
</div>
</div>
<div class="modal-body">
<div class="body-head">
<slot name="body-head"></slot>
</div>
<slot name="body"></slot>
</div>
<div class="modal-footer" v-if="!hideFooter">
<button class="btn btn-cancel" @click="emits('close')">
{{ trans(MODAL_ACTION_CLOSE) }}
</button>
<slot name="footer"></slot>
</div>
</div>
<!-- :: end styles bootstrap :: -->
</div>
</div>
<!-- :: end styles bootstrap :: -->
</div>
</transition>
</transition>
</template>
<script lang="ts" setup>
@@ -50,27 +53,18 @@ import { trans, MODAL_ACTION_CLOSE } from "translator";
import { defineProps } from "vue";
export interface ModalProps {
modalDialogClass: string;
hideFooter: boolean;
modalDialogClass: object | null;
hideFooter: boolean;
}
defineProps({
modalDialogClass: {
type: String,
default: "",
},
hideFooter: {
type: Boolean,
default: false,
},
show: {
type: Boolean,
default: true,
},
// Define the props
const props = withDefaults(defineProps<ModalProps>(), {
hideFooter: false,
modalDialogClass: null,
});
const emits = defineEmits<{
close: [];
close: [];
}>();
</script>
@@ -79,17 +73,17 @@ const emits = defineEmits<{
* This is a mask behind the modal.
*/
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.75);
transition: opacity 0.3s ease;
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.75);
transition: opacity 0.3s ease;
}
.modal-header .close {
border-top-right-radius: 0.3rem;
border-top-right-radius: 0.3rem;
}
/*
* The following styles are auto-applied to elements with
@@ -100,23 +94,23 @@ const emits = defineEmits<{
* these styles.
*/
.modal-enter {
opacity: 0;
opacity: 0;
}
.modal-leave-active {
opacity: 0;
opacity: 0;
}
.modal-enter .modal-container,
.modal-leave-active .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
h3.modal-title {
font-size: 1.5rem;
font-weight: bold;
font-size: 1.5rem;
font-weight: bold;
}
div.modal-footer {
button:first-child {
margin-right: auto;
}
button:first-child {
margin-right: auto;
}
}
</style>

View File

@@ -1,17 +1,17 @@
<template>
<div>
<button
v-if="idsMarkedAsRead.length === 0"
class="btn btn-primary"
type="button"
@click="markAllRead"
>
<i class="fa fa-sm fa-envelope-open-o"></i> Marquer tout comme lu
</button>
<button v-else class="btn btn-primary" type="button" @click="undo">
<i class="fa fa-sm fa-envelope-open-o"></i> Annuler
</button>
</div>
<div>
<button
v-if="idsMarkedAsRead.length === 0"
class="btn btn-primary"
type="button"
@click="markAllRead"
>
<i class="fa fa-sm fa-envelope-open-o"></i> Marquer tout comme lu
</button>
<button v-else class="btn btn-primary" type="button" @click="undo">
<i class="fa fa-sm fa-envelope-open-o"></i> Annuler
</button>
</div>
</template>
<script lang="ts" setup>
@@ -19,36 +19,36 @@ import { makeFetch } from "../../../lib/api/apiMethods";
import { ref } from "vue";
const emit = defineEmits<{
(e: "markAsRead", id: number): void;
(e: "markAsUnRead", id: number): void;
(e: "markAsRead", id: number): void;
(e: "markAsUnRead", id: number): void;
}>();
const idsMarkedAsRead = ref([] as number[]);
async function markAllRead() {
const ids: number[] = await makeFetch(
"POST",
`/api/1.0/main/notification/mark/allread`,
null,
);
for (let i of ids) {
idsMarkedAsRead.value.push(i);
emit("markAsRead", i);
}
const ids: number[] = await makeFetch(
"POST",
`/api/1.0/main/notification/mark/allread`,
null,
);
for (let i of ids) {
idsMarkedAsRead.value.push(i);
emit("markAsRead", i);
}
}
async function undo() {
const touched: number[] = await makeFetch(
"POST",
`/api/1.0/main/notification/mark/undoallread`,
idsMarkedAsRead.value,
);
while (idsMarkedAsRead.value.length > 0) {
idsMarkedAsRead.value.pop();
}
for (let t of touched) {
emit("markAsUnRead", t);
}
const touched: number[] = await makeFetch(
"POST",
`/api/1.0/main/notification/mark/undoallread`,
idsMarkedAsRead.value,
);
while (idsMarkedAsRead.value.length > 0) {
idsMarkedAsRead.value.pop();
}
for (let t of touched) {
emit("markAsUnRead", t);
}
}
</script>

View File

@@ -1,96 +1,96 @@
<template>
<div
:class="{ 'btn-group btn-group-sm float-end': isButtonGroup }"
role="group"
aria-label="Notification actions"
>
<button
v-if="isRead"
class="btn"
:class="overrideClass"
type="button"
:title="trans(NOTIFICATION_MARK_AS_UNREAD)"
@click="markAsUnread"
<div
:class="{ 'btn-group btn-group-sm float-end': isButtonGroup }"
role="group"
aria-label="Notification actions"
>
<i class="fa fa-sm fa-envelope-o"></i>
<span v-if="!props.buttonNoText" class="ps-2">
{{ trans(NOTIFICATION_MARK_AS_UNREAD) }}
</span>
</button>
<button
v-if="isRead"
class="btn"
:class="overrideClass"
type="button"
:title="trans(NOTIFICATION_MARK_AS_UNREAD)"
@click="markAsUnread"
>
<i class="fa fa-sm fa-envelope-o"></i>
<span v-if="!props.buttonNoText" class="ps-2">
{{ trans(NOTIFICATION_MARK_AS_UNREAD) }}
</span>
</button>
<button
v-if="!isRead"
class="btn"
:class="overrideClass"
type="button"
:title="trans(NOTIFICATION_MARK_AS_READ)"
@click="markAsRead"
>
<i class="fa fa-sm fa-envelope-open-o"></i>
<span v-if="!buttonNoText" class="ps-2">
{{ trans(NOTIFICATION_MARK_AS_READ) }}
</span>
</button>
<button
v-if="!isRead"
class="btn"
:class="overrideClass"
type="button"
:title="trans(NOTIFICATION_MARK_AS_READ)"
@click="markAsRead"
>
<i class="fa fa-sm fa-envelope-open-o"></i>
<span v-if="!buttonNoText" class="ps-2">
{{ trans(NOTIFICATION_MARK_AS_READ) }}
</span>
</button>
<a
v-if="isButtonGroup"
type="button"
class="btn btn-outline-primary"
:href="showUrl"
:title="trans(SEE)"
>
<i class="fa fa-sm fa-comment-o"></i>
</a>
<a
v-if="isButtonGroup"
type="button"
class="btn btn-outline-primary"
:href="showUrl"
:title="trans(SEE)"
>
<i class="fa fa-sm fa-comment-o"></i>
</a>
<!-- "Mark All Read" button -->
<button
v-if="showMarkAllButton"
class="btn"
:class="overrideClass"
type="button"
:title="$t('markAllRead')"
@click="markAllRead"
>
<i class="fa fa-sm fa-envelope-o"></i>
<span v-if="!buttonNoText" class="ps-2">
{{ $t("markAllRead") }}
</span>
</button>
</div>
<!-- "Mark All Read" button -->
<button
v-if="showMarkAllButton"
class="btn"
:class="overrideClass"
type="button"
:title="$t('markAllRead')"
@click="markAllRead"
>
<i class="fa fa-sm fa-envelope-o"></i>
<span v-if="!buttonNoText" class="ps-2">
{{ $t("markAllRead") }}
</span>
</button>
</div>
</template>
<script setup>
import { computed } from "vue";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
import {
trans,
NOTIFICATION_MARK_AS_READ,
NOTIFICATION_MARK_AS_UNREAD,
SEE,
trans,
NOTIFICATION_MARK_AS_READ,
NOTIFICATION_MARK_AS_UNREAD,
SEE,
} from "translator";
// Props
const props = defineProps({
isRead: {
type: Boolean,
required: true,
},
notificationId: {
type: Number,
required: true,
},
buttonClass: {
type: String,
required: false,
},
buttonNoText: {
type: Boolean,
required: false,
},
showUrl: {
type: String,
required: false,
},
isRead: {
type: Boolean,
required: true,
},
notificationId: {
type: Number,
required: true,
},
buttonClass: {
type: String,
required: false,
},
buttonNoText: {
type: Boolean,
required: false,
},
showUrl: {
type: String,
required: false,
},
});
// Emits
@@ -102,23 +102,23 @@ const isButtonGroup = computed(() => props.showUrl);
// Methods
const markAsUnread = () => {
makeFetch(
"POST",
`/api/1.0/main/notification/${props.notificationId}/mark/unread`,
[],
).then(() => {
emit("markRead", { notificationId: props.notificationId });
});
makeFetch(
"POST",
`/api/1.0/main/notification/${props.notificationId}/mark/unread`,
[],
).then(() => {
emit("markRead", { notificationId: props.notificationId });
});
};
const markAsRead = () => {
makeFetch(
"POST",
`/api/1.0/main/notification/${props.notificationId}/mark/read`,
[],
).then(() => {
emit("markUnread", { notificationId: props.notificationId });
});
makeFetch(
"POST",
`/api/1.0/main/notification/${props.notificationId}/mark/read`,
[],
).then(() => {
emit("markUnread", { notificationId: props.notificationId });
});
};
</script>

View File

@@ -2,86 +2,87 @@ import { createI18n } from "vue-i18n";
import datetimeFormats from "../i18n/datetimeFormats";
const messages = {
fr: {
action: {
actions: "Actions",
show: "Voir",
edit: "Modifier",
create: "Créer",
remove: "Enlever",
delete: "Supprimer",
save: "Enregistrer",
valid: "Valider",
valid_and_see: "Valider et voir",
add: "Ajouter",
show_modal: "Ouvrir une modale",
ok: "OK",
cancel: "Annuler",
close: "Fermer",
back: "Retour",
check_all: "cocher tout",
reset: "réinitialiser",
redirect: {
person: "Quitter la page et ouvrir la fiche de l'usager",
thirdparty: "Quitter la page et voir le tiers",
},
refresh: "Rafraîchir",
addContact: "Ajouter un contact",
fr: {
action: {
actions: "Actions",
show: "Voir",
edit: "Modifier",
create: "Créer",
remove: "Enlever",
delete: "Supprimer",
save: "Enregistrer",
valid: "Valider",
valid_and_see: "Valider et voir",
add: "Ajouter",
show_modal: "Ouvrir une modale",
ok: "OK",
cancel: "Annuler",
close: "Fermer",
back: "Retour",
check_all: "cocher tout",
reset: "réinitialiser",
redirect: {
person: "Quitter la page et ouvrir la fiche de l'usager",
thirdparty: "Quitter la page et voir le tiers",
},
refresh: "Rafraîchir",
addContact: "Ajouter un contact",
},
nav: {
next: "Suivant",
previous: "Précédent",
top: "Haut",
bottom: "Bas",
},
renderbox: {
person: "Usager",
birthday: {
man: "Né le",
woman: "Née le",
neutral: "Né·e le",
unknown: "Né·e le",
},
deathdate: "Date de décès",
household_without_address: "Le ménage de l'usager est sans adresse",
no_data: "Aucune information renseignée",
type: {
thirdparty: "Tiers",
person: "Usager",
},
holder: "Titulaire",
years_old: "1 an | {n} an | {n} ans",
residential_address: "Adresse de résidence",
located_at: "réside chez",
},
},
nav: {
next: "Suivant",
previous: "Précédent",
top: "Haut",
bottom: "Bas",
},
renderbox: {
person: "Usager",
birthday: {
man: "Né le",
woman: "Née le",
neutral: "Né·e le",
unknown: "Né·e le",
},
deathdate: "Date de décès",
household_without_address: "Le ménage de l'usager est sans adresse",
no_data: "Aucune information renseignée",
type: {
thirdparty: "Tiers",
person: "Usager",
},
holder: "Titulaire",
years_old: "1 an | {n} an | {n} ans",
residential_address: "Adresse de résidence",
located_at: "réside chez",
},
},
};
const _createI18n = (appMessages: any, legacy?: boolean) => {
Object.assign(messages.fr, appMessages.fr);
return createI18n({
legacy: typeof legacy === undefined ? true : legacy,
locale: "fr",
fallbackLocale: "fr",
// @ts-ignore
datetimeFormats,
messages,
});
Object.assign(messages.fr, appMessages.fr);
return createI18n({
legacy: typeof legacy === undefined ? true : legacy,
locale: "fr",
fallbackLocale: "fr",
// @ts-ignore
datetimeFormats,
messages,
});
};
export { _createI18n };
export const multiSelectMessages = {
fr: {
multiselect: {
placeholder: "Choisir",
tag_placeholder: "Créer un nouvel élément",
select_label: '"Entrée" ou cliquez pour sélectionner',
deselect_label: '"Entrée" ou cliquez pour désélectionner',
select_group_label: 'Appuyer sur "Entrée" pour sélectionner ce groupe',
deselect_group_label:
'Appuyer sur "Entrée" pour désélectionner ce groupe',
selected_label: "Sélectionné",
fr: {
multiselect: {
placeholder: "Choisir",
tag_placeholder: "Créer un nouvel élément",
select_label: '"Entrée" ou cliquez pour sélectionner',
deselect_label: '"Entrée" ou cliquez pour désélectionner',
select_group_label:
'Appuyer sur "Entrée" pour sélectionner ce groupe',
deselect_group_label:
'Appuyer sur "Entrée" pour désélectionner ce groupe',
selected_label: "Sélectionné",
},
},
},
};

View File

@@ -1,27 +1,27 @@
export default {
fr: {
short: {
year: "numeric",
month: "numeric",
day: "numeric",
fr: {
short: {
year: "numeric",
month: "numeric",
day: "numeric",
},
text: {
year: "numeric",
month: "long",
day: "numeric",
},
long: {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
hour12: false,
},
hoursOnly: {
hour: "numeric",
minute: "numeric",
hour12: false,
},
},
text: {
year: "numeric",
month: "long",
day: "numeric",
},
long: {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
hour12: false,
},
hoursOnly: {
hour: "numeric",
minute: "numeric",
hour12: false,
},
},
};