Merge branch 'ticket-app-master' into 1849-1848-1920-1921-fix-bugs

This commit is contained in:
Boris Waaub
2025-12-09 17:21:13 +01:00
69 changed files with 4643 additions and 4700 deletions

View File

@@ -11,7 +11,6 @@
"@hotwired/stimulus": "^3.0.0",
"@luminateone/eslint-baseline": "^1.0.9",
"@symfony/stimulus-bridge": "^3.2.0",
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
"@symfony/webpack-encore": "^4.1.0",
"@tsconfig/node20": "^20.1.4",
"@types/dompurify": "^3.0.5",

View File

@@ -1,103 +1,98 @@
<template>
<teleport to="#social-issues-acc">
<div class="mb-3 row">
<div class="col-4">
<label :class="socialIssuesClassList">{{
trans(ACTIVITY_SOCIAL_ISSUES)
}}</label>
</div>
<div class="col-8">
<check-social-issue
v-for="issue in socialIssuesList"
:key="issue.id"
:issue="issue"
:selection="socialIssuesSelected"
@updateSelected="updateIssuesSelected"
>
</check-social-issue>
<teleport to="#social-issues-acc">
<div class="mb-3 row">
<div class="col-4">
<label :class="socialIssuesClassList">{{
trans(ACTIVITY_SOCIAL_ISSUES)
}}</label>
</div>
<div class="col-8">
<check-social-issue
v-for="issue in socialIssuesList"
:key="issue.id"
:issue="issue"
:selection="socialIssuesSelected"
@updateSelected="updateIssuesSelected"
>
</check-social-issue>
<div class="my-3">
<VueMultiselect
name="otherIssues"
label="text"
track-by="id"
open-direction="bottom"
:close-on-select="true"
:preserve-search="false"
:reset-after="true"
:hide-selected="true"
:taggable="false"
:multiple="false"
:searchable="true"
:allow-empty="true"
:show-labels="false"
:loading="issueIsLoading"
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
:options="socialIssuesOther"
@select="addIssueInList"
>
</VueMultiselect>
</div>
</div>
<div class="my-3">
<VueMultiselect
name="otherIssues"
label="text"
track-by="id"
open-direction="bottom"
:close-on-select="true"
:preserve-search="false"
:reset-after="true"
:hide-selected="true"
:taggable="false"
:multiple="false"
:searchable="true"
:allow-empty="true"
:show-labels="false"
:loading="issueIsLoading"
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
:options="socialIssuesOther"
@select="addIssueInList"
>
</VueMultiselect>
</div>
</div>
</div>
<div class="mb-3 row">
<div class="col-4">
<label :class="socialActionsClassList">{{
trans(ACTIVITY_SOCIAL_ACTIONS)
}}</label>
</div>
<div class="col-8">
<div v-if="actionIsLoading === true">
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg"></i>
</div>
<div class="mb-3 row">
<div class="col-4">
<label :class="socialActionsClassList">{{
trans(ACTIVITY_SOCIAL_ACTIONS)
}}</label>
</div>
<div class="col-8">
<div v-if="actionIsLoading === true">
<i
class="chill-green fa fa-circle-o-notch fa-spin fa-lg"
></i>
</div>
<span
v-else-if="socialIssuesSelected.length === 0"
class="inline-choice chill-no-data-statement mt-3"
>
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
</span>
<span
v-else-if="socialIssuesSelected.length === 0"
class="inline-choice chill-no-data-statement mt-3"
>
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
</span>
<template
v-else-if="
socialActionsList.length > 0 &&
(socialIssuesSelected.length || socialActionsSelected.length)
"
>
<div
id="actionsList"
v-for="group in socialActionsList"
:key="group.issue"
>
<span class="badge bg-chill-l-gray text-dark">{{
group.issue
}}</span>
<check-social-action
v-for="action in group.actions"
:key="action.id"
:action="action"
:selection="socialActionsSelected"
@updateSelected="updateActionsSelected"
>
</check-social-action>
</div>
</template>
<template
v-else-if="
socialActionsList.length > 0 &&
(socialIssuesSelected.length ||
socialActionsSelected.length)
"
>
<div
id="actionsList"
v-for="group in socialActionsList"
:key="group.issue"
>
<span class="badge bg-chill-l-gray text-dark">{{
group.issue
}}</span>
<check-social-action
v-for="action in group.actions"
:key="action.id"
:action="action"
:selection="socialActionsSelected"
@updateSelected="updateActionsSelected"
>
</check-social-action>
</div>
</template>
<span
v-else-if="
actionAreLoaded && socialActionsList.length === 0
"
class="inline-choice chill-no-data-statement mt-3"
>
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
</span>
</div>
</div>
</teleport>
<span
v-else-if="actionAreLoaded && socialActionsList.length === 0"
class="inline-choice chill-no-data-statement mt-3"
>
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
</span>
</div>
</div>
</teleport>
</template>
<script>
@@ -106,175 +101,174 @@ import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
import {
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
ACTIVITY_SOCIAL_ACTIONS,
ACTIVITY_SOCIAL_ISSUES,
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
trans,
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
ACTIVITY_SOCIAL_ACTIONS,
ACTIVITY_SOCIAL_ISSUES,
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
trans,
} from "translator";
export default {
name: "SocialIssuesAcc",
components: {
CheckSocialIssue,
CheckSocialAction,
VueMultiselect,
name: "SocialIssuesAcc",
components: {
CheckSocialIssue,
CheckSocialAction,
VueMultiselect,
},
setup() {
return {
trans,
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
ACTIVITY_SOCIAL_ACTIONS,
ACTIVITY_SOCIAL_ISSUES,
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
};
},
data() {
return {
issueIsLoading: false,
actionIsLoading: false,
actionAreLoaded: false,
socialIssuesClassList: {
"col-form-label": true,
required: false,
},
socialActionsClassList: {
"col-form-label": true,
required: false,
},
};
},
computed: {
socialIssuesList() {
return this.$store.state.activity.accompanyingPeriod.socialIssues;
},
setup() {
return {
trans,
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
ACTIVITY_SOCIAL_ACTIONS,
ACTIVITY_SOCIAL_ISSUES,
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
};
socialIssuesSelected() {
return this.$store.state.activity.socialIssues;
},
data() {
return {
issueIsLoading: false,
actionIsLoading: false,
actionAreLoaded: false,
socialIssuesClassList: {
"col-form-label": true,
required: false,
},
socialActionsClassList: {
"col-form-label": true,
required: false,
},
};
socialIssuesOther() {
return this.$store.state.socialIssuesOther;
},
computed: {
socialIssuesList() {
return this.$store.state.activity.accompanyingPeriod.socialIssues;
},
socialIssuesSelected() {
return this.$store.state.activity.socialIssues;
},
socialIssuesOther() {
return this.$store.state.socialIssuesOther;
},
socialActionsList() {
return this.$store.getters.socialActionsListSorted;
},
socialActionsSelected() {
return this.$store.state.activity.socialActions;
},
socialActionsList() {
return this.$store.getters.socialActionsListSorted;
},
mounted() {
/* Load classNames after element is present */
const socialActionsEl = document.querySelector(
"input#chill_activitybundle_activity_socialActions",
);
if (socialActionsEl && socialActionsEl.hasAttribute("required")) {
this.socialActionsClassList.required = true;
socialActionsSelected() {
return this.$store.state.activity.socialActions;
},
},
mounted() {
/* Load classNames after element is present */
const socialActionsEl = document.querySelector(
"input#chill_activitybundle_activity_socialActions",
);
if (socialActionsEl && socialActionsEl.hasAttribute("required")) {
this.socialActionsClassList.required = true;
}
const socialIssuesEl = document.querySelector(
"input#chill_activitybundle_activity_socialIssues",
);
if (socialIssuesEl && socialIssuesEl.hasAttribute("required")) {
this.socialIssuesClassList.required = true;
}
/* Load other issues in multiselect */
this.issueIsLoading = true;
this.actionAreLoaded = false;
getSocialIssues().then((response) => {
/* Add issues to the store */
this.$store.commit("updateIssuesOther", response);
/* Add in list the issues already associated (if not yet listed) */
this.socialIssuesSelected.forEach((issue) => {
if (
this.socialIssuesList.filter((i) => i.id === issue.id).length !== 1
) {
this.$store.commit("addIssueInList", issue);
}
});
const socialIssuesEl = document.querySelector(
"input#chill_activitybundle_activity_socialIssues",
);
if (socialIssuesEl && socialIssuesEl.hasAttribute("required")) {
this.socialIssuesClassList.required = true;
}
/* Remove from multiselect the issues that are not yet in the checkbox list */
this.socialIssuesList.forEach((issue) => {
this.$store.commit("removeIssueInOther", issue);
});
/* Load other issues in multiselect */
this.issueIsLoading = true;
this.actionAreLoaded = false;
/* Filter issues */
this.$store.commit("filterList", "issues");
getSocialIssues().then((response) => {
/* Add issues to the store */
this.$store.commit("updateIssuesOther", response);
/* Add in list the actions already associated (if not yet listed) */
this.socialActionsSelected.forEach((action) => {
this.$store.commit("addActionInList", action);
});
/* Add in list the issues already associated (if not yet listed) */
this.socialIssuesSelected.forEach((issue) => {
if (
this.socialIssuesList.filter((i) => i.id === issue.id)
.length !== 1
) {
this.$store.commit("addIssueInList", issue);
}
});
/* Filter actions */
this.$store.commit("filterList", "actions");
/* Remove from multiselect the issues that are not yet in the checkbox list */
this.socialIssuesList.forEach((issue) => {
this.$store.commit("removeIssueInOther", issue);
});
/* Filter issues */
this.$store.commit("filterList", "issues");
/* Add in list the actions already associated (if not yet listed) */
this.socialActionsSelected.forEach((action) => {
this.$store.commit("addActionInList", action);
});
/* Filter actions */
this.$store.commit("filterList", "actions");
this.issueIsLoading = false;
this.actionAreLoaded = true;
this.updateActionsList();
});
},
methods: {
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
this.issueIsLoading = false;
this.actionAreLoaded = true;
this.updateActionsList();
});
},
methods: {
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
remove it from multiselect, and add socialActions concerned
*/
addIssueInList(value) {
//console.log('addIssueInList', value);
this.$store.commit("addIssueInList", value);
this.$store.commit("removeIssueInOther", value);
this.$store.dispatch("addIssueSelected", value);
this.updateActionsList();
},
/* Update value for selected issues checkboxes
*/
updateIssuesSelected(issues) {
//console.log('updateIssuesSelected', issues);
this.$store.dispatch("updateIssuesSelected", issues);
this.updateActionsList();
},
/* Update value for selected actions checkboxes
*/
updateActionsSelected(actions) {
//console.log('updateActionsSelected', actions);
this.$store.dispatch("updateActionsSelected", actions);
},
/* Add socialActions concerned: after reset, loop on each issue selected
addIssueInList(value) {
//console.log('addIssueInList', value);
this.$store.commit("addIssueInList", value);
this.$store.commit("removeIssueInOther", value);
this.$store.dispatch("addIssueSelected", value);
this.updateActionsList();
},
/* Update value for selected issues checkboxes
*/
updateIssuesSelected(issues) {
//console.log('updateIssuesSelected', issues);
this.$store.dispatch("updateIssuesSelected", issues);
this.updateActionsList();
},
/* Update value for selected actions checkboxes
*/
updateActionsSelected(actions) {
//console.log('updateActionsSelected', actions);
this.$store.dispatch("updateActionsSelected", actions);
},
/* Add socialActions concerned: after reset, loop on each issue selected
to get social actions concerned
*/
updateActionsList() {
this.resetActionsList();
this.socialIssuesSelected.forEach((item) => {
this.actionIsLoading = true;
getSocialActionByIssue(item.id).then(
(actions) =>
new Promise((resolve) => {
actions.results.forEach((action) => {
this.$store.commit("addActionInList", action);
}, this);
updateActionsList() {
this.resetActionsList();
this.socialIssuesSelected.forEach((item) => {
this.actionIsLoading = true;
getSocialActionByIssue(item.id).then(
(actions) =>
new Promise((resolve) => {
actions.results.forEach((action) => {
this.$store.commit("addActionInList", action);
}, this);
this.$store.commit("filterList", "actions");
this.$store.commit("filterList", "actions");
this.actionIsLoading = false;
this.actionAreLoaded = true;
resolve();
}),
);
}, this);
},
/* Reset socialActions List: flush list and restore selected actions
*/
resetActionsList() {
this.$store.commit("resetActionsList");
this.actionAreLoaded = false;
this.socialActionsSelected.forEach((item) => {
this.$store.commit("addActionInList", item);
}, this);
},
this.actionIsLoading = false;
this.actionAreLoaded = true;
resolve();
}),
);
}, this);
},
/* Reset socialActions List: flush list and restore selected actions
*/
resetActionsList() {
this.$store.commit("resetActionsList");
this.actionAreLoaded = false;
this.socialActionsSelected.forEach((item) => {
this.$store.commit("addActionInList", item);
}, this);
},
},
};
</script>
@@ -284,18 +278,18 @@ export default {
@import "ChillMainAssets/chill/scss/chill_variables";
span.multiselect__single {
display: none !important;
display: none !important;
}
#actionsList {
border-radius: 0.5rem;
padding: 1rem;
margin: 0.5rem;
background-color: whitesmoke;
border-radius: 0.5rem;
padding: 1rem;
margin: 0.5rem;
background-color: whitesmoke;
}
span.badge {
margin-bottom: 0.5rem;
@include badge_social($social-issue-color);
margin-bottom: 0.5rem;
@include badge_social($social-issue-color);
}
</style>

View File

@@ -44,20 +44,21 @@ span.badge {
@include badge_social($social-action-color);
font-size: 95%;
white-space: normal;
word-wrap: break-word;
word-break: break-word;
display: inline-block;
max-width: 100%;margin-bottom: 5px;
word-wrap: break-word;
word-break: break-word;
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
margin-right: 1em;
text-align: left;
line-height: 1.2em;
line-height: 1.2em;
&::before {
position: absolute;
left: 11px;
top: 0;
margin: 0 0.3em 0 -0.75em;
}
position: relative;
padding-left: 1.5em;
position: absolute;
left: 11px;
top: 0;
margin: 0 0.3em 0 -0.75em;
}
position: relative;
padding-left: 1.5em;
}
</style>

View File

@@ -42,19 +42,21 @@ span.badge {
@include badge_social($social-issue-color);
font-size: 95%;
white-space: normal;
word-wrap: break-word;
word-break: break-word;
display: inline-block;
max-width: 100%;margin-bottom: 5px;
margin-right: 1em;text-align: left;
word-wrap: break-word;
word-break: break-word;
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
margin-right: 1em;
text-align: left;
&::before {
position: absolute;
left: 11px;
top: 0;
margin: 0 0.3em 0 -0.75em;
}
position: relative;
padding-left: 1.5em;
&::before {
position: absolute;
left: 11px;
top: 0;
margin: 0 0.3em 0 -0.75em;
}
position: relative;
padding-left: 1.5em;
}
</style>

View File

@@ -1,166 +1,148 @@
<template>
<teleport to="#mainUser">
<h2 class="chill-red">Utilisateur principal</h2>
<div>
<div>
<div v-if="null !== this.$store.getters.getMainUser">
<calendar-active :user="this.$store.getters.getMainUser" />
</div>
<pick-entity
:multiple="false"
:types="['user']"
:uniqid="'main_user_calendar'"
:picked="
null !== this.$store.getters.getMainUser
? [this.$store.getters.getMainUser]
: []
"
:removable-if-set="false"
:display-picked="false"
:suggested="this.suggestedUsers"
:label="'main_user'"
@add-new-entity="setMainUser"
/>
</div>
<teleport to="#mainUser">
<h2 class="chill-red">Utilisateur principal</h2>
<div>
<div>
<div v-if="null !== this.$store.getters.getMainUser">
<calendar-active :user="this.$store.getters.getMainUser" />
</div>
</teleport>
<pick-entity
:multiple="false"
:types="['user']"
:uniqid="'main_user_calendar'"
:picked="
null !== this.$store.getters.getMainUser
? [this.$store.getters.getMainUser]
: []
"
:removable-if-set="false"
:display-picked="false"
:suggested="this.suggestedUsers"
:label="'main_user'"
@add-new-entity="setMainUser"
/>
</div>
</div>
</teleport>
<concerned-groups />
<concerned-groups />
<teleport to="#schedule">
<div class="row mb-3" v-if="activity.startDate !== null">
<label class="col-form-label col-sm-4">Date</label>
<div class="col-sm-8">
{{ $d(activity.startDate, "long") }} -
{{ $d(activity.endDate, "hoursOnly") }}
<span v-if="activity.calendarRange === null"
>(Pas de plage de disponibilité sélectionnée)</span
>
<span v-else>(Une plage de disponibilité sélectionnée)</span>
</div>
</div>
</teleport>
<location />
<teleport to="#fullCalendar">
<div class="calendar-actives">
<template v-for="u in getActiveUsers" :key="u.id">
<calendar-active
:user="u"
:invite="this.$store.getters.getInviteForUser(u)"
/>
</template>
</div>
<div
class="display-options row justify-content-between"
style="margin-top: 1rem"
<teleport to="#schedule">
<div class="row mb-3" v-if="activity.startDate !== null">
<label class="col-form-label col-sm-4">Date</label>
<div class="col-sm-8">
{{ $d(activity.startDate, "long") }} -
{{ $d(activity.endDate, "hoursOnly") }}
<span v-if="activity.calendarRange === null"
>(Pas de plage de disponibilité sélectionnée)</span
>
<div class="col-sm-9 col-xs-12">
<div class="input-group mb-3">
<label class="input-group-text" for="slotDuration"
>Durée des créneaux</label
>
<select
v-model="slotDuration"
id="slotDuration"
class="form-select"
>
<option value="00:05:00">5 minutes</option>
<option value="00:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option>
<option value="00:45:00">45 minutes</option>
<option value="00:60:00">60 minutes</option>
</select>
<label class="input-group-text" for="slotMinTime">De</label>
<select
v-model="slotMinTime"
id="slotMinTime"
class="form-select"
>
<option value="00:00:00">0h</option>
<option value="01:00:00">1h</option>
<option value="02:00:00">2h</option>
<option value="03:00:00">3h</option>
<option value="04:00:00">4h</option>
<option value="05:00:00">5h</option>
<option value="06:00:00">6h</option>
<option value="07:00:00">7h</option>
<option value="08:00:00">8h</option>
<option value="09:00:00">9h</option>
<option value="10:00:00">10h</option>
<option value="11:00:00">11h</option>
<option value="12:00:00">12h</option>
</select>
<label class="input-group-text" for="slotMaxTime">À</label>
<select
v-model="slotMaxTime"
id="slotMaxTime"
class="form-select"
>
<option value="12:00:00">12h</option>
<option value="13:00:00">13h</option>
<option value="14:00:00">14h</option>
<option value="15:00:00">15h</option>
<option value="16:00:00">16h</option>
<option value="17:00:00">17h</option>
<option value="18:00:00">18h</option>
<option value="19:00:00">19h</option>
<option value="20:00:00">20h</option>
<option value="21:00:00">21h</option>
<option value="22:00:00">22h</option>
<option value="23:00:00">23h</option>
<option value="23:59:59">24h</option>
</select>
</div>
</div>
<div class="col-sm-3 col-xs-12">
<div class="float-end">
<div class="form-check input-group">
<span class="input-group-text">
<input
id="showHideWE"
class="mt-0"
type="checkbox"
v-model="hideWeekends"
/>
</span>
<label
for="showHideWE"
class="form-check-label input-group-text"
>Week-ends</label
>
</div>
</div>
</div>
<span v-else>(Une plage de disponibilité sélectionnée)</span>
</div>
</div>
</teleport>
<location />
<teleport to="#fullCalendar">
<div class="calendar-actives">
<template v-for="u in getActiveUsers" :key="u.id">
<calendar-active
:user="u"
:invite="this.$store.getters.getInviteForUser(u)"
/>
</template>
</div>
<div
class="display-options row justify-content-between"
style="margin-top: 1rem"
>
<div class="col-sm-9 col-xs-12">
<div class="input-group mb-3">
<label class="input-group-text" for="slotDuration"
>Durée des créneaux</label
>
<select v-model="slotDuration" id="slotDuration" class="form-select">
<option value="00:05:00">5 minutes</option>
<option value="00:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option>
<option value="00:45:00">45 minutes</option>
<option value="00:60:00">60 minutes</option>
</select>
<label class="input-group-text" for="slotMinTime">De</label>
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
<option value="00:00:00">0h</option>
<option value="01:00:00">1h</option>
<option value="02:00:00">2h</option>
<option value="03:00:00">3h</option>
<option value="04:00:00">4h</option>
<option value="05:00:00">5h</option>
<option value="06:00:00">6h</option>
<option value="07:00:00">7h</option>
<option value="08:00:00">8h</option>
<option value="09:00:00">9h</option>
<option value="10:00:00">10h</option>
<option value="11:00:00">11h</option>
<option value="12:00:00">12h</option>
</select>
<label class="input-group-text" for="slotMaxTime">À</label>
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
<option value="12:00:00">12h</option>
<option value="13:00:00">13h</option>
<option value="14:00:00">14h</option>
<option value="15:00:00">15h</option>
<option value="16:00:00">16h</option>
<option value="17:00:00">17h</option>
<option value="18:00:00">18h</option>
<option value="19:00:00">19h</option>
<option value="20:00:00">20h</option>
<option value="21:00:00">21h</option>
<option value="22:00:00">22h</option>
<option value="23:00:00">23h</option>
<option value="23:59:59">24h</option>
</select>
</div>
<FullCalendar ref="fullCalendar" :options="calendarOptions">
<template #eventContent="arg">
<span>
<b v-if="arg.event.extendedProps.is === 'remote'">{{
arg.event.title
}}</b>
<b v-else-if="arg.event.extendedProps.is === 'range'"
>{{ arg.timeText }}
{{ arg.event.extendedProps.locationName }}
<small>{{
arg.event.extendedProps.userLabel
}}</small></b
>
<b v-else-if="arg.event.extendedProps.is === 'current'"
>{{ arg.timeText }} {{ $t("current_selected") }}
</b>
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
arg.event.title
}}</b>
<b v-else
>{{ arg.timeText }} {{ $t("current_selected") }}
</b>
</span>
</template>
</FullCalendar>
</teleport>
</div>
<div class="col-sm-3 col-xs-12">
<div class="float-end">
<div class="form-check input-group">
<span class="input-group-text">
<input
id="showHideWE"
class="mt-0"
type="checkbox"
v-model="hideWeekends"
/>
</span>
<label for="showHideWE" class="form-check-label input-group-text"
>Week-ends</label
>
</div>
</div>
</div>
</div>
<FullCalendar ref="fullCalendar" :options="calendarOptions">
<template #eventContent="arg">
<span>
<b v-if="arg.event.extendedProps.is === 'remote'">{{
arg.event.title
}}</b>
<b v-else-if="arg.event.extendedProps.is === 'range'"
>{{ arg.timeText }}
{{ arg.event.extendedProps.locationName }}
<small>{{ arg.event.extendedProps.userLabel }}</small></b
>
<b v-else-if="arg.event.extendedProps.is === 'current'"
>{{ arg.timeText }} {{ $t("current_selected") }}
</b>
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
arg.event.title
}}</b>
<b v-else>{{ arg.timeText }} {{ $t("current_selected") }} </b>
</span>
</template>
</FullCalendar>
</teleport>
</template>
<script>
@@ -177,219 +159,210 @@ import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
import { mapGetters, mapState } from "vuex";
export default {
name: "App",
components: {
ConcernedGroups,
Location,
FullCalendar,
CalendarActive,
PickEntity,
name: "App",
components: {
ConcernedGroups,
Location,
FullCalendar,
CalendarActive,
PickEntity,
},
data() {
return {
errorMsg: [],
showMyCalendar: false,
slotDuration: "00:05:00",
slotMinTime: "09:00:00",
slotMaxTime: "18:00:00",
hideWeekEnds: true,
previousUser: [],
};
},
computed: {
...mapGetters(["getMainUser"]),
...mapState(["activity"]),
events() {
return this.$store.getters.getEventSources;
},
data() {
return {
errorMsg: [],
showMyCalendar: false,
slotDuration: "00:05:00",
slotMinTime: "09:00:00",
slotMaxTime: "18:00:00",
hideWeekEnds: true,
previousUser: [],
};
calendarOptions() {
return {
locale: frLocale,
plugins: [
dayGridPlugin,
interactionPlugin,
timeGridPlugin,
dayGridPlugin,
listPlugin,
],
initialView: "timeGridWeek",
initialDate: this.$store.getters.getInitialDate,
eventSources: this.events,
selectable: true,
slotMinTime: this.slotMinTime,
slotMaxTime: this.slotMaxTime,
scrollTimeReset: false,
datesSet: this.onDatesSet,
select: this.onDateSelect,
eventChange: this.onEventChange,
eventClick: this.onEventClick,
selectMirror: true,
editable: true,
weekends: !this.hideWeekEnds,
headerToolbar: {
left: "prev,next today",
center: "title",
right: "timeGridWeek,timeGridDay,listWeek",
},
views: {
timeGrid: {
slotEventOverlap: false,
slotDuration: this.slotDuration,
},
},
};
},
computed: {
...mapGetters(["getMainUser"]),
...mapState(["activity"]),
events() {
return this.$store.getters.getEventSources;
},
calendarOptions() {
return {
locale: frLocale,
plugins: [
dayGridPlugin,
interactionPlugin,
timeGridPlugin,
dayGridPlugin,
listPlugin,
],
initialView: "timeGridWeek",
initialDate: this.$store.getters.getInitialDate,
eventSources: this.events,
selectable: true,
slotMinTime: this.slotMinTime,
slotMaxTime: this.slotMaxTime,
scrollTimeReset: false,
datesSet: this.onDatesSet,
select: this.onDateSelect,
eventChange: this.onEventChange,
eventClick: this.onEventClick,
selectMirror: true,
editable: true,
weekends: !this.hideWeekEnds,
headerToolbar: {
left: "prev,next today",
center: "title",
right: "timeGridWeek,timeGridDay,listWeek",
},
views: {
timeGrid: {
slotEventOverlap: false,
slotDuration: this.slotDuration,
},
},
};
},
getActiveUsers() {
const users = [];
for (const id of this.$store.state.currentView.users.keys()) {
users.push(this.$store.getters.getUserDataById(id).user);
}
return users;
},
suggestedUsers() {
const suggested = [];
this.$data.previousUser.forEach((u) => {
if (u.id !== this.$store.getters.getMainUser.id) {
suggested.push(u);
}
});
return suggested;
},
getActiveUsers() {
const users = [];
for (const id of this.$store.state.currentView.users.keys()) {
users.push(this.$store.getters.getUserDataById(id).user);
}
return users;
},
methods: {
setMainUser({ entity }) {
const user = entity;
console.log("setMainUser APP", entity);
suggestedUsers() {
const suggested = [];
if (
user.id !== this.$store.getters.getMainUser &&
(this.$store.state.activity.calendarRange !== null ||
this.$store.state.activity.startDate !== null ||
this.$store.state.activity.endDate !== null)
) {
if (
!window.confirm(
this.$t("change_main_user_will_reset_event_data"),
)
) {
return;
}
}
this.$data.previousUser.forEach((u) => {
if (u.id !== this.$store.getters.getMainUser.id) {
suggested.push(u);
}
});
// add the previous user, if any, in the previous user list (in use for suggestion)
if (null !== this.$store.getters.getMainUser) {
const suggestedUids = new Set(
this.$data.previousUser.map((u) => u.id),
);
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
this.$data.previousUser.push(
this.$store.getters.getMainUser,
);
}
}
this.$store.dispatch("setMainUser", user);
this.$store.commit("showUserOnCalendar", {
user,
ranges: true,
remotes: true,
});
},
removeMainUser(user) {
console.log("removeMainUser APP", user);
window.alert(this.$t("main_user_is_mandatory"));
return;
},
onDatesSet(event) {
console.log("onDatesSet", event);
this.$store.dispatch("setCurrentDatesView", {
start: event.start,
end: event.end,
});
},
onDateSelect(payload) {
console.log("onDateSelect", payload);
// show an alert if changing mainUser
if (
(this.$store.getters.getMainUser !== null &&
this.$store.state.me.id !==
this.$store.getters.getMainUser.id) ||
this.$store.getters.getMainUser === null
) {
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
return;
} else {
this.$store.commit("showUserOnCalendar", {
user: this.$store.state.me,
remotes: true,
ranges: true,
});
}
}
this.$store.dispatch("setEventTimes", {
start: payload.start,
end: payload.end,
});
},
onEventChange(payload) {
console.log("onEventChange", payload);
if (this.$store.state.activity.calendarRange !== null) {
throw new Error(
"not allowed to edit a calendar associated with a calendar range",
);
}
this.$store.dispatch("setEventTimes", {
start: payload.event.start,
end: payload.event.end,
});
},
onEventClick(payload) {
if (payload.event.extendedProps.is !== "range") {
// do nothing when clicking on remote
return;
}
// show an alert if changing mainUser
if (
this.$store.getters.getMainUser !== null &&
payload.event.extendedProps.userId !==
this.$store.getters.getMainUser.id
) {
if (
!window.confirm(
this.$t("this_calendar_range_will_change_main_user"),
)
) {
return;
}
}
this.$store.dispatch("associateCalendarToRange", {
range: payload.event,
});
},
return suggested;
},
},
methods: {
setMainUser({ entity }) {
const user = entity;
console.log("setMainUser APP", entity);
if (
user.id !== this.$store.getters.getMainUser &&
(this.$store.state.activity.calendarRange !== null ||
this.$store.state.activity.startDate !== null ||
this.$store.state.activity.endDate !== null)
) {
if (
!window.confirm(this.$t("change_main_user_will_reset_event_data"))
) {
return;
}
}
// add the previous user, if any, in the previous user list (in use for suggestion)
if (null !== this.$store.getters.getMainUser) {
const suggestedUids = new Set(this.$data.previousUser.map((u) => u.id));
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
this.$data.previousUser.push(this.$store.getters.getMainUser);
}
}
this.$store.dispatch("setMainUser", user);
this.$store.commit("showUserOnCalendar", {
user,
ranges: true,
remotes: true,
});
},
removeMainUser(user) {
console.log("removeMainUser APP", user);
window.alert(this.$t("main_user_is_mandatory"));
return;
},
onDatesSet(event) {
console.log("onDatesSet", event);
this.$store.dispatch("setCurrentDatesView", {
start: event.start,
end: event.end,
});
},
onDateSelect(payload) {
console.log("onDateSelect", payload);
// show an alert if changing mainUser
if (
(this.$store.getters.getMainUser !== null &&
this.$store.state.me.id !== this.$store.getters.getMainUser.id) ||
this.$store.getters.getMainUser === null
) {
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
return;
} else {
this.$store.commit("showUserOnCalendar", {
user: this.$store.state.me,
remotes: true,
ranges: true,
});
}
}
this.$store.dispatch("setEventTimes", {
start: payload.start,
end: payload.end,
});
},
onEventChange(payload) {
console.log("onEventChange", payload);
if (this.$store.state.activity.calendarRange !== null) {
throw new Error(
"not allowed to edit a calendar associated with a calendar range",
);
}
this.$store.dispatch("setEventTimes", {
start: payload.event.start,
end: payload.event.end,
});
},
onEventClick(payload) {
if (payload.event.extendedProps.is !== "range") {
// do nothing when clicking on remote
return;
}
// show an alert if changing mainUser
if (
this.$store.getters.getMainUser !== null &&
payload.event.extendedProps.userId !==
this.$store.getters.getMainUser.id
) {
if (
!window.confirm(this.$t("this_calendar_range_will_change_main_user"))
) {
return;
}
}
this.$store.dispatch("associateCalendarToRange", {
range: payload.event,
});
},
},
};
</script>
<style>
.calendar-actives {
display: flex;
flex-direction: row;
flex-wrap: wrap;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.display-options {
margin-top: 1rem;
margin-top: 1rem;
}
/* for events which are range */
.fc-event.isrange {
border-width: 3px;
border-width: 3px;
}
</style>

View File

@@ -1,231 +1,185 @@
<template>
<div class="row">
<div class="col-sm">
<label class="form-label">{{ $t("created_availabilities") }}</label>
<vue-multiselect
v-model="pickedLocation"
:options="locations"
:label="'name'"
:track-by="'id'"
:selectLabel="'Presser \'Entrée\' pour choisir'"
:selectedLabel="'Choisir'"
:deselectLabel="'Presser \'Entrée\' pour enlever'"
:placeholder="'Choisir'"
></vue-multiselect>
</div>
<div class="row">
<div class="col-sm">
<label class="form-label">{{ $t("created_availabilities") }}</label>
<vue-multiselect
v-model="pickedLocation"
:options="locations"
:label="'name'"
:track-by="'id'"
:selectLabel="'Presser \'Entrée\' pour choisir'"
:selectedLabel="'Choisir'"
:deselectLabel="'Presser \'Entrée\' pour enlever'"
:placeholder="'Choisir'"
></vue-multiselect>
</div>
<div
class="display-options row justify-content-between"
style="margin-top: 1rem"
>
<div class="col-sm-9 col-xs-12">
<div class="input-group mb-3">
<label class="input-group-text" for="slotDuration"
>Durée des créneaux</label
>
<select
v-model="slotDuration"
id="slotDuration"
class="form-select"
>
<option value="00:05:00">5 minutes</option>
<option value="00:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option>
<option value="00:45:00">45 minutes</option>
<option value="00:60:00">60 minutes</option>
</select>
<label class="input-group-text" for="slotMinTime">De</label>
<select
v-model="slotMinTime"
id="slotMinTime"
class="form-select"
>
<option value="00:00:00">0h</option>
<option value="01:00:00">1h</option>
<option value="02:00:00">2h</option>
<option value="03:00:00">3h</option>
<option value="04:00:00">4h</option>
<option value="05:00:00">5h</option>
<option value="06:00:00">6h</option>
<option value="07:00:00">7h</option>
<option value="08:00:00">8h</option>
<option value="09:00:00">9h</option>
<option value="10:00:00">10h</option>
<option value="11:00:00">11h</option>
<option value="12:00:00">12h</option>
</select>
<label class="input-group-text" for="slotMaxTime">À</label>
<select
v-model="slotMaxTime"
id="slotMaxTime"
class="form-select"
>
<option value="12:00:00">12h</option>
<option value="13:00:00">13h</option>
<option value="14:00:00">14h</option>
<option value="15:00:00">15h</option>
<option value="16:00:00">16h</option>
<option value="17:00:00">17h</option>
<option value="18:00:00">18h</option>
<option value="19:00:00">19h</option>
<option value="20:00:00">20h</option>
<option value="21:00:00">21h</option>
<option value="22:00:00">22h</option>
<option value="23:00:00">23h</option>
<option value="23:59:59">24h</option>
</select>
</div>
</div>
<div class="col-xs-12 col-sm-3">
<div class="float-end">
<div class="form-check input-group">
<span class="input-group-text">
<input
id="showHideWE"
class="mt-0"
type="checkbox"
v-model="showWeekends"
/>
</span>
<label
for="showHideWE"
class="form-check-label input-group-text"
>Week-ends</label
>
</div>
</div>
</div>
</div>
<div
class="display-options row justify-content-between"
style="margin-top: 1rem"
>
<div class="col-sm-9 col-xs-12">
<div class="input-group mb-3">
<label class="input-group-text" for="slotDuration"
>Durée des créneaux</label
>
<select v-model="slotDuration" id="slotDuration" class="form-select">
<option value="00:05:00">5 minutes</option>
<option value="00:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option>
<option value="00:45:00">45 minutes</option>
<option value="00:60:00">60 minutes</option>
</select>
<label class="input-group-text" for="slotMinTime">De</label>
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
<option value="00:00:00">0h</option>
<option value="01:00:00">1h</option>
<option value="02:00:00">2h</option>
<option value="03:00:00">3h</option>
<option value="04:00:00">4h</option>
<option value="05:00:00">5h</option>
<option value="06:00:00">6h</option>
<option value="07:00:00">7h</option>
<option value="08:00:00">8h</option>
<option value="09:00:00">9h</option>
<option value="10:00:00">10h</option>
<option value="11:00:00">11h</option>
<option value="12:00:00">12h</option>
</select>
<label class="input-group-text" for="slotMaxTime">À</label>
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
<option value="12:00:00">12h</option>
<option value="13:00:00">13h</option>
<option value="14:00:00">14h</option>
<option value="15:00:00">15h</option>
<option value="16:00:00">16h</option>
<option value="17:00:00">17h</option>
<option value="18:00:00">18h</option>
<option value="19:00:00">19h</option>
<option value="20:00:00">20h</option>
<option value="21:00:00">21h</option>
<option value="22:00:00">22h</option>
<option value="23:00:00">23h</option>
<option value="23:59:59">24h</option>
</select>
</div>
</div>
<FullCalendar :options="calendarOptions" ref="calendarRef">
<template v-slot:eventContent="{ event }: { event: EventApi }">
<span :class="eventClasses">
<b v-if="event.extendedProps.is === 'remote'">{{
event.title
}}</b>
<b v-else-if="event.extendedProps.is === 'range'"
>{{ formatDate(event.startStr, "time") }} -
{{ formatDate(event.endStr, "time") }}:
{{ event.extendedProps.locationName }}</b
>
<a
:href="calendarLink(event.id)"
v-else-if="event.extendedProps.is === 'local'"
>
<b>{{ event.title }}</b>
</a>
<b v-else>no 'is'</b>
<a
v-if="event.extendedProps.is === 'range'"
class="fa fa-fw fa-times delete"
@click.prevent="onClickDelete(event)"
>
</a>
</span>
<div class="col-xs-12 col-sm-3">
<div class="float-end">
<div class="form-check input-group">
<span class="input-group-text">
<input
id="showHideWE"
class="mt-0"
type="checkbox"
v-model="showWeekends"
/>
</span>
<label for="showHideWE" class="form-check-label input-group-text"
>Week-ends</label
>
</div>
</div>
</div>
</div>
<FullCalendar :options="calendarOptions" ref="calendarRef">
<template v-slot:eventContent="{ event }: { event: EventApi }">
<span :class="eventClasses">
<b v-if="event.extendedProps.is === 'remote'">{{ event.title }}</b>
<b v-else-if="event.extendedProps.is === 'range'"
>{{ formatDate(event.startStr, "time") }} -
{{ formatDate(event.endStr, "time") }}:
{{ event.extendedProps.locationName }}</b
>
<a
:href="calendarLink(event.id)"
v-else-if="event.extendedProps.is === 'local'"
>
<b>{{ event.title }}</b>
</a>
<b v-else>no 'is'</b>
<a
v-if="event.extendedProps.is === 'range'"
class="fa fa-fw fa-times delete"
@click.prevent="onClickDelete(event)"
>
</a>
</span>
</template>
</FullCalendar>
<div id="copy-widget">
<div class="container mt-2 mb-2">
<div class="row justify-content-between align-items-center mb-4">
<div class="col-xs-12 col-sm-3 col-md-2">
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
</div>
<div class="col-xs-12 col-sm-9 col-md-2">
<select v-model="dayOrWeek" id="dayOrWeek" class="form-select">
<option value="day">{{ $t("from_day_to_day") }}</option>
<option value="week">
{{ $t("from_week_to_week") }}
</option>
</select>
</div>
<template v-if="dayOrWeek === 'day'">
<div class="col-xs-12 col-sm-3 col-md-3">
<input class="form-control" type="date" v-model="copyFrom" />
</div>
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
<i class="fa fa-angle-double-right"></i>
</div>
<div class="col-xs-12 col-sm-3 col-md-3">
<input class="form-control" type="date" v-model="copyTo" />
</div>
<div class="col-xs-12 col-sm-5 col-md-1">
<button class="btn btn-action float-end" @click="copyDay">
{{ $t("copy_range") }}
</button>
</div>
</template>
</FullCalendar>
<div id="copy-widget">
<div class="container mt-2 mb-2">
<div class="row justify-content-between align-items-center mb-4">
<div class="col-xs-12 col-sm-3 col-md-2">
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
</div>
<div class="col-xs-12 col-sm-9 col-md-2">
<select
v-model="dayOrWeek"
id="dayOrWeek"
class="form-select"
>
<option value="day">{{ $t("from_day_to_day") }}</option>
<option value="week">
{{ $t("from_week_to_week") }}
</option>
</select>
</div>
<template v-if="dayOrWeek === 'day'">
<div class="col-xs-12 col-sm-3 col-md-3">
<input
class="form-control"
type="date"
v-model="copyFrom"
/>
</div>
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
<i class="fa fa-angle-double-right"></i>
</div>
<div class="col-xs-12 col-sm-3 col-md-3">
<input
class="form-control"
type="date"
v-model="copyTo"
/>
</div>
<div class="col-xs-12 col-sm-5 col-md-1">
<button
class="btn btn-action float-end"
@click="copyDay"
>
{{ $t("copy_range") }}
</button>
</div>
</template>
<template v-else>
<div class="col-xs-12 col-sm-3 col-md-3">
<select
v-model="copyFromWeek"
id="copyFromWeek"
class="form-select"
>
<option
v-for="w in lastWeeks"
:value="w.value"
:key="w.value"
>
{{ w.text }}
</option>
</select>
</div>
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
<i class="fa fa-angle-double-right"></i>
</div>
<div class="col-xs-12 col-sm-3 col-md-3">
<select
v-model="copyToWeek"
id="copyToWeek"
class="form-select"
>
<option
v-for="w in nextWeeks"
:value="w.value"
:key="w.value"
>
{{ w.text }}
</option>
</select>
</div>
<div class="col-xs-12 col-sm-5 col-md-1">
<button
class="btn btn-action float-end"
@click="copyWeek"
>
{{ $t("copy_range") }}
</button>
</div>
</template>
</div>
</div>
<template v-else>
<div class="col-xs-12 col-sm-3 col-md-3">
<select
v-model="copyFromWeek"
id="copyFromWeek"
class="form-select"
>
<option v-for="w in lastWeeks" :value="w.value" :key="w.value">
{{ w.text }}
</option>
</select>
</div>
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
<i class="fa fa-angle-double-right"></i>
</div>
<div class="col-xs-12 col-sm-3 col-md-3">
<select v-model="copyToWeek" id="copyToWeek" class="form-select">
<option v-for="w in nextWeeks" :value="w.value" :key="w.value">
{{ w.text }}
</option>
</select>
</div>
<div class="col-xs-12 col-sm-5 col-md-1">
<button class="btn btn-action float-end" @click="copyWeek">
{{ $t("copy_range") }}
</button>
</div>
</template>
</div>
</div>
</div>
<!-- not directly seen, but include in a modal -->
<edit-location ref="editLocation"></edit-location>
<!-- not directly seen, but include in a modal -->
<edit-location ref="editLocation"></edit-location>
</template>
<script setup lang="ts">
import type {
CalendarOptions,
DatesSetArg,
EventInput,
CalendarOptions,
DatesSetArg,
EventInput,
} from "@fullcalendar/core";
import { computed, ref, onMounted } from "vue";
import { useStore } from "vuex";
@@ -233,14 +187,14 @@ import { key } from "./store";
import FullCalendar from "@fullcalendar/vue3";
import frLocale from "@fullcalendar/core/locales/fr";
import interactionPlugin, {
EventResizeDoneArg,
EventResizeDoneArg,
} from "@fullcalendar/interaction";
import timeGridPlugin from "@fullcalendar/timegrid";
import {
EventApi,
DateSelectArg,
EventDropArg,
EventClickArg,
EventApi,
DateSelectArg,
EventDropArg,
EventClickArg,
} from "@fullcalendar/core";
import { dateToISO, ISOToDate } from "ChillMainAssets/chill/js/date";
import VueMultiselect from "vue-multiselect";
@@ -261,113 +215,113 @@ const copyFromWeek = ref<string | null>(null);
const copyToWeek = ref<string | null>(null);
interface Weeks {
value: string | null;
text: string;
value: string | null;
text: string;
}
const getMonday = (week: number): Date => {
const lastMonday = new Date();
lastMonday.setDate(
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
);
return lastMonday;
const lastMonday = new Date();
lastMonday.setDate(
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
);
return lastMonday;
};
const dateOptions: Intl.DateTimeFormatOptions = {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
};
const lastWeeks = computed((): Weeks[] =>
Array.from(Array(30).keys()).map((w) => {
const lastMonday = getMonday(15 - w);
return {
value: dateToISO(lastMonday),
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
};
}),
Array.from(Array(30).keys()).map((w) => {
const lastMonday = getMonday(15 - w);
return {
value: dateToISO(lastMonday),
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
};
}),
);
const nextWeeks = computed((): Weeks[] =>
Array.from(Array(52).keys()).map((w) => {
const nextMonday = getMonday(w + 1);
return {
value: dateToISO(nextMonday),
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
};
}),
Array.from(Array(52).keys()).map((w) => {
const nextMonday = getMonday(w + 1);
return {
value: dateToISO(nextMonday),
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
};
}),
);
const formatDate = (datetime: string, format: null | "time" = null) => {
const date = ISOToDate(datetime);
if (!date) return "";
const date = ISOToDate(datetime);
if (!date) return "";
if (format === "time") {
return date.toLocaleTimeString("fr-FR", {
hour: "2-digit",
minute: "2-digit",
});
}
// French date formatting
return date.toLocaleDateString("fr-FR", {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
if (format === "time") {
return date.toLocaleTimeString("fr-FR", {
hour: "2-digit",
minute: "2-digit",
});
}
// French date formatting
return date.toLocaleDateString("fr-FR", {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
};
const baseOptions = ref<CalendarOptions>({
locale: frLocale,
plugins: [interactionPlugin, timeGridPlugin],
initialView: "timeGridWeek",
initialDate: new Date(),
scrollTimeReset: false,
selectable: true,
// when the dates are changes in the fullcalendar view OR when new events are added
datesSet: onDatesSet,
// when a date is selected
select: onDateSelect,
// when a event is resized
eventResize: onEventDropOrResize,
// when an event is moved
eventDrop: onEventDropOrResize,
// when an event si clicked
eventClick: onEventClick,
selectMirror: false,
editable: true,
headerToolbar: {
left: "prev,next today",
center: "title",
right: "timeGridWeek,timeGridDay",
},
locale: frLocale,
plugins: [interactionPlugin, timeGridPlugin],
initialView: "timeGridWeek",
initialDate: new Date(),
scrollTimeReset: false,
selectable: true,
// when the dates are changes in the fullcalendar view OR when new events are added
datesSet: onDatesSet,
// when a date is selected
select: onDateSelect,
// when a event is resized
eventResize: onEventDropOrResize,
// when an event is moved
eventDrop: onEventDropOrResize,
// when an event si clicked
eventClick: onEventClick,
selectMirror: false,
editable: true,
headerToolbar: {
left: "prev,next today",
center: "title",
right: "timeGridWeek,timeGridDay",
},
});
const ranges = computed<EventInput[]>(() => {
return store.state.calendarRanges.ranges;
return store.state.calendarRanges.ranges;
});
const locations = computed<Location[]>(() => {
return store.state.locations.locations;
return store.state.locations.locations;
});
const pickedLocation = computed<Location | null>({
get(): Location | null {
return (
store.state.locations.locationPicked ||
store.state.locations.currentLocation
);
},
set(newLocation: Location | null): void {
store.commit("locations/setLocationPicked", newLocation, {
root: true,
});
},
get(): Location | null {
return (
store.state.locations.locationPicked ||
store.state.locations.currentLocation
);
},
set(newLocation: Location | null): void {
store.commit("locations/setLocationPicked", newLocation, {
root: true,
});
},
});
/**
@@ -396,122 +350,122 @@ const sources = computed<EventSourceInput[]>(() => {
*/
const calendarOptions = computed((): CalendarOptions => {
return {
...baseOptions.value,
weekends: showWeekends.value,
slotDuration: slotDuration.value,
events: ranges.value,
slotMinTime: slotMinTime.value,
slotMaxTime: slotMaxTime.value,
};
return {
...baseOptions.value,
weekends: showWeekends.value,
slotDuration: slotDuration.value,
events: ranges.value,
slotMinTime: slotMinTime.value,
slotMaxTime: slotMaxTime.value,
};
});
/**
* launched when the calendar range date change
*/
function onDatesSet(event: DatesSetArg): void {
store.dispatch("fullCalendar/setCurrentDatesView", {
start: event.start,
end: event.end,
});
store.dispatch("fullCalendar/setCurrentDatesView", {
start: event.start,
end: event.end,
});
}
function onDateSelect(event: DateSelectArg): void {
if (null === pickedLocation.value) {
window.alert(
"Indiquez une localisation avant de créer une période de disponibilité.",
);
return;
}
if (null === pickedLocation.value) {
window.alert(
"Indiquez une localisation avant de créer une période de disponibilité.",
);
return;
}
store.dispatch("calendarRanges/createRange", {
start: event.start,
end: event.end,
location: pickedLocation.value,
});
store.dispatch("calendarRanges/createRange", {
start: event.start,
end: event.end,
location: pickedLocation.value,
});
}
/**
* When a calendar range is deleted
*/
function onClickDelete(event: EventApi): void {
if (event.extendedProps.is !== "range") {
return;
}
if (event.extendedProps.is !== "range") {
return;
}
store.dispatch(
"calendarRanges/deleteRange",
event.extendedProps.calendarRangeId,
);
store.dispatch(
"calendarRanges/deleteRange",
event.extendedProps.calendarRangeId,
);
}
function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) {
if (payload.event.extendedProps.is !== "range") {
return;
}
if (payload.event.extendedProps.is !== "range") {
return;
}
store.dispatch("calendarRanges/patchRangeTime", {
calendarRangeId: payload.event.extendedProps.calendarRangeId,
start: payload.event.start,
end: payload.event.end,
});
store.dispatch("calendarRanges/patchRangeTime", {
calendarRangeId: payload.event.extendedProps.calendarRangeId,
start: payload.event.start,
end: payload.event.end,
});
}
function onEventClick(payload: EventClickArg): void {
// @ts-ignore TS does not recognize the target. But it does exists.
if (payload.jsEvent.target.classList.contains("delete")) {
return;
}
if (payload.event.extendedProps.is !== "range") {
return;
}
// @ts-ignore TS does not recognize the target. But it does exists.
if (payload.jsEvent.target.classList.contains("delete")) {
return;
}
if (payload.event.extendedProps.is !== "range") {
return;
}
editLocation.value?.startEdit(payload.event);
editLocation.value?.startEdit(payload.event);
}
function copyDay() {
if (null === copyFrom.value || null === copyTo.value) {
return;
}
store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
from: ISOToDate(copyFrom.value),
to: ISOToDate(copyTo.value),
});
if (null === copyFrom.value || null === copyTo.value) {
return;
}
store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
from: ISOToDate(copyFrom.value),
to: ISOToDate(copyTo.value),
});
}
function copyWeek() {
if (null === copyFromWeek.value || null === copyToWeek.value) {
return;
}
store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
fromMonday: ISOToDate(copyFromWeek.value),
toMonday: ISOToDate(copyToWeek.value),
});
if (null === copyFromWeek.value || null === copyToWeek.value) {
return;
}
store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
fromMonday: ISOToDate(copyFromWeek.value),
toMonday: ISOToDate(copyToWeek.value),
});
}
const calendarLink = (calendarId: string) => {
const idStr = calendarId.match(/_(\d+)$/)?.[1];
const idStr = calendarId.match(/_(\d+)$/)?.[1];
return `/fr/calendar/calendar/${idStr}/edit`;
return `/fr/calendar/calendar/${idStr}/edit`;
};
onMounted(() => {
copyFromWeek.value = dateToISO(getMonday(0));
copyToWeek.value = dateToISO(getMonday(1));
copyFromWeek.value = dateToISO(getMonday(0));
copyToWeek.value = dateToISO(getMonday(1));
});
</script>
<style scoped>
#copy-widget {
position: sticky;
bottom: 0px;
background-color: white;
z-index: 9999999999;
padding: 0.25rem 0 0.25rem;
position: sticky;
bottom: 0px;
background-color: white;
z-index: 9999999999;
padding: 0.25rem 0 0.25rem;
}
div.copy-chevron {
text-align: center;
font-size: x-large;
width: 2rem;
text-align: center;
font-size: x-large;
width: 2rem;
}
</style>

View File

@@ -2,7 +2,7 @@ import { DateTime } from "ChillMainAssets/types";
import { StoredObject } from "ChillDocStoreAssets/types/index";
export interface GenericDocMetadata {
isPresent: boolean;
isPresent: boolean;
}
/**
@@ -15,69 +15,69 @@ export interface EmptyMetadata extends GenericDocMetadata {}
* Minimal Metadata for a GenericDoc with a normalizer
*/
export interface BaseMetadata extends GenericDocMetadata {
title: string;
title: string;
}
/**
* A generic doc is a document attached to a Person or an AccompanyingPeriod.
*/
export interface GenericDoc {
type: "doc_store_generic_doc";
uniqueKey: string;
key: string;
identifiers: { id: number };
context: "person" | "accompanying-period";
doc_date: DateTime;
metadata: GenericDocMetadata;
storedObject: StoredObject | null;
type: "doc_store_generic_doc";
uniqueKey: string;
key: string;
identifiers: { id: number };
context: "person" | "accompanying-period";
doc_date: DateTime;
metadata: GenericDocMetadata;
storedObject: StoredObject | null;
}
export interface GenericDocForAccompanyingPeriod extends GenericDoc {
context: "accompanying-period";
context: "accompanying-period";
}
export function isGenericDocForAccompanyingPeriod(
doc: GenericDoc,
doc: GenericDoc,
): doc is GenericDocForAccompanyingPeriod {
return doc.context === "accompanying-period";
return doc.context === "accompanying-period";
}
export function isGenericDocWithStoredObject(
doc: GenericDoc,
doc: GenericDoc,
): doc is GenericDoc & { storedObject: StoredObject } {
return doc.storedObject !== null;
return doc.storedObject !== null;
}
interface BaseMetadataWithHtml extends BaseMetadata {
html: string;
html: string;
}
export interface GenericDocForAccompanyingCourseDocument extends GenericDocForAccompanyingPeriod {
key: "accompanying_course_document";
metadata: BaseMetadataWithHtml;
storedObject: StoredObject;
key: "accompanying_course_document";
metadata: BaseMetadataWithHtml;
storedObject: StoredObject;
}
export interface GenericDocForAccompanyingCourseActivityDocument extends GenericDocForAccompanyingPeriod {
key: "accompanying_course_activity_document";
metadata: BaseMetadataWithHtml;
storedObject: StoredObject;
key: "accompanying_course_activity_document";
metadata: BaseMetadataWithHtml;
storedObject: StoredObject;
}
export interface GenericDocForAccompanyingCourseCalendarDocument extends GenericDocForAccompanyingPeriod {
key: "accompanying_course_calendar_document";
metadata: BaseMetadataWithHtml;
storedObject: StoredObject;
key: "accompanying_course_calendar_document";
metadata: BaseMetadataWithHtml;
storedObject: StoredObject;
}
export interface GenericDocForAccompanyingCoursePersonDocument extends GenericDocForAccompanyingPeriod {
key: "person_document";
metadata: BaseMetadataWithHtml;
storedObject: StoredObject;
key: "person_document";
metadata: BaseMetadataWithHtml;
storedObject: StoredObject;
}
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument extends GenericDocForAccompanyingPeriod {
key: "accompanying_period_work_evaluation_document";
metadata: BaseMetadataWithHtml;
storedObject: StoredObject;
key: "accompanying_period_work_evaluation_document";
metadata: BaseMetadataWithHtml;
storedObject: StoredObject;
}

View File

@@ -46,8 +46,7 @@ export interface StoredObjectVersionCreated extends StoredObjectVersion {
persisted: false;
}
export interface StoredObjectVersionPersisted
extends StoredObjectVersionCreated {
export interface StoredObjectVersionPersisted extends StoredObjectVersionCreated {
version: number;
id: number;
createdAt: DateTime | null;
@@ -61,8 +60,7 @@ export interface StoredObjectStatusChange {
type: string;
}
export interface StoredObjectVersionWithPointInTime
extends StoredObjectVersionPersisted {
export interface StoredObjectVersionWithPointInTime extends StoredObjectVersionPersisted {
"point-in-times": StoredObjectPointInTime[];
"from-restored": StoredObjectVersionPersisted | null;
}

View File

@@ -7,24 +7,24 @@ import { useToast } from "vue-toast-notification";
import { DOCUMENT_ADD, trans } from "translator";
interface DropFileConfig {
allowRemove: boolean;
existingDoc?: StoredObject;
allowRemove: boolean;
existingDoc?: StoredObject;
}
const props = withDefaults(defineProps<DropFileConfig>(), {
allowRemove: false,
allowRemove: false,
});
const emit = defineEmits<{
(
e: "addDocument",
{
stored_object: StoredObject,
stored_object_version: StoredObjectVersion,
file_name: string,
},
): void;
(e: "removeDocument"): void;
(
e: "addDocument",
{
stored_object: StoredObject,
stored_object_version: StoredObjectVersion,
file_name: string,
},
): void;
(e: "removeDocument"): void;
}>();
const $toast = useToast();
@@ -34,65 +34,65 @@ const state = reactive({ showModal: false });
const modalClasses = { "modal-dialog-centered": true, "modal-md": true };
const buttonState = computed<"add" | "replace">(() => {
if (props.existingDoc === undefined || props.existingDoc === null) {
return "add";
}
if (props.existingDoc === undefined || props.existingDoc === null) {
return "add";
}
return "replace";
return "replace";
});
function onAddDocument({
stored_object,
stored_object_version,
file_name,
stored_object,
stored_object_version,
file_name,
}: {
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
file_name: string;
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
file_name: string;
}): void {
const message =
buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
$toast.success(message);
emit("addDocument", { stored_object_version, stored_object, file_name });
state.showModal = false;
const message =
buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
$toast.success(message);
emit("addDocument", { stored_object_version, stored_object, file_name });
state.showModal = false;
}
function onRemoveDocument(): void {
emit("removeDocument");
emit("removeDocument");
}
function openModal(): void {
state.showModal = true;
state.showModal = true;
}
function closeModal(): void {
state.showModal = false;
state.showModal = false;
}
</script>
<template>
<button
v-if="buttonState === 'add'"
@click="openModal"
class="btn btn-create"
>
{{ trans(DOCUMENT_ADD) }}
</button>
<button v-else @click="openModal" class="btn btn-edit"></button>
<modal
v-if="state.showModal"
:modal-dialog-class="modalClasses"
@close="closeModal"
>
<template v-slot:body>
<drop-file-widget
:existing-doc="existingDoc"
:allow-remove="allowRemove"
@add-document="onAddDocument"
@remove-document="onRemoveDocument"
></drop-file-widget>
</template>
</modal>
<button
v-if="buttonState === 'add'"
@click="openModal"
class="btn btn-create"
>
{{ trans(DOCUMENT_ADD) }}
</button>
<button v-else @click="openModal" class="btn btn-edit"></button>
<modal
v-if="state.showModal"
:modal-dialog-class="modalClasses"
@close="closeModal"
>
<template v-slot:body>
<drop-file-widget
:existing-doc="existingDoc"
:allow-remove="allowRemove"
@add-document="onAddDocument"
@remove-document="onRemoveDocument"
></drop-file-widget>
</template>
</modal>
</template>
<style scoped lang="scss"></style>

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import {
StoredObject,
StoredObjectPointInTime,
StoredObjectVersionWithPointInTime,
StoredObject,
StoredObjectPointInTime,
StoredObjectVersionWithPointInTime,
} from "ChillDocStoreAssets/types";
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue";
import { ISOToDatetime } from "ChillMainAssets/chill/js/date";
@@ -12,185 +12,173 @@ import DownloadButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/Downloa
import { computed } from "vue";
interface HistoryButtonListItemConfig {
version: StoredObjectVersionWithPointInTime;
storedObject: StoredObject;
canEdit: boolean;
isCurrent: boolean;
version: StoredObjectVersionWithPointInTime;
storedObject: StoredObject;
canEdit: boolean;
isCurrent: boolean;
}
const emit = defineEmits<{
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
}>();
const props = defineProps<HistoryButtonListItemConfig>();
const onRestore = ({
newVersion,
newVersion,
}: {
newVersion: StoredObjectVersionWithPointInTime;
newVersion: StoredObjectVersionWithPointInTime;
}) => {
emit("restoreVersion", { newVersion });
emit("restoreVersion", { newVersion });
};
const isKeptBeforeConversion = computed<boolean>(() => {
if ("point-in-times" in props.version) {
return props.version["point-in-times"].reduce(
(accumulator: boolean, pit: StoredObjectPointInTime) =>
accumulator || "keep-before-conversion" === pit.reason,
false,
);
} else {
return false;
}
if ("point-in-times" in props.version) {
return props.version["point-in-times"].reduce(
(accumulator: boolean, pit: StoredObjectPointInTime) =>
accumulator || "keep-before-conversion" === pit.reason,
false,
);
} else {
return false;
}
});
const isRestored = computed<boolean>(
() => props.version.version > 0 && null !== props.version["from-restored"],
() => props.version.version > 0 && null !== props.version["from-restored"],
);
const isDuplicated = computed<boolean>(
() =>
props.version.version === 0 && null !== props.version["from-restored"],
() => props.version.version === 0 && null !== props.version["from-restored"],
);
const classes = computed<{
row: true;
"row-hover": true;
"blinking-1": boolean;
"blinking-2": boolean;
row: true;
"row-hover": true;
"blinking-1": boolean;
"blinking-2": boolean;
}>(() => ({
row: true,
"row-hover": true,
"blinking-1": props.isRestored && 0 === props.version.version % 2,
"blinking-2": props.isRestored && 1 === props.version.version % 2,
row: true,
"row-hover": true,
"blinking-1": props.isRestored && 0 === props.version.version % 2,
"blinking-2": props.isRestored && 1 === props.version.version % 2,
}));
</script>
<template>
<div :class="classes">
<div
class="col-12 tags"
v-if="
isCurrent ||
isKeptBeforeConversion ||
isRestored ||
isDuplicated
"
>
<span class="badge bg-success" v-if="isCurrent"
>Version actuelle</span
>
<span class="badge bg-info" v-if="isKeptBeforeConversion"
>Conservée avant conversion dans un autre format</span
>
<span class="badge bg-info" v-if="isRestored"
>Restaurée depuis la version
{{ version["from-restored"]?.version + 1 }}</span
>
<span class="badge bg-info" v-if="isDuplicated"
>Dupliqué depuis un autre document</span
>
</div>
<div class="col-12">
<file-icon :type="version.type"></file-icon>
<span
><strong>&nbsp;#{{ version.version + 1 }}&nbsp;</strong></span
>
<template
v-if="version.createdBy !== null && version.createdAt !== null"
><strong v-if="version.version == 0">créé par</strong
><strong v-else>modifié par</strong>
<span class="badge-user"
><UserRenderBoxBadge
:user="version.createdBy"
></UserRenderBoxBadge
></span>
<strong>à</strong>
{{
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
}}</template
><template
v-if="version.createdBy === null && version.createdAt !== null"
><strong v-if="version.version == 0">Créé le</strong
><strong v-else>modifié le</strong>
{{
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
}}</template
>
</div>
<div class="col-12">
<ul class="record_actions small slim on-version-actions">
<li v-if="canEdit && !isCurrent">
<restore-version-button
:stored-object-version="props.version"
@restore-version="onRestore"
></restore-version-button>
</li>
<li>
<download-button
:stored-object="storedObject"
:at-version="version"
:classes="{
btn: true,
'btn-outline-primary': true,
'btn-sm': true,
}"
:display-action-string-in-button="false"
></download-button>
</li>
</ul>
</div>
<div :class="classes">
<div
class="col-12 tags"
v-if="isCurrent || isKeptBeforeConversion || isRestored || isDuplicated"
>
<span class="badge bg-success" v-if="isCurrent">Version actuelle</span>
<span class="badge bg-info" v-if="isKeptBeforeConversion"
>Conservée avant conversion dans un autre format</span
>
<span class="badge bg-info" v-if="isRestored"
>Restaurée depuis la version
{{ version["from-restored"]?.version + 1 }}</span
>
<span class="badge bg-info" v-if="isDuplicated"
>Dupliqué depuis un autre document</span
>
</div>
<div class="col-12">
<file-icon :type="version.type"></file-icon>
<span
><strong>&nbsp;#{{ version.version + 1 }}&nbsp;</strong></span
>
<template v-if="version.createdBy !== null && version.createdAt !== null"
><strong v-if="version.version == 0">créé par</strong
><strong v-else>modifié par</strong>
<span class="badge-user"
><UserRenderBoxBadge :user="version.createdBy"></UserRenderBoxBadge
></span>
<strong>à</strong>
{{
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
}}</template
><template v-if="version.createdBy === null && version.createdAt !== null"
><strong v-if="version.version == 0">Créé le</strong
><strong v-else>modifié le</strong>
{{
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
}}</template
>
</div>
<div class="col-12">
<ul class="record_actions small slim on-version-actions">
<li v-if="canEdit && !isCurrent">
<restore-version-button
:stored-object-version="props.version"
@restore-version="onRestore"
></restore-version-button>
</li>
<li>
<download-button
:stored-object="storedObject"
:at-version="version"
:classes="{
btn: true,
'btn-outline-primary': true,
'btn-sm': true,
}"
:display-action-string-in-button="false"
></download-button>
</li>
</ul>
</div>
</div>
</template>
<style scoped lang="scss">
div.tags {
span.badge:not(:last-child) {
margin-right: 0.5rem;
}
span.badge:not(:last-child) {
margin-right: 0.5rem;
}
}
// to make the animation restart, we have the same animation twice,
// and alternate between both
.blinking-1 {
animation-name: backgroundColorPalette-1;
animation-duration: 8s;
animation-iteration-count: 1;
animation-direction: normal;
animation-timing-function: linear;
animation-name: backgroundColorPalette-1;
animation-duration: 8s;
animation-iteration-count: 1;
animation-direction: normal;
animation-timing-function: linear;
}
@keyframes backgroundColorPalette-1 {
0% {
background: var(--bs-chill-green-dark);
}
25% {
background: var(--bs-chill-green);
}
65% {
background: var(--bs-chill-beige);
}
100% {
background: unset;
}
0% {
background: var(--bs-chill-green-dark);
}
25% {
background: var(--bs-chill-green);
}
65% {
background: var(--bs-chill-beige);
}
100% {
background: unset;
}
}
.blinking-2 {
animation-name: backgroundColorPalette-2;
animation-duration: 8s;
animation-iteration-count: 1;
animation-direction: normal;
animation-timing-function: linear;
animation-name: backgroundColorPalette-2;
animation-duration: 8s;
animation-iteration-count: 1;
animation-direction: normal;
animation-timing-function: linear;
}
@keyframes backgroundColorPalette-2 {
0% {
background: var(--bs-chill-green-dark);
}
25% {
background: var(--bs-chill-green);
}
65% {
background: var(--bs-chill-beige);
}
100% {
background: unset;
}
0% {
background: var(--bs-chill-green-dark);
}
25% {
background: var(--bs-chill-green);
}
65% {
background: var(--bs-chill-beige);
}
100% {
background: unset;
}
}
</style>

View File

@@ -13,15 +13,15 @@
*
*/
export const dateToISO = (date: Date | null): string | null => {
if (null === date) {
return null;
}
if (null === date) {
return null;
}
return [
date.getFullYear(),
(date.getMonth() + 1).toString().padStart(2, "0"),
date.getDate().toString().padStart(2, "0"),
].join("-");
return [
date.getFullYear(),
(date.getMonth() + 1).toString().padStart(2, "0"),
date.getDate().toString().padStart(2, "0"),
].join("-");
};
/**
@@ -30,21 +30,21 @@ export const dateToISO = (date: Date | null): string | null => {
* **Experimental**
*/
export const ISOToDate = (str: string | null): Date | null => {
if (null === str) {
return null;
}
if ("" === str.trim()) {
return null;
}
if (null === str) {
return null;
}
if ("" === str.trim()) {
return null;
}
// If the string already contains time info, use it directly
if (str.includes("T") || str.includes(" ")) {
return new Date(str);
}
// If the string already contains time info, use it directly
if (str.includes("T") || str.includes(" ")) {
return new Date(str);
}
// Otherwise, parse date only
const [year, month, day] = str.split("-").map((p) => parseInt(p));
return new Date(year, month - 1, day, 0, 0, 0, 0);
// Otherwise, parse date only
const [year, month, day] = str.split("-").map((p) => parseInt(p));
return new Date(year, month - 1, day, 0, 0, 0, 0);
};
/**
@@ -52,21 +52,19 @@ export const ISOToDate = (str: string | null): Date | null => {
*
*/
export const ISOToDatetime = (str: string | null): Date | null => {
if (null === str) {
return null;
}
if (null === str) {
return null;
}
const [cal, times] = str.split("T"),
[year, month, date] = cal.split("-").map((s) => parseInt(s)),
[time, timezone] = times.split(times.charAt(8)),
[hours, minutes, seconds] = time.split(":").map((s) => parseInt(s));
if ("0000" === timezone) {
return new Date(
Date.UTC(year, month - 1, date, hours, minutes, seconds),
);
}
const [cal, times] = str.split("T"),
[year, month, date] = cal.split("-").map((s) => parseInt(s)),
[time, timezone] = times.split(times.charAt(8)),
[hours, minutes, seconds] = time.split(":").map((s) => parseInt(s));
if ("0000" === timezone) {
return new Date(Date.UTC(year, month - 1, date, hours, minutes, seconds));
}
return new Date(year, month - 1, date, hours, minutes, seconds);
return new Date(year, month - 1, date, hours, minutes, seconds);
};
/**
@@ -74,102 +72,100 @@ export const ISOToDatetime = (str: string | null): Date | null => {
*
*/
export const datetimeToISO = (date: Date): string => {
const cal = [
date.getFullYear(),
(date.getMonth() + 1).toString().padStart(2, "0"),
date.getDate().toString().padStart(2, "0"),
].join("-");
const cal = [
date.getFullYear(),
(date.getMonth() + 1).toString().padStart(2, "0"),
date.getDate().toString().padStart(2, "0"),
].join("-");
const time = [
date.getHours().toString().padStart(2, "0"),
date.getMinutes().toString().padStart(2, "0"),
date.getSeconds().toString().padStart(2, "0"),
].join(":");
const time = [
date.getHours().toString().padStart(2, "0"),
date.getMinutes().toString().padStart(2, "0"),
date.getSeconds().toString().padStart(2, "0"),
].join(":");
const offset = [
date.getTimezoneOffset() <= 0 ? "+" : "-",
Math.abs(Math.floor(date.getTimezoneOffset() / 60))
.toString()
.padStart(2, "0"),
":",
Math.abs(date.getTimezoneOffset() % 60)
.toString()
.padStart(2, "0"),
].join("");
const offset = [
date.getTimezoneOffset() <= 0 ? "+" : "-",
Math.abs(Math.floor(date.getTimezoneOffset() / 60))
.toString()
.padStart(2, "0"),
":",
Math.abs(date.getTimezoneOffset() % 60)
.toString()
.padStart(2, "0"),
].join("");
const x = cal + "T" + time + offset;
const x = cal + "T" + time + offset;
return x;
return x;
};
export const intervalDaysToISO = (days: number | string | null): string => {
if (null === days) {
return "P0D";
}
if (null === days) {
return "P0D";
}
return `P${days}D`;
return `P${days}D`;
};
export const intervalISOToDays = (str: string | null): number | null => {
if (null === str) {
return null;
}
if (null === str) {
return null;
}
if ("" === str.trim()) {
return null;
}
if ("" === str.trim()) {
return null;
}
let days = 0;
let isDate = true;
let vstring = "";
for (let i = 0; i < str.length; i = i + 1) {
if (!isDate) {
continue;
}
switch (str.charAt(i)) {
case "P":
isDate = true;
break;
case "T":
isDate = false;
break;
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
vstring = vstring + str.charAt(i);
break;
case "Y":
days = days + Number.parseInt(vstring) * 365;
vstring = "";
break;
case "M":
days = days + Number.parseInt(vstring) * 30;
vstring = "";
break;
case "D":
days = days + Number.parseInt(vstring);
vstring = "";
break;
default:
throw Error(
"this character should not appears: " + str.charAt(i),
);
}
let days = 0;
let isDate = true;
let vstring = "";
for (let i = 0; i < str.length; i = i + 1) {
if (!isDate) {
continue;
}
switch (str.charAt(i)) {
case "P":
isDate = true;
break;
case "T":
isDate = false;
break;
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
vstring = vstring + str.charAt(i);
break;
case "Y":
days = days + Number.parseInt(vstring) * 365;
vstring = "";
break;
case "M":
days = days + Number.parseInt(vstring) * 30;
vstring = "";
break;
case "D":
days = days + Number.parseInt(vstring);
vstring = "";
break;
default:
throw Error("this character should not appears: " + str.charAt(i));
}
}
return days;
return days;
};
export function getTimezoneOffsetString(date: Date, timeZone: string): string {
const utcDate = new Date(date.toLocaleString("en-US", { timeZone: "UTC" }));
const tzDate = new Date(date.toLocaleString("en-US", { timeZone }));
const tzDate = new Date(date.toLocaleString("en-US", { timeZone }));
const offsetMinutes = (utcDate.getTime() - tzDate.getTime()) / (60 * 1000);
// Inverser le signe pour avoir la convention ±HH:MM
@@ -180,4 +176,3 @@ export function getTimezoneOffsetString(date: Date, timeZone: string): string {
return `${sign}${hours}:${minutes}`;
}

View File

@@ -3,7 +3,7 @@ import {
Scope,
ValidationExceptionInterface,
ValidationProblemFromMap,
ViolationFromMap
ViolationFromMap,
} from "../../types";
export type body = Record<string, boolean | string | number | null>;
@@ -32,11 +32,11 @@ export interface TransportExceptionInterface {
}
export class ValidationException<
M extends Record<string, Record<string, string|number>> = Record<
string,
Record<string, string|number>
>,
>
M extends Record<string, Record<string, string | number>> = Record<
string,
Record<string, string | number>
>,
>
extends Error
implements ValidationExceptionInterface<M>
{
@@ -68,7 +68,10 @@ export class ValidationException<
this.byProperty = problem.violations.reduce(
(acc, v) => {
const key = v.propertyPath.replace('/\[\d+\]$/', "") as Extract<keyof M, string>;
const key = v.propertyPath.replace("/\[\d+\]$/", "") as Extract<
keyof M,
string
>;
(acc[key] ||= []).push(v.title);
return acc;
},
@@ -80,28 +83,27 @@ export class ValidationException<
}
}
violationsByNormalizedProperty(property: Extract<keyof M, string>): ViolationFromMap<M>[] {
return this.violationsList.filter((v) => v.propertyPath.replace(/\[\d+\]$/, "") === property);
violationsByNormalizedProperty(
property: Extract<keyof M, string>,
): ViolationFromMap<M>[] {
return this.violationsList.filter(
(v) => v.propertyPath.replace(/\[\d+\]$/, "") === property,
);
}
violationsByNormalizedPropertyAndParams<
P extends Extract<keyof M, string>,
K extends Extract<keyof M[P], string>
>(
property: P,
param: K,
param_value: M[P][K]
): ViolationFromMap<M>[]
{
K extends Extract<keyof M[P], string>,
>(property: P, param: K, param_value: M[P][K]): ViolationFromMap<M>[] {
const list = this.violationsByNormalizedProperty(property);
return list.filter(
return list.filter(
(v): boolean =>
!!v.parameters &&
// `with_parameter in v.parameters` check indexing
param in v.parameters &&
// the cast is safe, because we have overloading that bind the types
(v.parameters as M[P])[param] === param_value
(v.parameters as M[P])[param] === param_value,
);
}
}
@@ -110,9 +112,9 @@ export class ValidationException<
* Check that the exception is a ValidationExceptionInterface
* @param x
*/
export function isValidationException<M extends Record<string, Record<string, string|number>>>(
x: unknown,
): x is ValidationExceptionInterface<M> {
export function isValidationException<
M extends Record<string, Record<string, string | number>>,
>(x: unknown): x is ValidationExceptionInterface<M> {
return (
x instanceof ValidationException ||
(typeof x === "object" &&
@@ -147,8 +149,7 @@ export interface AccessExceptionInterface extends TransportExceptionInterface {
violations: string[];
}
export interface NotFoundExceptionInterface
extends TransportExceptionInterface {
export interface NotFoundExceptionInterface extends TransportExceptionInterface {
name: "NotFoundException";
}
@@ -159,8 +160,7 @@ export interface ServerExceptionInterface extends TransportExceptionInterface {
body: string;
}
export interface ConflictHttpExceptionInterface
extends TransportExceptionInterface {
export interface ConflictHttpExceptionInterface extends TransportExceptionInterface {
name: "ConflictHttpException";
violations: string[];
}
@@ -306,9 +306,9 @@ export interface ConflictHttpExceptionInterface
export const makeFetch = async <
Input,
Output,
M extends Record<string, Record<string, string|number>> = Record<
M extends Record<string, Record<string, string | number>> = Record<
string,
Record<string, string|number>
Record<string, string | number>
>,
>(
method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE",

View File

@@ -1,4 +1,4 @@
import {Gender, GenderTranslation} from "ChillMainAssets/types";
import { Gender, GenderTranslation } from "ChillMainAssets/types";
/**
* Translates a given gender object into its corresponding gender translation string.
@@ -7,8 +7,7 @@ import {Gender, GenderTranslation} from "ChillMainAssets/types";
* @return {GenderTranslation} Returns the gender translation string corresponding to the provided gender,
* or "unknown" if the gender is null.
*/
export function toGenderTranslation(gender: Gender|null): GenderTranslation
{
export function toGenderTranslation(gender: Gender | null): GenderTranslation {
if (null === gender) {
return "unknown";
}

View File

@@ -6,8 +6,8 @@
* @return {string} The "returnPath" from the query string, or the fallback path if "returnPath" is not present.
*/
export function returnPathOr(fallbackPath: string): string {
const urlParams = new URLSearchParams(window.location.search);
const returnPath = urlParams.get("returnPath");
const urlParams = new URLSearchParams(window.location.search);
const returnPath = urlParams.get("returnPath");
return returnPath ?? fallbackPath;
return returnPath ?? fallbackPath;
}

View File

@@ -2,15 +2,15 @@ import { EntityWorkflow } from "ChillMainAssets/types";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
export const fetchWorkflow = async (
workflowId: number,
workflowId: number,
): Promise<EntityWorkflow> => {
try {
return await makeFetch<null, EntityWorkflow>(
"GET",
`/api/1.0/main/workflow/${workflowId}.json`,
);
} catch (error) {
console.error(`Failed to fetch workflow ${workflowId}:`, error);
throw error;
}
try {
return await makeFetch<null, EntityWorkflow>(
"GET",
`/api/1.0/main/workflow/${workflowId}.json`,
);
} catch (error) {
console.error(`Failed to fetch workflow ${workflowId}:`, error);
throw error;
}
};

View File

@@ -1,10 +1,10 @@
import {
GenericDoc,
isGenericDocWithStoredObject,
GenericDoc,
isGenericDocWithStoredObject,
} from "ChillDocStoreAssets/types/generic_doc";
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
import { CreatableEntityType } from "ChillPersonAssets/types";
import {ThirdpartyCompany} from "../../../ChillThirdPartyBundle/Resources/public/types";
import { ThirdpartyCompany } from "../../../ChillThirdPartyBundle/Resources/public/types";
import { Person } from "../../../ChillPersonBundle/Resources/public/types";
export interface DateTime {
@@ -38,7 +38,6 @@ export interface SetCivility {
id: number;
}
/**
* Gender translation.
*
@@ -282,74 +281,74 @@ export interface WorkflowAttachment {
}
export type AttachmentWithDocAndStored = WorkflowAttachment & {
genericDoc: GenericDoc & { storedObject: StoredObject };
genericDoc: GenericDoc & { storedObject: StoredObject };
};
export function isAttachmentWithDocAndStored(
a: WorkflowAttachment,
a: WorkflowAttachment,
): a is AttachmentWithDocAndStored {
return (
isWorkflowAttachmentWithGenericDoc(a) &&
isGenericDocWithStoredObject(a.genericDoc)
);
return (
isWorkflowAttachmentWithGenericDoc(a) &&
isGenericDocWithStoredObject(a.genericDoc)
);
}
export function isWorkflowAttachmentWithGenericDoc(
attachment: WorkflowAttachment,
attachment: WorkflowAttachment,
): attachment is WorkflowAttachment & { genericDoc: GenericDoc } {
return attachment.genericDoc !== null;
return attachment.genericDoc !== null;
}
export interface Workflow {
name: string;
text: string;
name: string;
text: string;
}
export interface EntityWorkflowStep {
type: "entity_workflow_step";
id: number;
comment: string;
currentStep: StepDefinition;
isFinal: boolean;
isFreezed: boolean;
isFinalized: boolean;
transitionPrevious: Transition | null;
transitionAfter: Transition | null;
previousId: number | null;
nextId: number | null;
transitionPreviousBy: User | null;
transitionPreviousAt: DateTime | null;
type: "entity_workflow_step";
id: number;
comment: string;
currentStep: StepDefinition;
isFinal: boolean;
isFreezed: boolean;
isFinalized: boolean;
transitionPrevious: Transition | null;
transitionAfter: Transition | null;
previousId: number | null;
nextId: number | null;
transitionPreviousBy: User | null;
transitionPreviousAt: DateTime | null;
}
export interface Transition {
name: string;
text: string;
isForward: boolean;
name: string;
text: string;
isForward: boolean;
}
export interface StepDefinition {
name: string;
text: string;
name: string;
text: string;
}
export interface EntityWorkflow {
type: "entity_workflow";
id: number;
relatedEntityClass: string;
relatedEntityId: number;
workflow: Workflow;
currentStep: EntityWorkflowStep;
steps: EntityWorkflowStep[];
datas: WorkflowData;
title: string;
isOnHoldAtCurrentStep: boolean;
_permissions: {
CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT: boolean;
};
type: "entity_workflow";
id: number;
relatedEntityClass: string;
relatedEntityId: number;
workflow: Workflow;
currentStep: EntityWorkflowStep;
steps: EntityWorkflowStep[];
datas: WorkflowData;
title: string;
isOnHoldAtCurrentStep: boolean;
_permissions: {
CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT: boolean;
};
}
export interface WorkflowData {
persons: Person[];
persons: Person[];
}
export interface ExportGeneration {
@@ -380,17 +379,20 @@ export type DynamicKeys<M extends Record<string, Record<string, unknown>>> =
type NormalizeKey<K extends string> = K extends `${infer B}[${number}]` ? B : K;
export type ViolationFromMap<M extends Record<string, Record<string, unknown>>> = {
[K in DynamicKeys<M> & string]: { // <- note le "& string" ici
export type ViolationFromMap<
M extends Record<string, Record<string, unknown>>,
> = {
[K in DynamicKeys<M> & string]: {
// <- note le "& string" ici
propertyPath: K;
title: string;
parameters?: M[NormalizeKey<K>];
type?: string;
}
};
}[DynamicKeys<M> & string];
export type ValidationProblemFromMap<
M extends Record<string, Record<string, string|number>>,
M extends Record<string, Record<string, string | number>>,
> = {
type: string;
title: string;
@@ -399,9 +401,9 @@ export type ValidationProblemFromMap<
} & Record<string, unknown>;
export interface ValidationExceptionInterface<
M extends Record<string, Record<string, string|number>> = Record<
M extends Record<string, Record<string, string | number>> = Record<
string,
Record<string, string|number>
Record<string, string | number>
>,
> extends Error {
name: "ValidationException";
@@ -418,15 +420,17 @@ export interface ValidationExceptionInterface<
/** Indexing by property (useful for display by field) */
byProperty: Record<Extract<keyof M, string>, string[]>;
violationsByNormalizedProperty(property: Extract<keyof M, string>): ViolationFromMap<M>[];
violationsByNormalizedProperty(
property: Extract<keyof M, string>,
): ViolationFromMap<M>[];
violationsByNormalizedPropertyAndParams<
P extends Extract<keyof M, string>,
K extends Extract<keyof M[P], string>
K extends Extract<keyof M[P], string>,
>(
property: P,
param: K,
param_value: M[P][K]
param_value: M[P][K],
): ViolationFromMap<M>[];
}
@@ -435,8 +439,7 @@ export interface AccessExceptionInterface extends TransportExceptionInterface {
violations: string[];
}
export interface NotFoundExceptionInterface
extends TransportExceptionInterface {
export interface NotFoundExceptionInterface extends TransportExceptionInterface {
name: "NotFoundException";
}
@@ -447,8 +450,7 @@ export interface ServerExceptionInterface extends TransportExceptionInterface {
body: string;
}
export interface ConflictHttpExceptionInterface
extends TransportExceptionInterface {
export interface ConflictHttpExceptionInterface extends TransportExceptionInterface {
name: "ConflictHttpException";
violations: string[];
}
@@ -496,16 +498,16 @@ export interface TabDefinition {
counter: () => number;
}
export type CreateComponentConfigGeneral = {
action: 'create';
export interface CreateComponentConfigGeneral {
action: "create";
allowedTypes: CreatableEntityType[];
query: string;
parent: null;
}
export type CreateComponentThirdPartyAddContact = {
action: 'addContact';
allowedTypes: readonly ['thirdparty'];
export interface CreateComponentThirdPartyAddContact {
action: "addContact";
allowedTypes: readonly ["thirdparty"];
query: string;
parent: ThirdpartyCompany;
}
@@ -513,8 +515,9 @@ export type CreateComponentThirdPartyAddContact = {
/**
* Configuration for the CreateModal and Create component
*/
export type CreateComponentConfig = CreateComponentConfigGeneral | CreateComponentThirdPartyAddContact;
export type CreateComponentConfig =
| CreateComponentConfigGeneral
| CreateComponentThirdPartyAddContact;
/**
* Possible states for the WaitingScreen Component.

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";
@@ -14,9 +14,9 @@ import WaitingScreen from "../_components/WaitingScreen.vue";
import { ExportGeneration, WaitingScreenState } from "ChillMainAssets/types";
interface AppProps {
exportGenerationId: string;
title: string;
createdDate: string;
exportGenerationId: string;
title: string;
createdDate: string;
}
const props = defineProps<AppProps>();
@@ -24,25 +24,25 @@ 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 filename = computed<string>(() => `${props.title}-${props.createdDate}`);
const state = computed<WaitingScreenState>((): WaitingScreenState => {
if (status.value === "empty") {
return "pending";
}
if (status.value === "empty") {
return "pending";
}
return status.value;
return status.value;
});
/**
@@ -56,69 +56,69 @@ 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,
);
if (isPending.value) {
checkForReady();
return Promise.resolve();
}
exportGeneration.value = await fetchExportGenerationStatus(
props.exportGenerationId,
);
if (isPending.value) {
checkForReady();
return Promise.resolve();
}
return Promise.resolve();
};
onMounted(() => {
onObjectNewStatusCallback();
onObjectNewStatusCallback();
});
</script>
<template>
<WaitingScreen :state="state">
<template v-slot:pending>
<p>
{{ trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING) }}
</p>
</template>
<WaitingScreen :state="state">
<template v-slot:pending>
<p>
{{ trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING) }}
</p>
</template>
<template v-slot:stopped>
<p>
{{ trans(EXPORT_GENERATION_TOO_MANY_RETRIES) }}
</p>
</template>
<template v-slot:stopped>
<p>
{{ trans(EXPORT_GENERATION_TOO_MANY_RETRIES) }}
</p>
</template>
<template v-slot:failure>
<p>
{{ trans(EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT) }}
</p>
</template>
<template v-slot:failure>
<p>
{{ trans(EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT) }}
</p>
</template>
<template v-slot:ready>
<p>
{{ trans(EXPORT_GENERATION_EXPORT_READY) }}
</p>
<template v-slot:ready>
<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>
</template>
</WaitingScreen>
<p v-if="storedObject !== null">
<document-action-buttons-group
:stored-object="storedObject"
:filename="filename"
></document-action-buttons-group>
</p>
</template>
</WaitingScreen>
</template>

View File

@@ -61,7 +61,7 @@ import { CreatableEntityType, Person } from "ChillPersonAssets/types";
import { CreateComponentConfig } from "ChillMainAssets/types";
import PersonEdit from "ChillPersonAssets/vuejs/_components/OnTheFly/PersonEdit.vue";
import ThirdPartyEdit from "ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdPartyEdit.vue";
import {Thirdparty} from "../../../../../../ChillThirdPartyBundle/Resources/public/types";
import { Thirdparty } from "../../../../../../ChillThirdPartyBundle/Resources/public/types";
const props = withDefaults(defineProps<CreateComponentConfig>(), {
action: "create",
@@ -69,11 +69,10 @@ const props = withDefaults(defineProps<CreateComponentConfig>(), {
parent: null,
});
const emit =
defineEmits<{
(e: "onPersonCreated", payload: { person: Person }): void,
(e: "onThirdPartyCreated", payload: { thirdParty: Thirdparty }): void,
}>();
const emit = defineEmits<{
(e: "onPersonCreated", payload: { person: Person }): void;
(e: "onThirdPartyCreated", payload: { thirdParty: Thirdparty }): void;
}>();
const type = ref<CreatableEntityType | null>(null);
@@ -88,8 +87,8 @@ const radioType = computed<CreatableEntityType | null>({
type PersonEditComponent = InstanceType<typeof PersonEdit>;
type ThirdPartyEditComponent = InstanceType<typeof ThirdPartyEdit>;
const castPerson = ref<PersonEditComponent|null>(null);
const castThirdparty = ref<ThirdPartyEditComponent|null>(null);
const castPerson = ref<PersonEditComponent | null>(null);
const castThirdparty = ref<ThirdPartyEditComponent | null>(null);
onMounted(() => {
type.value =
@@ -106,7 +105,7 @@ const containsThirdParty = computed<boolean>(() =>
props.allowedTypes.includes("thirdparty"),
);
const containsPerson = computed<boolean>(() => {
if (props.action === 'addContact') {
if (props.action === "addContact") {
return false;
}
return props.allowedTypes.includes("person");
@@ -115,7 +114,10 @@ const containsPerson = computed<boolean>(() => {
function save(): void {
if (radioType.value === "person" && castPerson.value !== null) {
castPerson.value.postPerson();
} else if (radioType.value === "thirdparty" && castThirdparty.value !== null) {
} else if (
radioType.value === "thirdparty" &&
castThirdparty.value !== null
) {
castThirdparty.value.postThirdParty();
}
}

View File

@@ -5,7 +5,7 @@ import { CreateComponentConfig } from "ChillMainAssets/types";
import { trans, SAVE } from "translator";
import { useTemplateRef } from "vue";
import { Person } from "ChillPersonAssets/types";
import {Thirdparty} from "../../../../../../ChillThirdPartyBundle/Resources/public/types";
import { Thirdparty } from "../../../../../../ChillThirdPartyBundle/Resources/public/types";
const emit = defineEmits<{
(e: "onPersonCreated", payload: { person: Person }): void;
@@ -13,20 +13,24 @@ const emit = defineEmits<{
(e: "close"): void;
}>();
const props = defineProps<CreateComponentConfig & {modalTitle: string}>();
const props = defineProps<CreateComponentConfig & { modalTitle: string }>();
const modalDialogClass = { "modal-xl": true, "modal-scrollable": true };
type CreateComponentType = InstanceType<typeof Create>;
const create = useTemplateRef<CreateComponentType>("create");
const onPersonCreated = ({person}: {person: Person}): void => {
emit("onPersonCreated", {person});
const onPersonCreated = ({ person }: { person: Person }): void => {
emit("onPersonCreated", { person });
};
const onThirdPartyCreated = ({thirdParty}: {thirdParty: Thirdparty}): void => {
emit("onThirdPartyCreated", {thirdParty: thirdParty});
}
const onThirdPartyCreated = ({
thirdParty,
}: {
thirdParty: Thirdparty;
}): void => {
emit("onThirdPartyCreated", { thirdParty: thirdParty });
};
function save(): void {
console.log("save from CreateModal");

View File

@@ -115,7 +115,7 @@
</teleport>
</template>
<script setup lang="ts">
import {ref, computed, defineEmits, defineProps, useTemplateRef} from "vue";
import { ref, computed, defineEmits, defineProps, useTemplateRef } from "vue";
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import OnTheFlyCreate from "./Create.vue";
import OnTheFlyPerson from "ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue";
@@ -177,14 +177,19 @@ const props = withDefaults(defineProps<OnTheFlyComponentProps>(), {
query: "",
});
const emit = defineEmits<{
(e: "saveFormOnTheFly", payload: { type: string | undefined; data: any }): void;
}>();
const emit =
defineEmits<
(
e: "saveFormOnTheFly",
payload: { type: string | undefined; data: any },
) => void
>();
type castEditPersonType = InstanceType<typeof PersonEdit>;
type castEditThirdPartyType = InstanceType<typeof ThirdPartyEdit>;
const castEditPerson = useTemplateRef<castEditPersonType>('castEditPerson')
const castEditThirdParty = useTemplateRef<castEditThirdPartyType>('castEditThirdParty');
const castEditPerson = useTemplateRef<castEditPersonType>("castEditPerson");
const castEditThirdParty =
useTemplateRef<castEditThirdPartyType>("castEditThirdParty");
const modal = ref<{ showModal: boolean; modalDialogClass: string }>({
showModal: false,
@@ -234,35 +239,35 @@ const titleAction = computed<string>(() => {
const titleCreate = computed<string>(() => {
if (typeof props.allowedTypes === "undefined") {
return trans(ONTHEFLY_CREATE_TITLE_DEFAULT)
return trans(ONTHEFLY_CREATE_TITLE_DEFAULT);
}
return props.allowedTypes.every((t: EntityType) => t === "person")
? (trans(ONTHEFLY_CREATE_TITLE_PERSON))
? trans(ONTHEFLY_CREATE_TITLE_PERSON)
: props.allowedTypes.every((t: EntityType) => t === "thirdparty")
? (trans(ONTHEFLY_CREATE_TITLE_THIRDPARTY))
: (trans(ONTHEFLY_CREATE_TITLE_DEFAULT));
? trans(ONTHEFLY_CREATE_TITLE_THIRDPARTY)
: trans(ONTHEFLY_CREATE_TITLE_DEFAULT);
});
const titleModal = computed<string>(() => {
switch (props.action) {
case "show":
if (props.type == "person") {
return trans(ONTHEFLY_SHOW_PERSON)
return trans(ONTHEFLY_SHOW_PERSON);
} else if (props.type == "thirdparty") {
return trans(ONTHEFLY_SHOW_THIRDPARTY)
return trans(ONTHEFLY_SHOW_THIRDPARTY);
}
break;
case "edit":
if (props.type == "person") {
return trans(ONTHEFLY_EDIT_PERSON)
return trans(ONTHEFLY_EDIT_PERSON);
} else if (props.type == "thirdparty") {
return trans(ONTHEFLY_EDIT_THIRDPARTY)
return trans(ONTHEFLY_EDIT_THIRDPARTY);
}
break;
case "create":
return titleCreate.value;
case "addContact":
return trans(THIRDPARTY_ADDCONTACT_TITLE)
return trans(THIRDPARTY_ADDCONTACT_TITLE);
default:
break;
}
@@ -311,7 +316,10 @@ function openModal(): void {
modal.value.showModal = true;
}
function buildLocation(id: string | number | undefined, type: EntityType | undefined): string | undefined {
function buildLocation(
id: string | number | undefined,
type: EntityType | undefined,
): string | undefined {
if (type === "person") {
return encodeURI(`/fr/person/${id}/general${getReturnPath.value}`);
} else if (type === "thirdparty") {
@@ -324,17 +332,16 @@ async function saveAction() {
if (props.type === "person") {
const person = await castEditPerson.value?.postPerson();
if (null !== person) {
emit("saveFormOnTheFly", {type: props.type, data: person})
emit("saveFormOnTheFly", { type: props.type, data: person });
}
} else if (props.type === 'thirdparty') {
} else if (props.type === "thirdparty") {
const thirdParty = await castEditThirdParty.value?.postThirdParty();
if (null !== thirdParty) {
emit("saveFormOnTheFly", {type: props.type, data: thirdParty })
emit("saveFormOnTheFly", { type: props.type, data: thirdParty });
}
}
}
defineExpose({
openModal,
closeModal,

View File

@@ -19,10 +19,11 @@
>
{{ p.text }}
</span>
<span v-else
:class="getBadgeClass(p)"
class="chill_denomination"
:style="getBadgeStyle(p)"
<span
v-else
:class="getBadgeClass(p)"
class="chill_denomination"
:style="getBadgeStyle(p)"
>
Ménage n°{{ p.id }}
</span>
@@ -61,12 +62,14 @@
@click="addNewSuggested(s)"
style="margin: 0"
>
<span v-if="!isEntityHousehold(s)" :class="getBadgeClass(s)" :style="getBadgeStyle(s)">
<span
v-if="!isEntityHousehold(s)"
:class="getBadgeClass(s)"
:style="getBadgeStyle(s)"
>
{{ s.text }}
</span>
<span v-else>
Ménage n°{{ s.id }}
</span>
<span v-else> Ménage n°{{ s.id }} </span>
</li>
</ul>
</div>
@@ -85,7 +88,8 @@ import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
import {
Entities,
EntitiesOrMe,
EntityType, isEntityHousehold,
EntityType,
isEntityHousehold,
SearchOptions,
Suggestion,
} from "ChillPersonAssets/types";

View File

@@ -6,15 +6,15 @@ import { ref } from "vue";
import WaitingScreen from "ChillMainAssets/vuejs/_components/WaitingScreen.vue";
import { WaitingScreenState } from "ChillMainAssets/types";
import {
trans,
WORKFLOW_WAIT_TITLE,
WORKFLOW_WAIT_ERROR_WHILE_WAITING,
WORKFLOW_WAIT_SUCCESS,
trans,
WORKFLOW_WAIT_TITLE,
WORKFLOW_WAIT_ERROR_WHILE_WAITING,
WORKFLOW_WAIT_SUCCESS,
} from "translator";
interface WaitPostProcessWorkflowComponentProps {
workflowId: number;
expectedStep: string;
workflowId: number;
expectedStep: string;
}
const props = defineProps<WaitPostProcessWorkflowComponentProps>();
@@ -24,52 +24,52 @@ const MAX_TRYIES = 50;
const state = ref<WaitingScreenState>("pending");
const { pause, resume } = useIntervalFn(
async () => {
try {
const workflow = await fetchWorkflow(props.workflowId);
counter.value++;
if (workflow.currentStep.currentStep.name === props.expectedStep) {
window.location.assign(
returnPathOr("/fr/main/workflow" + workflow.id + "/show"),
);
resume();
state.value = "ready";
}
async () => {
try {
const workflow = await fetchWorkflow(props.workflowId);
counter.value++;
if (workflow.currentStep.currentStep.name === props.expectedStep) {
window.location.assign(
returnPathOr("/fr/main/workflow" + workflow.id + "/show"),
);
resume();
state.value = "ready";
}
if (counter.value > MAX_TRYIES) {
pause();
state.value = "failure";
}
} catch (error) {
console.error(error);
pause();
}
},
2000,
{ immediate: true },
if (counter.value > MAX_TRYIES) {
pause();
state.value = "failure";
}
} catch (error) {
console.error(error);
pause();
}
},
2000,
{ immediate: true },
);
</script>
<template>
<div class="container">
<WaitingScreen :state="state">
<template v-slot:pending>
<p>
{{ trans(WORKFLOW_WAIT_TITLE) }}
</p>
</template>
<template v-slot:failure>
<p>
{{ trans(WORKFLOW_WAIT_ERROR_WHILE_WAITING) }}
</p>
</template>
<template v-slot:ready>
<p>
{{ trans(WORKFLOW_WAIT_SUCCESS) }}
</p>
</template>
</WaitingScreen>
</div>
<div class="container">
<WaitingScreen :state="state">
<template v-slot:pending>
<p>
{{ trans(WORKFLOW_WAIT_TITLE) }}
</p>
</template>
<template v-slot:failure>
<p>
{{ trans(WORKFLOW_WAIT_ERROR_WHILE_WAITING) }}
</p>
</template>
<template v-slot:ready>
<p>
{{ trans(WORKFLOW_WAIT_SUCCESS) }}
</p>
</template>
</WaitingScreen>
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -2,50 +2,50 @@ import { createApp } from "vue";
import App from "./App.vue";
function mountApp(): void {
const el = document.querySelector<HTMLDivElement>(".screen-wait");
if (!el) {
console.error(
"WaitPostProcessWorkflow: mount element .screen-wait not found",
);
return;
}
const el = document.querySelector<HTMLDivElement>(".screen-wait");
if (!el) {
console.error(
"WaitPostProcessWorkflow: mount element .screen-wait not found",
);
return;
}
const workflowIdAttr = el.getAttribute("data-workflow-id");
const expectedStep = el.getAttribute("data-expected-step") || "";
const workflowIdAttr = el.getAttribute("data-workflow-id");
const expectedStep = el.getAttribute("data-expected-step") || "";
if (!workflowIdAttr) {
console.error(
"WaitPostProcessWorkflow: data-workflow-id attribute missing on mount element",
);
return;
}
if (!workflowIdAttr) {
console.error(
"WaitPostProcessWorkflow: data-workflow-id attribute missing on mount element",
);
return;
}
if (!expectedStep) {
console.error(
"WaitPostProcessWorkflow: data-expected-step attribute missing on mount element",
);
return;
}
if (!expectedStep) {
console.error(
"WaitPostProcessWorkflow: data-expected-step attribute missing on mount element",
);
return;
}
const workflowId = Number(workflowIdAttr);
if (Number.isNaN(workflowId)) {
console.error(
"WaitPostProcessWorkflow: data-workflow-id is not a valid number:",
workflowIdAttr,
);
return;
}
const workflowId = Number(workflowIdAttr);
if (Number.isNaN(workflowId)) {
console.error(
"WaitPostProcessWorkflow: data-workflow-id is not a valid number:",
workflowIdAttr,
);
return;
}
const app = createApp(App, {
workflowId,
expectedStep,
});
const app = createApp(App, {
workflowId,
expectedStep,
});
app.mount(el);
app.mount(el);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", mountApp);
document.addEventListener("DOMContentLoaded", mountApp);
} else {
mountApp();
mountApp();
}

View File

@@ -9,17 +9,17 @@ import { fetchWorkflow } from "ChillMainAssets/lib/workflow/api";
import { trans, WORKFLOW_ATTACHMENTS_ADD_AN_ATTACHMENT } from "translator";
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>;
@@ -28,66 +28,66 @@ 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 workflow = ref<EntityWorkflow | null>(null);
onMounted(async () => {
workflow.value = await fetchWorkflow(Number(props.workflowId));
console.log("workflow", workflow.value);
workflow.value = await fetchWorkflow(Number(props.workflowId));
console.log("workflow", workflow.value);
});
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);
};
const canEditAttachement = computed<boolean>(() => {
if (null === workflow.value) {
return false;
}
if (null === workflow.value) {
return false;
}
return workflow.value._permissions.CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT;
return workflow.value._permissions.CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT;
});
</script>
<template>
<pick-generic-doc-modal
:workflow="workflow"
:accompanying-period-id="props.accompanyingPeriodId"
:to-remove="attachedGenericDoc"
ref="pickDocModal"
@pickGenericDoc="onPickGenericDoc"
></pick-generic-doc-modal>
<attachment-list
:workflow="workflow"
:attachments="props.attachments"
@removeAttachment="onRemoveAttachment"
></attachment-list>
<ul v-if="canEditAttachement" class="record_actions">
<li>
<button type="button" class="btn btn-create" @click="openModal">
{{ trans(WORKFLOW_ATTACHMENTS_ADD_AN_ATTACHMENT) }}
</button>
</li>
</ul>
<pick-generic-doc-modal
:workflow="workflow"
:accompanying-period-id="props.accompanyingPeriodId"
:to-remove="attachedGenericDoc"
ref="pickDocModal"
@pickGenericDoc="onPickGenericDoc"
></pick-generic-doc-modal>
<attachment-list
:workflow="workflow"
:attachments="props.attachments"
@removeAttachment="onRemoveAttachment"
></attachment-list>
<ul v-if="canEditAttachement" class="record_actions">
<li>
<button type="button" class="btn btn-create" @click="openModal">
{{ trans(WORKFLOW_ATTACHMENTS_ADD_AN_ATTACHMENT) }}
</button>
</li>
</ul>
</template>
<style scoped lang="scss"></style>

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import {
AttachmentWithDocAndStored,
EntityWorkflow,
isAttachmentWithDocAndStored,
WorkflowAttachment,
AttachmentWithDocAndStored,
EntityWorkflow,
isAttachmentWithDocAndStored,
WorkflowAttachment,
} from "ChillMainAssets/types";
import GenericDocItemBox from "ChillMainAssets/vuejs/WorkflowAttachment/Component/GenericDocItemBox.vue";
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
@@ -11,63 +11,61 @@ import { computed } from "vue";
import { trans, WORKFLOW_ATTACHMENTS_NO_ATTACHMENT } from "translator";
interface AttachmentListProps {
attachments: WorkflowAttachment[];
workflow: EntityWorkflow | null;
attachments: WorkflowAttachment[];
workflow: EntityWorkflow | null;
}
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>();
const notNullAttachments = computed<AttachmentWithDocAndStored[]>(() =>
props.attachments.filter(
(a: WorkflowAttachment): a is AttachmentWithDocAndStored =>
isAttachmentWithDocAndStored(a),
),
props.attachments.filter(
(a: WorkflowAttachment): a is AttachmentWithDocAndStored =>
isAttachmentWithDocAndStored(a),
),
);
const canRemove = computed<boolean>((): boolean => {
if (null === props.workflow) {
return false;
}
if (null === props.workflow) {
return false;
}
return props.workflow._permissions.CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT;
return props.workflow._permissions.CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT;
});
</script>
<template>
<p
v-if="notNullAttachments.length === 0"
class="chill-no-data-statement text-center"
>
{{ trans(WORKFLOW_ATTACHMENTS_NO_ATTACHMENT) }}
</p>
<div v-else class="flex-table">
<div v-for="a in notNullAttachments" :key="a.id" class="item-bloc">
<generic-doc-item-box
:generic-doc="a.genericDoc"
></generic-doc-item-box>
<div class="item-row separator">
<ul class="record_actions">
<li>
<document-action-buttons-group
:stored-object="a.genericDoc.storedObject"
></document-action-buttons-group>
</li>
<li v-if="canRemove">
<button
type="button"
class="btn btn-delete"
@click="emit('removeAttachment', { attachment: a })"
></button>
</li>
</ul>
</div>
</div>
<p
v-if="notNullAttachments.length === 0"
class="chill-no-data-statement text-center"
>
{{ trans(WORKFLOW_ATTACHMENTS_NO_ATTACHMENT) }}
</p>
<div v-else class="flex-table">
<div v-for="a in notNullAttachments" :key="a.id" class="item-bloc">
<generic-doc-item-box :generic-doc="a.genericDoc"></generic-doc-item-box>
<div class="item-row separator">
<ul class="record_actions">
<li>
<document-action-buttons-group
:stored-object="a.genericDoc.storedObject"
></document-action-buttons-group>
</li>
<li v-if="canRemove">
<button
type="button"
class="btn btn-delete"
@click="emit('removeAttachment', { attachment: a })"
></button>
</li>
</ul>
</div>
</div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -2,17 +2,17 @@
import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc";
interface GenericDocItemBoxProps {
genericDoc: GenericDoc;
genericDoc: GenericDoc;
}
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,7 +1,7 @@
<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";
@@ -9,51 +9,50 @@ import { computed, onMounted, ref } from "vue";
import { EntityWorkflow } from "ChillMainAssets/types";
interface PickGenericDocProps {
workflow: EntityWorkflow | null;
accompanyingPeriodId: number;
pickedList: GenericDocForAccompanyingPeriod[];
toRemove: GenericDocForAccompanyingPeriod[];
workflow: EntityWorkflow | null;
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 () => {
const fetchedGenericDocs = await fetch_generic_docs_by_accompanying_period(
props.accompanyingPeriodId,
);
const documentClasses = [
"Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument",
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
"Chill\\DocStoreBundle\\Entity\\PersonDocument",
];
const fetchedGenericDocs = await fetch_generic_docs_by_accompanying_period(
props.accompanyingPeriodId,
);
const documentClasses = [
"Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument",
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
"Chill\\DocStoreBundle\\Entity\\PersonDocument",
];
genericDocs.value = fetchedGenericDocs.filter(
(doc) =>
!documentClasses.includes(
props.workflow?.relatedEntityClass || "",
) || props.workflow?.relatedEntityId !== doc.identifiers.id,
);
loaded.value = true;
genericDocs.value = fetchedGenericDocs.filter(
(doc) =>
!documentClasses.includes(props.workflow?.relatedEntityClass || "") ||
props.workflow?.relatedEntityId !== doc.identifiers.id,
);
loaded.value = true;
});
const textFilter = ref<string>("");
@@ -62,229 +61,213 @@ 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 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>
</div>
</div>
</div>
</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>
<div v-else class="text-center chill-no-data-statement">
Aucun document dans ce parcours
</div>
</div>
<div v-else>
<div class="d-flex align-items-center">
<strong>Chargement</strong>
<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="spinner-border ms-auto"
role="status"
aria-hidden="true"
></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">
<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>
</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>
<div v-else class="text-center chill-no-data-statement">
Aucun document dans ce parcours
</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

@@ -6,20 +6,20 @@ import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/gener
import { EntityWorkflow } from "ChillMainAssets/types";
interface PickGenericDocModalProps {
workflow: EntityWorkflow | null;
accompanyingPeriodId: number;
toRemove: GenericDocForAccompanyingPeriod[];
workflow: EntityWorkflow | null;
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);
@@ -29,88 +29,88 @@ 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
:workflow="props.workflow"
: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
:workflow="props.workflow"
: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

@@ -5,7 +5,7 @@
<script setup lang="ts">
import { computed } from "vue";
import type { Gender } from "ChillMainAssets/types";
import {toGenderTranslation} from "ChillMainAssets/lib/api/genderHelper";
import { toGenderTranslation } from "ChillMainAssets/lib/api/genderHelper";
interface GenderIconRenderBoxProps {
gender: Gender;

View File

@@ -1,79 +1,71 @@
<template>
<div class="d-grid gap-2 my-3">
<button
class="btn btn-outline-primary text-start d-flex align-items-center"
:class="{ active: subscriberFinal }"
type="button"
@click="
subscribeTo(
subscriberFinal ? 'unsubscribe' : 'subscribe',
'final',
)
"
>
<i
class="fa fa-fw me-2"
:class="subscriberFinal ? 'fa-check-square-o' : 'fa-square-o'"
></i>
<span>{{ trans(WORKFLOW_SUBSCRIBE_FINAL) }}</span>
</button>
<button
class="btn btn-outline-primary text-start d-flex align-items-center"
:class="{ active: subscriberStep }"
type="button"
@click="
subscribeTo(
subscriberStep ? 'unsubscribe' : 'subscribe',
'step',
)
"
>
<i
class="fa fa-fw me-2"
:class="subscriberStep ? 'fa-check-square-o' : 'fa-square-o'"
></i>
<span>{{ trans(WORKFLOW_SUBSCRIBE_ALL_STEPS) }}</span>
</button>
</div>
<div class="d-grid gap-2 my-3">
<button
class="btn btn-outline-primary text-start d-flex align-items-center"
:class="{ active: subscriberFinal }"
type="button"
@click="
subscribeTo(subscriberFinal ? 'unsubscribe' : 'subscribe', 'final')
"
>
<i
class="fa fa-fw me-2"
:class="subscriberFinal ? 'fa-check-square-o' : 'fa-square-o'"
></i>
<span>{{ trans(WORKFLOW_SUBSCRIBE_FINAL) }}</span>
</button>
<button
class="btn btn-outline-primary text-start d-flex align-items-center"
:class="{ active: subscriberStep }"
type="button"
@click="subscribeTo(subscriberStep ? 'unsubscribe' : 'subscribe', 'step')"
>
<i
class="fa fa-fw me-2"
:class="subscriberStep ? 'fa-check-square-o' : 'fa-square-o'"
></i>
<span>{{ trans(WORKFLOW_SUBSCRIBE_ALL_STEPS) }}</span>
</button>
</div>
</template>
<script setup>
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
import { defineProps, defineEmits } from "vue";
import {
trans,
WORKFLOW_SUBSCRIBE_FINAL,
WORKFLOW_SUBSCRIBE_ALL_STEPS,
trans,
WORKFLOW_SUBSCRIBE_FINAL,
WORKFLOW_SUBSCRIBE_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,42 +1,39 @@
<template>
<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>
<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>
<!-- :: end styles bootstrap :: -->
<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>
</transition>
</div>
<!-- :: end styles bootstrap :: -->
</div>
</transition>
</template>
<script lang="ts" setup>
@@ -53,10 +50,10 @@ import { trans, MODAL_ACTION_CLOSE } from "translator";
import { defineProps } from "vue";
defineSlots<{
header?: () => any,
body?: () => any,
footer?: () => any,
"body-head"?: () => any,
header?: () => any;
body?: () => any;
footer?: () => any;
"body-head"?: () => any;
}>();
export interface ModalProps {
@@ -72,7 +69,7 @@ const props = withDefaults(defineProps<ModalProps>(), {
});
const emits = defineEmits<{
close: [];
close: [];
}>();
</script>
@@ -81,19 +78,19 @@ 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;
margin-right: 0;
margin-left: auto;
border-top-right-radius: 0.3rem;
margin-right: 0;
margin-left: auto;
}
/*
* The following styles are auto-applied to elements with
@@ -104,23 +101,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

@@ -2,61 +2,61 @@
import { WaitingScreenState } from "ChillMainAssets/types";
interface Props {
state: WaitingScreenState;
state: WaitingScreenState;
}
const props = defineProps<Props>();
</script>
<template>
<div id="waiting-screen">
<div
v-if="props.state === 'pending' && !!$slots.pending"
class="alert alert-danger text-center"
>
<div>
<slot name="pending"></slot>
</div>
<div id="waiting-screen">
<div
v-if="props.state === 'pending' && !!$slots.pending"
class="alert alert-danger text-center"
>
<div>
<slot name="pending"></slot>
</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="props.state === 'stopped' && !!$slots.stopped"
class="alert alert-info"
>
<div>
<slot name="stopped"></slot>
</div>
</div>
<div
v-if="props.state === 'failure' && !!$slots.failure"
class="alert alert-danger text-center"
>
<div>
<slot name="failure"></slot>
</div>
</div>
<div
v-if="props.state === 'ready' && !!$slots.ready"
class="alert alert-success text-center"
>
<div>
<slot name="ready"></slot>
</div>
</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="props.state === 'stopped' && !!$slots.stopped"
class="alert alert-info"
>
<div>
<slot name="stopped"></slot>
</div>
</div>
<div
v-if="props.state === 'failure' && !!$slots.failure"
class="alert alert-danger text-center"
>
<div>
<slot name="failure"></slot>
</div>
</div>
<div
v-if="props.state === 'ready' && !!$slots.ready"
class="alert alert-success text-center"
>
<div>
<slot name="ready"></slot>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
#waiting-screen {
> .alert {
min-height: 350px;
}
> .alert {
min-height: 350px;
}
}
</style>

View File

@@ -1,23 +1,25 @@
import {ref} from "vue";
import {ValidationExceptionInterface} from "ChillMainAssets/types";
import { ref } from "vue";
import { ValidationExceptionInterface } from "ChillMainAssets/types";
export function useViolationList<T extends Record<string, Record<string, string>>>() {
export function useViolationList<
T extends Record<string, Record<string, string>>,
>() {
type ViolationKey = Extract<keyof T, string>;
const violationsList = ref<ValidationExceptionInterface<T>|null>(null);
const violationsList = ref<ValidationExceptionInterface<T> | null>(null);
function violationTitles<P extends ViolationKey>(property: P): string[] {
if (null === violationsList.value) {
return [];
}
const r = violationsList.value.violationsByNormalizedProperty(property).map((v) => v.title);
const r = violationsList.value
.violationsByNormalizedProperty(property)
.map((v) => v.title);
return r;
}
function violationTitlesWithParameter<
P extends ViolationKey,
Param extends Extract<keyof T[P], string>
Param extends Extract<keyof T[P], string>,
>(
property: P,
with_parameter: Param,
@@ -26,26 +28,38 @@ export function useViolationList<T extends Record<string, Record<string, string>
if (violationsList.value === null) {
return [];
}
return violationsList.value.violationsByNormalizedPropertyAndParams(property, with_parameter, with_parameter_value)
return violationsList.value
.violationsByNormalizedPropertyAndParams(
property,
with_parameter,
with_parameter_value,
)
.map((v) => v.title);
}
function hasViolation<P extends ViolationKey>(property: P): boolean {
return violationTitles(property).length > 0;
}
function hasViolationWithParameter<
P extends ViolationKey,
Param extends Extract<keyof T[P], string>
Param extends Extract<keyof T[P], string>,
>(
property: P,
with_parameter: Param,
with_parameter_value: T[P][Param],
): boolean {
return violationTitlesWithParameter(property, with_parameter, with_parameter_value).length > 0;
return (
violationTitlesWithParameter(
property,
with_parameter,
with_parameter_value,
).length > 0
);
}
function setValidationException<V extends ValidationExceptionInterface<T>>(validationException: V): void {
function setValidationException<V extends ValidationExceptionInterface<T>>(
validationException: V,
): void {
violationsList.value = validationException;
}
@@ -53,5 +67,12 @@ export function useViolationList<T extends Record<string, Record<string, string>
violationsList.value = null;
}
return {violationTitles, violationTitlesWithParameter, setValidationException, cleanException, hasViolationWithParameter, hasViolation};
return {
violationTitles,
violationTitlesWithParameter,
setValidationException,
cleanException,
hasViolationWithParameter,
hasViolation,
};
}

View File

@@ -14,7 +14,8 @@ import {
DateTimeWrite,
SetGender,
SetCenter,
SetCivility, Gender,
SetCivility,
Gender,
} from "ChillMainAssets/types";
import { StoredObject } from "ChillDocStoreAssets/types";
import { Thirdparty } from "../../../ChillThirdPartyBundle/Resources/public/types";
@@ -157,16 +158,16 @@ export interface AccompanyingPeriod {
}
export interface AccompanyingPeriodWorkEvaluationDocument {
id: number;
type: "accompanying_period_work_evaluation_document";
storedObject: StoredObject;
title: string;
createdAt: DateTime | null;
createdBy: User | null;
updatedAt: DateTime | null;
updatedBy: User | null;
workflows_availables: WorkflowAvailable[];
workflows: object[];
id: number;
type: "accompanying_period_work_evaluation_document";
storedObject: StoredObject;
title: string;
createdAt: DateTime | null;
createdBy: User | null;
updatedAt: DateTime | null;
updatedBy: User | null;
workflows_availables: WorkflowAvailable[];
workflows: object[];
}
export interface AccompanyingPeriodWork {
@@ -195,139 +196,139 @@ export interface AccompanyingPeriodWork {
}
interface SocialAction {
id: number;
parent?: SocialAction | null;
children: SocialAction[];
issue?: SocialIssue | null;
ordering: number;
title: {
fr: string;
};
text: string;
defaultNotificationDelay?: string | null;
desactivationDate?: string | null;
evaluations: Evaluation[];
goals: Goal[];
results: Result[];
id: number;
parent?: SocialAction | null;
children: SocialAction[];
issue?: SocialIssue | null;
ordering: number;
title: {
fr: string;
};
text: string;
defaultNotificationDelay?: string | null;
desactivationDate?: string | null;
evaluations: Evaluation[];
goals: Goal[];
results: Result[];
}
export interface AccompanyingPeriodResource {
id: number;
accompanyingPeriod: AccompanyingPeriod;
comment?: string | null;
person?: Person | null;
thirdParty?: Thirdparty | null;
id: number;
accompanyingPeriod: AccompanyingPeriod;
comment?: string | null;
person?: Person | null;
thirdParty?: Thirdparty | null;
}
export interface Origin {
id: number;
label: {
fr: string;
};
noActiveAfter: DateTime;
id: number;
label: {
fr: string;
};
noActiveAfter: DateTime;
}
export interface ClosingMotive {
id: number;
active: boolean;
name: {
fr: string;
};
ordering: number;
isCanceledAccompanyingPeriod: boolean;
parent?: ClosingMotive | null;
children: ClosingMotive[];
id: number;
active: boolean;
name: {
fr: string;
};
ordering: number;
isCanceledAccompanyingPeriod: boolean;
parent?: ClosingMotive | null;
children: ClosingMotive[];
}
export interface AccompanyingPeriodParticipation {
id: number;
startDate: DateTime;
endDate?: DateTime | null;
accompanyingPeriod: AccompanyingPeriod;
person: Person;
id: number;
startDate: DateTime;
endDate?: DateTime | null;
accompanyingPeriod: AccompanyingPeriod;
person: Person;
}
export interface AccompanyingPeriodLocationHistory {
id: number;
startDate: DateTime;
endDate?: DateTime | null;
addressLocation?: Address | null;
period: AccompanyingPeriod;
personLocation?: Person | null;
id: number;
startDate: DateTime;
endDate?: DateTime | null;
addressLocation?: Address | null;
period: AccompanyingPeriod;
personLocation?: Person | null;
}
export interface SocialIssue {
id: number;
text: string;
parent?: SocialIssue | null;
children: SocialIssue[];
socialActions?: SocialAction[] | null;
ordering: number;
title: {
fr: string;
};
desactivationDate?: string | null;
id: number;
text: string;
parent?: SocialIssue | null;
children: SocialIssue[];
socialActions?: SocialAction[] | null;
ordering: number;
title: {
fr: string;
};
desactivationDate?: string | null;
}
export interface Goal {
id: number;
results: Result[];
socialActions?: SocialAction[] | null;
title: {
fr: string;
};
id: number;
results: Result[];
socialActions?: SocialAction[] | null;
title: {
fr: string;
};
}
export interface Result {
id: number;
accompanyingPeriodWorks: AccompanyingPeriodWork[];
accompanyingPeriodWorkGoals: AccompanyingPeriodWorkGoal[];
goals: Goal[];
socialActions: SocialAction[];
title: {
fr: string;
};
desactivationDate?: string | null;
id: number;
accompanyingPeriodWorks: AccompanyingPeriodWork[];
accompanyingPeriodWorkGoals: AccompanyingPeriodWorkGoal[];
goals: Goal[];
socialActions: SocialAction[];
title: {
fr: string;
};
desactivationDate?: string | null;
}
export interface AccompanyingPeriodWorkGoal {
id: number;
accompanyingPeriodWork: AccompanyingPeriodWork;
goal: Goal;
note: string;
results: Result[];
id: number;
accompanyingPeriodWork: AccompanyingPeriodWork;
goal: Goal;
note: string;
results: Result[];
}
export interface AccompanyingPeriodWorkEvaluation {
type: 'accompanying_period_work_evaluation';
accompanyingPeriodWork: AccompanyingPeriodWork | null;
comment: string;
createdAt: DateTime | null;
createdBy: User | null;
documents: AccompanyingPeriodWorkEvaluationDocument[];
endDate: DateTime | null;
evaluation: Evaluation | null;
id: number | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
key: any;
maxDate: DateTime | null;
startDate: DateTime | null;
updatedAt: DateTime | null;
updatedBy: User | null;
warningInterval: string | null;
timeSpent: number | null;
type: "accompanying_period_work_evaluation";
accompanyingPeriodWork: AccompanyingPeriodWork | null;
comment: string;
createdAt: DateTime | null;
createdBy: User | null;
documents: AccompanyingPeriodWorkEvaluationDocument[];
endDate: DateTime | null;
evaluation: Evaluation | null;
id: number | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
key: any;
maxDate: DateTime | null;
startDate: DateTime | null;
updatedAt: DateTime | null;
updatedBy: User | null;
warningInterval: string | null;
timeSpent: number | null;
}
export interface Evaluation {
id: number;
url: string;
socialActions: SocialAction[];
title: {
fr: string;
};
active: boolean;
delay: string;
notificationDelay: string;
id: number;
url: string;
socialActions: SocialAction[];
title: {
fr: string;
};
active: boolean;
delay: string;
notificationDelay: string;
}
export interface Step {
@@ -394,15 +395,15 @@ export interface AccompanyingCourse {
}
export interface AccompanyingPeriodWorkReferrerHistory {
id: number;
accompanyingPeriodWork: AccompanyingPeriodWork;
user: User;
startDate: DateTime;
endDate: DateTime | null;
createdAt: DateTime;
updatedAt: DateTime | null;
createdBy: User;
updatedBy: User | null;
id: number;
accompanyingPeriodWork: AccompanyingPeriodWork;
user: User;
startDate: DateTime;
endDate: DateTime | null;
createdAt: DateTime;
updatedAt: DateTime | null;
createdBy: User;
updatedBy: User | null;
}
export interface AccompanyingPeriodWorkEvaluationDocument {
@@ -432,7 +433,7 @@ export type EntityType =
| "user"
| "household";
export type Entities = (UserGroup | User | Person | Thirdparty | Household);
export type Entities = UserGroup | User | Person | Thirdparty | Household;
export function isEntityHousehold(e: Entities): e is Household {
return e.type === "household";
@@ -440,25 +441,34 @@ export function isEntityHousehold(e: Entities): e is Household {
export type EntitiesOrMe = "me" | Entities;
// Type guards to discriminate Suggestions by their result kind
export function isSuggestionForUserGroup(s: Suggestion): s is Suggestion & { result: UserGroup } {
export function isSuggestionForUserGroup(
s: Suggestion,
): s is Suggestion & { result: UserGroup } {
return (s as any)?.result?.type === "user_group";
}
export function isSuggestionForUser(s: Suggestion): s is Suggestion & { result: User } {
export function isSuggestionForUser(
s: Suggestion,
): s is Suggestion & { result: User } {
return (s as any)?.result?.type === "user";
}
export function isSuggestionForPerson(s: Suggestion): s is Suggestion & { result: Person } {
export function isSuggestionForPerson(
s: Suggestion,
): s is Suggestion & { result: Person } {
return (s as any)?.result?.type === "person";
}
export function isSuggestionForThirdParty(s: Suggestion): s is Suggestion & { result: Thirdparty } {
export function isSuggestionForThirdParty(
s: Suggestion,
): s is Suggestion & { result: Thirdparty } {
return (s as any)?.result?.type === "thirdparty";
}
export function isSuggestionForHousehold(s: Suggestion): s is Suggestion & { result: Household } {
export function isSuggestionForHousehold(
s: Suggestion,
): s is Suggestion & { result: Household } {
return (s as any)?.result?.type === "household";
}
@@ -482,7 +492,7 @@ export interface SearchPagination {
export interface Search {
count: number;
pagination: SearchPagination;
results: {relevance: number, result: Entities}[];
results: { relevance: number; result: Entities }[];
}
export interface SearchOptions {
@@ -498,7 +508,11 @@ export interface SearchOptions {
};
}
type PersonIdentifierPresence = 'NOT_EDITABLE' | 'ON_EDIT' | 'ON_CREATION' | 'REQUIRED';
type PersonIdentifierPresence =
| "NOT_EDITABLE"
| "ON_EDIT"
| "ON_CREATION"
| "REQUIRED";
export interface PersonIdentifierWorker {
type: "person_identifier_worker";

View File

@@ -42,9 +42,7 @@
</a>
</li>
<li>
<button
class="btn btn-save"
@click="modal.showModal = true">
<button class="btn btn-save" @click="modal.showModal = true">
{{ $t("confirm.ok") }}
</button>
</li>

View File

@@ -84,8 +84,12 @@
<ul class="record_actions">
<li class="add-persons">
<add-persons
:button-title="trans(ACCOMPANYING_COURSE_PERSONS_ASSOCIATED_ADD_PERSON)"
:modal-title="trans(ACCOMPANYING_COURSE_PERSONS_ASSOCIATED_ADD_PERSON)"
:button-title="
trans(ACCOMPANYING_COURSE_PERSONS_ASSOCIATED_ADD_PERSON)
"
:modal-title="
trans(ACCOMPANYING_COURSE_PERSONS_ASSOCIATED_ADD_PERSON)
"
:key="addPersons.key"
:options="addPersons.options"
@add-new-persons="addNewPersons"
@@ -108,7 +112,10 @@ import { mapGetters, mapState } from "vuex";
import ParticipationItem from "./PersonsAssociated/ParticipationItem.vue";
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
import {ACCOMPANYING_COURSE_PERSONS_ASSOCIATED_ADD_PERSON, trans} from "translator";
import {
ACCOMPANYING_COURSE_PERSONS_ASSOCIATED_ADD_PERSON,
trans,
} from "translator";
export default {
name: "PersonsAssociated",

View File

@@ -245,7 +245,7 @@ import Confidential from "ChillMainAssets/vuejs/_components/Confidential.vue";
import { mapState } from "vuex";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
import {ACCOMPANYING_COURSE_REQUESTOR_ADD, trans} from "translator";
import { ACCOMPANYING_COURSE_REQUESTOR_ADD, trans } from "translator";
export default {
name: "Requestor",

View File

@@ -59,7 +59,7 @@ import { mapState } from "vuex";
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
import ResourceItem from "./Resources/ResourceItem.vue";
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
import {ACCOMPANYING_COURSE_RESOURCES_ADD_RESOURCES, trans} from "translator";
import { ACCOMPANYING_COURSE_RESOURCES_ADD_RESOURCES, trans } from "translator";
export default {
name: "Resources",

View File

@@ -1,19 +1,19 @@
<template>
<div class="row mb-3">
<label class="col-sm-4 col-form-label visually-hidden">{{
trans(EVALUATION_PUBLIC_COMMENT)
}}</label>
<div class="col-sm-12">
<ckeditor
:editor="ClassicEditor"
:config="classicEditorConfig"
:placeholder="trans(EVALUATION_COMMENT_PLACEHOLDER)"
:value="comment"
@input="$emit('update:comment', $event)"
tag-name="textarea"
></ckeditor>
</div>
<div class="row mb-3">
<label class="col-sm-4 col-form-label visually-hidden">{{
trans(EVALUATION_PUBLIC_COMMENT)
}}</label>
<div class="col-sm-12">
<ckeditor
:editor="ClassicEditor"
:config="classicEditorConfig"
:placeholder="trans(EVALUATION_COMMENT_PLACEHOLDER)"
:value="comment"
@input="$emit('update:comment', $event)"
tag-name="textarea"
></ckeditor>
</div>
</div>
</template>
<script setup>
@@ -21,9 +21,9 @@ import { Ckeditor } from "@ckeditor/ckeditor5-vue";
import { ClassicEditor } from "ckeditor5";
import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config";
import {
EVALUATION_PUBLIC_COMMENT,
EVALUATION_COMMENT_PLACEHOLDER,
trans,
EVALUATION_PUBLIC_COMMENT,
EVALUATION_COMMENT_PLACEHOLDER,
trans,
} from "translator";
defineProps(["comment"]);

View File

@@ -1,71 +1,71 @@
<template>
<div class="row mb-3">
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
{{ trans(EVALUATION_STARTDATE) }}
</label>
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
<input
class="form-control form-control-sm"
type="date"
:value="startDate"
@input="$emit('update:startDate', $event.target.value)"
/>
</div>
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
{{ trans(EVALUATION_ENDDATE) }}
</label>
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
<input
class="form-control form-control-sm"
type="date"
:value="endDate"
@input="$emit('update:endDate', $event.target.value)"
/>
</div>
<div class="row mb-3">
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
{{ trans(EVALUATION_STARTDATE) }}
</label>
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
<input
class="form-control form-control-sm"
type="date"
:value="startDate"
@input="$emit('update:startDate', $event.target.value)"
/>
</div>
<div class="row mb-3">
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
{{ trans(EVALUATION_MAXDATE) }}
</label>
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
<input
class="form-control form-control-sm"
type="date"
:value="maxDate"
@input="$emit('update:maxDate', $event.target.value)"
/>
</div>
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
{{ trans(EVALUATION_WARNING_INTERVAL) }}
</label>
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
<input
class="form-control form-control-sm"
type="number"
:value="warningInterval"
@input="$emit('update:warningInterval', $event.target.value)"
/>
</div>
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
{{ trans(EVALUATION_ENDDATE) }}
</label>
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
<input
class="form-control form-control-sm"
type="date"
:value="endDate"
@input="$emit('update:endDate', $event.target.value)"
/>
</div>
</div>
<div class="row mb-3">
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
{{ trans(EVALUATION_MAXDATE) }}
</label>
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
<input
class="form-control form-control-sm"
type="date"
:value="maxDate"
@input="$emit('update:maxDate', $event.target.value)"
/>
</div>
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
{{ trans(EVALUATION_WARNING_INTERVAL) }}
</label>
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
<input
class="form-control form-control-sm"
type="number"
:value="warningInterval"
@input="$emit('update:warningInterval', $event.target.value)"
/>
</div>
</div>
</template>
<script setup>
import {
EVALUATION_STARTDATE,
EVALUATION_ENDDATE,
EVALUATION_MAXDATE,
EVALUATION_WARNING_INTERVAL,
trans,
EVALUATION_STARTDATE,
EVALUATION_ENDDATE,
EVALUATION_MAXDATE,
EVALUATION_WARNING_INTERVAL,
trans,
} from "translator";
defineProps(["startDate", "endDate", "maxDate", "warningInterval"]);
defineEmits([
"update:startDate",
"update:endDate",
"update:maxDate",
"update:warningInterval",
"update:startDate",
"update:endDate",
"update:maxDate",
"update:warningInterval",
]);
</script>

View File

@@ -1,43 +1,43 @@
<template>
<div class="row mb-3">
<h6>{{ trans(EVALUATION_DOCUMENT_ADD) }} :</h6>
<pick-template
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation"
:id="evaluation.id"
:templates="templates"
:preventDefaultMoveToGenerate="true"
@go-to-generate-document="submitBeforeGenerate"
>
<template v-slot:title>
<label class="col-form-label">{{
trans(EVALUATION_GENERATE_A_DOCUMENT)
}}</label>
</template>
</pick-template>
<div>
<label class="col-form-label">{{
trans(EVALUATION_DOCUMENT_UPLOAD)
}}</label>
<ul class="record_actions document-upload">
<li>
<drop-file-modal
:allow-remove="false"
@add-document="emit('addDocument', $event)"
></drop-file-modal>
</li>
</ul>
</div>
<div class="row mb-3">
<h6>{{ trans(EVALUATION_DOCUMENT_ADD) }} :</h6>
<pick-template
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation"
:id="evaluation.id"
:templates="templates"
:preventDefaultMoveToGenerate="true"
@go-to-generate-document="submitBeforeGenerate"
>
<template v-slot:title>
<label class="col-form-label">{{
trans(EVALUATION_GENERATE_A_DOCUMENT)
}}</label>
</template>
</pick-template>
<div>
<label class="col-form-label">{{
trans(EVALUATION_DOCUMENT_UPLOAD)
}}</label>
<ul class="record_actions document-upload">
<li>
<drop-file-modal
:allow-remove="false"
@add-document="emit('addDocument', $event)"
></drop-file-modal>
</li>
</ul>
</div>
</div>
</template>
<script setup>
import PickTemplate from "ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue";
import DropFileModal from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileModal.vue";
import {
EVALUATION_DOCUMENT_ADD,
EVALUATION_DOCUMENT_UPLOAD,
EVALUATION_GENERATE_A_DOCUMENT,
trans,
EVALUATION_DOCUMENT_ADD,
EVALUATION_DOCUMENT_UPLOAD,
EVALUATION_GENERATE_A_DOCUMENT,
trans,
} from "translator";
import { buildLink } from "ChillDocGeneratorAssets/lib/document-generator";
import { useStore } from "vuex";
@@ -48,29 +48,29 @@ const props = defineProps(["evaluation", "templates"]);
const emit = defineEmits(["addDocument"]);
async function submitBeforeGenerate({ template }) {
const callback = (data) => {
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(
(e) => e.key === props.evaluation.key,
).id;
const callback = (data) => {
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(
(e) => e.key === props.evaluation.key,
).id;
window.location.assign(
buildLink(
template,
evaluationId,
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation",
),
);
};
window.location.assign(
buildLink(
template,
evaluationId,
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation",
),
);
};
return store.dispatch("submit", callback).catch((e) => {
console.log(e);
throw e;
});
return store.dispatch("submit", callback).catch((e) => {
console.log(e);
throw e;
});
}
</script>
<style scoped>
ul.document-upload {
justify-content: flex-start;
justify-content: flex-start;
}
</style>

View File

@@ -1,288 +1,205 @@
<template>
<div class="row mb-3">
<h5>{{ trans(EVALUATION_DOCUMENTS) }} :</h5>
<div class="flex-table">
<div
class="item-bloc"
v-for="(d, i) in documents"
:key="d.id"
:class="[
parseInt(docAnchorId) === d.id ? 'bg-blink' : 'nothing',
]"
>
<div :id="'document_' + d.id" class="item-row">
<div class="input-group input-group-lg mb-3 row">
<label class="col-sm-3 col-form-label"
>Titre du document:</label
>
<div class="col-sm-9">
<input
class="form-control document-title"
type="text"
:value="d.title"
:id="d.id"
:data-key="i"
@input="$emit('inputDocumentTitle', $event)"
/>
</div>
</div>
</div>
<div class="item-row">
<div class="item-col item-meta">
<p v-if="d.createdBy" class="createdBy">
Créé par {{ d.createdBy.text }}<br />
Le
{{
$d(ISOToDatetime(d.createdAt.datetime), "long")
}}
</p>
</div>
</div>
<div class="item-row">
<div class="item-col">
<ul class="record_actions">
<li
v-if="
d.workflows_availables.length > 0 ||
d.workflows.length > 0
"
>
<list-workflow-modal
:workflows="d.workflows"
:allowCreate="true"
relatedEntityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument"
:relatedEntityId="d.id"
:workflowsAvailables="
d.workflows_availables
"
:preventDefaultMoveToGenerate="true"
:goToGenerateWorkflowPayload="{ doc: d }"
@go-to-generate-workflow="
goToGenerateWorkflowEvaluationDocument
"
></list-workflow-modal>
</li>
<li>
<button
v-if="AmIRefferer"
class="btn btn-notify"
@click="
$emit(
'goToGenerateNotification',
d,
false,
)
"
></button>
<template v-else>
<button
id="btnGroupNotifyButtons"
type="button"
class="btn btn-notify dropdown-toggle"
:title="
trans(EVALUATION_NOTIFICATION_SEND)
"
data-bs-toggle="dropdown"
aria-expanded="false"
>
&nbsp;
</button>
<ul
class="dropdown-menu"
aria-labelledby="btnGroupNotifyButtons"
>
<li>
<a
class="dropdown-item"
@click="
goToGenerateDocumentNotification(
d,
true,
)
"
>
{{
trans(
EVALUATION_NOTIFICATION_NOTIFY_REFERRER,
)
}}
</a>
</li>
<li>
<a
class="dropdown-item"
@click="
goToGenerateDocumentNotification(
d,
false,
)
"
>
{{
trans(
EVALUATION_NOTIFICATION_NOTIFY_ANY,
)
}}
</a>
</li>
</ul>
</template>
</li>
<li>
<document-action-buttons-group
:stored-object="d.storedObject"
:filename="d.title"
:can-edit="true"
:execute-before-leave="
submitBeforeLeaveToEditor
"
:davLink="
d.storedObject._links?.dav_link.href
"
:davLinkExpiration="
d.storedObject._links?.dav_link
.expiration
"
@on-stored-object-status-change="
$emit('statusDocumentChanged', $event)
"
></document-action-buttons-group>
</li>
<!--replace document-->
<li
v-if="
Number.isInteger(d.id) &&
d.storedObject._permissions.canEdit
"
>
<drop-file-modal
:existing-doc="d.storedObject"
:allow-remove="false"
@add-document="
(arg) =>
replaceDocument(
d,
arg.stored_object,
arg.stored_object_version,
)
"
></drop-file-modal>
</li>
<li v-if="Number.isInteger(d.id)">
<div class="duplicate-dropdown">
<button
class="btn btn-outline-primary dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i class="bi bi-lightning-fill"></i>
</button>
<ul class="dropdown-menu">
<!--delete-->
<li v-if="d.workflows.length === 0">
<a
class="dropdown-item"
@click="
$emit('removeDocument', d)
"
>
<i
class="fa fa-trash-o"
aria-hidden="true"
></i>
{{
trans(
EVALUATION_DOCUMENT_DELETE,
)
}}
</a>
</li>
<!--duplicate document-->
<li>
<a
class="dropdown-item"
@click="
$emit(
'duplicateDocument',
d,
)
"
>
<i
class="fa fa-copy"
aria-hidden="true"
></i>
{{
trans(
EVALUATION_DOCUMENT_DUPLICATE_HERE,
)
}}
</a>
</li>
<li>
<a
class="dropdown-item"
@click="
prepareDocumentDuplicationToWork(
d,
)
"
>
<i
class="fa fa-copy"
aria-hidden="true"
></i>
{{
trans(
EVALUATION_DOCUMENT_DUPLICATE_TO_OTHER_EVALUATION,
)
}}</a
>
</li>
<!--move document-->
<li
v-if="
d.storedObject._permissions
.canEdit
"
>
<a
class="dropdown-item"
@click="
prepareDocumentMoveToWork(d)
"
>
<i
class="fa fa-arrows"
aria-hidden="true"
></i>
{{
trans(
EVALUATION_DOCUMENT_MOVE,
)
}}</a
>
</li>
</ul>
</div>
</li>
</ul>
</div>
</div>
<div class="row mb-3">
<h5>{{ trans(EVALUATION_DOCUMENTS) }} :</h5>
<div class="flex-table">
<div
class="item-bloc"
v-for="(d, i) in documents"
:key="d.id"
:class="[parseInt(docAnchorId) === d.id ? 'bg-blink' : 'nothing']"
>
<div :id="'document_' + d.id" class="item-row">
<div class="input-group input-group-lg mb-3 row">
<label class="col-sm-3 col-form-label">Titre du document:</label>
<div class="col-sm-9">
<input
class="form-control document-title"
type="text"
:value="d.title"
:id="d.id"
:data-key="i"
@input="$emit('inputDocumentTitle', $event)"
/>
</div>
</div>
</div>
<div class="item-row">
<div class="item-col item-meta">
<p v-if="d.createdBy" class="createdBy">
Créé par {{ d.createdBy.text }}<br />
Le
{{ $d(ISOToDatetime(d.createdAt.datetime), "long") }}
</p>
</div>
</div>
<div class="item-row">
<div class="item-col">
<ul class="record_actions">
<li
v-if="
d.workflows_availables.length > 0 || d.workflows.length > 0
"
>
<list-workflow-modal
:workflows="d.workflows"
:allowCreate="true"
relatedEntityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument"
:relatedEntityId="d.id"
:workflowsAvailables="d.workflows_availables"
:preventDefaultMoveToGenerate="true"
:goToGenerateWorkflowPayload="{ doc: d }"
@go-to-generate-workflow="
goToGenerateWorkflowEvaluationDocument
"
></list-workflow-modal>
</li>
<li>
<button
v-if="AmIRefferer"
class="btn btn-notify"
@click="$emit('goToGenerateNotification', d, false)"
></button>
<template v-else>
<button
id="btnGroupNotifyButtons"
type="button"
class="btn btn-notify dropdown-toggle"
:title="trans(EVALUATION_NOTIFICATION_SEND)"
data-bs-toggle="dropdown"
aria-expanded="false"
>
&nbsp;
</button>
<ul
class="dropdown-menu"
aria-labelledby="btnGroupNotifyButtons"
>
<li>
<a
class="dropdown-item"
@click="goToGenerateDocumentNotification(d, true)"
>
{{ trans(EVALUATION_NOTIFICATION_NOTIFY_REFERRER) }}
</a>
</li>
<li>
<a
class="dropdown-item"
@click="goToGenerateDocumentNotification(d, false)"
>
{{ trans(EVALUATION_NOTIFICATION_NOTIFY_ANY) }}
</a>
</li>
</ul>
</template>
</li>
<li>
<document-action-buttons-group
:stored-object="d.storedObject"
:filename="d.title"
:can-edit="true"
:execute-before-leave="submitBeforeLeaveToEditor"
:davLink="d.storedObject._links?.dav_link.href"
:davLinkExpiration="
d.storedObject._links?.dav_link.expiration
"
@on-stored-object-status-change="
$emit('statusDocumentChanged', $event)
"
></document-action-buttons-group>
</li>
<!--replace document-->
<li
v-if="
Number.isInteger(d.id) && d.storedObject._permissions.canEdit
"
>
<drop-file-modal
:existing-doc="d.storedObject"
:allow-remove="false"
@add-document="
(arg) =>
replaceDocument(
d,
arg.stored_object,
arg.stored_object_version,
)
"
></drop-file-modal>
</li>
<li v-if="Number.isInteger(d.id)">
<div class="duplicate-dropdown">
<button
class="btn btn-outline-primary dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i class="bi bi-lightning-fill"></i>
</button>
<ul class="dropdown-menu">
<!--delete-->
<li v-if="d.workflows.length === 0">
<a
class="dropdown-item"
@click="$emit('removeDocument', d)"
>
<i class="fa fa-trash-o" aria-hidden="true"></i>
{{ trans(EVALUATION_DOCUMENT_DELETE) }}
</a>
</li>
<!--duplicate document-->
<li>
<a
class="dropdown-item"
@click="$emit('duplicateDocument', d)"
>
<i class="fa fa-copy" aria-hidden="true"></i>
{{ trans(EVALUATION_DOCUMENT_DUPLICATE_HERE) }}
</a>
</li>
<li>
<a
class="dropdown-item"
@click="prepareDocumentDuplicationToWork(d)"
>
<i class="fa fa-copy" aria-hidden="true"></i>
{{
trans(
EVALUATION_DOCUMENT_DUPLICATE_TO_OTHER_EVALUATION,
)
}}</a
>
</li>
<!--move document-->
<li v-if="d.storedObject._permissions.canEdit">
<a
class="dropdown-item"
@click="prepareDocumentMoveToWork(d)"
>
<i class="fa fa-arrows" aria-hidden="true"></i>
{{ trans(EVALUATION_DOCUMENT_MOVE) }}</a
>
</li>
</ul>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<AccompanyingPeriodWorkSelectorModal
v-if="showAccompanyingPeriodSelector"
v-model:selectedAcpw="selectedAcpw"
:accompanying-period-id="accompanyingPeriodId"
:is-evaluation-selector="true"
:ignore-accompanying-period-work-ids="[]"
@close-modal="showAccompanyingPeriodSelector = false"
@update:selectedEvaluation="selectedEvaluation = $event"
/>
<AccompanyingPeriodWorkSelectorModal
v-if="showAccompanyingPeriodSelector"
v-model:selectedAcpw="selectedAcpw"
:accompanying-period-id="accompanyingPeriodId"
:is-evaluation-selector="true"
:ignore-accompanying-period-work-ids="[]"
@close-modal="showAccompanyingPeriodSelector = false"
@update:selectedEvaluation="selectedEvaluation = $event"
/>
</template>
<script setup>
@@ -291,15 +208,15 @@ import ListWorkflowModal from "ChillMainAssets/vuejs/_components/EntityWorkflow/
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
import DropFileModal from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileModal.vue";
import {
EVALUATION_NOTIFICATION_NOTIFY_REFERRER,
EVALUATION_NOTIFICATION_NOTIFY_ANY,
EVALUATION_NOTIFICATION_SEND,
EVALUATION_DOCUMENTS,
EVALUATION_DOCUMENT_MOVE,
EVALUATION_DOCUMENT_DELETE,
EVALUATION_DOCUMENT_DUPLICATE_HERE,
EVALUATION_DOCUMENT_DUPLICATE_TO_OTHER_EVALUATION,
trans,
EVALUATION_NOTIFICATION_NOTIFY_REFERRER,
EVALUATION_NOTIFICATION_NOTIFY_ANY,
EVALUATION_NOTIFICATION_SEND,
EVALUATION_DOCUMENTS,
EVALUATION_DOCUMENT_MOVE,
EVALUATION_DOCUMENT_DELETE,
EVALUATION_DOCUMENT_DUPLICATE_HERE,
EVALUATION_DOCUMENT_DUPLICATE_TO_OTHER_EVALUATION,
trans,
} from "translator";
import { computed, ref, watch } from "vue";
import AccompanyingPeriodWorkSelectorModal from "ChillPersonAssets/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue";
@@ -308,17 +225,17 @@ import { buildLinkCreate as buildLinkCreateNotification } from "ChillMainAssets/
import { useStore } from "vuex";
const props = defineProps([
"documents",
"docAnchorId",
"accompanyingPeriodId",
"evaluation",
"documents",
"docAnchorId",
"accompanyingPeriodId",
"evaluation",
]);
const emit = defineEmits([
"inputDocumentTitle",
"removeDocument",
"duplicateDocument",
"statusDocumentChanged",
"duplicateDocumentToWork",
"inputDocumentTitle",
"removeDocument",
"duplicateDocument",
"statusDocumentChanged",
"duplicateDocumentToWork",
]);
const store = useStore();
@@ -329,67 +246,67 @@ const selectedDocumentToDuplicate = ref(null);
const selectedDocumentToMove = ref(null);
const AmIRefferer = computed(() => {
return !(
store.state.work.accompanyingPeriod.user &&
store.state.me &&
store.state.work.accompanyingPeriod.user.id !== store.state.me.id
);
return !(
store.state.work.accompanyingPeriod.user &&
store.state.me &&
store.state.work.accompanyingPeriod.user.id !== store.state.me.id
);
});
const prepareDocumentDuplicationToWork = (d) => {
selectedDocumentToDuplicate.value = d;
/** ensure selectedDocumentToMove is null */
selectedDocumentToMove.value = null;
selectedDocumentToDuplicate.value = d;
/** ensure selectedDocumentToMove is null */
selectedDocumentToMove.value = null;
showAccompanyingPeriodSelector.value = true;
showAccompanyingPeriodSelector.value = true;
};
const prepareDocumentMoveToWork = (d) => {
selectedDocumentToMove.value = d;
/** ensure selectedDocumentToDuplicate is null */
selectedDocumentToDuplicate.value = null;
selectedDocumentToMove.value = d;
/** ensure selectedDocumentToDuplicate is null */
selectedDocumentToDuplicate.value = null;
showAccompanyingPeriodSelector.value = true;
showAccompanyingPeriodSelector.value = true;
};
watch(selectedEvaluation, (val) => {
if (selectedDocumentToDuplicate.value) {
emit("duplicateDocumentToEvaluation", {
evaluation: val,
document: selectedDocumentToDuplicate.value,
});
} else {
emit("moveDocumentToEvaluation", {
evaluationDest: val,
document: selectedDocumentToMove.value,
});
}
if (selectedDocumentToDuplicate.value) {
emit("duplicateDocumentToEvaluation", {
evaluation: val,
document: selectedDocumentToDuplicate.value,
});
} else {
emit("moveDocumentToEvaluation", {
evaluationDest: val,
document: selectedDocumentToMove.value,
});
}
});
async function goToGenerateWorkflowEvaluationDocument({
workflowName,
payload,
workflowName,
payload,
}) {
const callback = (data) => {
let evaluation = data.accompanyingPeriodWorkEvaluations.find(
(e) => e.key === props.evaluation.key,
);
let updatedDocument = evaluation.documents.find(
(d) => d.key === payload.doc.key,
);
window.location.assign(
buildLinkCreate(
workflowName,
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
updatedDocument.id,
),
);
};
const callback = (data) => {
let evaluation = data.accompanyingPeriodWorkEvaluations.find(
(e) => e.key === props.evaluation.key,
);
let updatedDocument = evaluation.documents.find(
(d) => d.key === payload.doc.key,
);
window.location.assign(
buildLinkCreate(
workflowName,
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
updatedDocument.id,
),
);
};
return store.dispatch("submit", callback).catch((e) => {
console.log(e);
throw e;
});
return store.dispatch("submit", callback).catch((e) => {
console.log(e);
throw e;
});
}
/**
@@ -401,55 +318,53 @@ async function goToGenerateWorkflowEvaluationDocument({
* @return {void}
*/
async function replaceDocument(oldDocument, storedObject, storedObjectVersion) {
let document = {
type: "accompanying_period_work_evaluation_document",
storedObject: storedObject,
title: oldDocument.title,
};
let document = {
type: "accompanying_period_work_evaluation_document",
storedObject: storedObject,
title: oldDocument.title,
};
return store.commit("replaceDocument", {
key: props.evaluation.key,
document,
oldDocument: oldDocument,
stored_object_version: storedObjectVersion,
});
return store.commit("replaceDocument", {
key: props.evaluation.key,
document,
oldDocument: oldDocument,
stored_object_version: storedObjectVersion,
});
}
async function goToGenerateDocumentNotification(document, tos) {
const callback = (data) => {
let evaluation = data.accompanyingPeriodWorkEvaluations.find(
(e) => e.key === props.evaluation.key,
);
let updatedDocument = evaluation.documents.find(
(d) => d.key === document.key,
);
window.location.assign(
buildLinkCreateNotification(
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
updatedDocument.id,
tos === true
? store.state.work.accompanyingPeriod.user?.id
: null,
window.location.pathname +
window.location.search +
window.location.hash,
),
);
};
const callback = (data) => {
let evaluation = data.accompanyingPeriodWorkEvaluations.find(
(e) => e.key === props.evaluation.key,
);
let updatedDocument = evaluation.documents.find(
(d) => d.key === document.key,
);
window.location.assign(
buildLinkCreateNotification(
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
updatedDocument.id,
tos === true ? store.state.work.accompanyingPeriod.user?.id : null,
window.location.pathname +
window.location.search +
window.location.hash,
),
);
};
return store.dispatch("submit", callback).catch((e) => {
console.log(e);
throw e;
});
return store.dispatch("submit", callback).catch((e) => {
console.log(e);
throw e;
});
}
async function submitBeforeLeaveToEditor() {
console.log("submit beore edit 2");
// empty callback
const callback = () => null;
return store.dispatch("submit", callback).catch((e) => {
console.log(e);
throw e;
});
console.log("submit beore edit 2");
// empty callback
const callback = () => null;
return store.dispatch("submit", callback).catch((e) => {
console.log(e);
throw e;
});
}
</script>

View File

@@ -1,49 +1,47 @@
<template>
<div>
<div class="m-md-3">
<DateInputs
:startDate="startDate"
:endDate="endDate"
:maxDate="maxDate"
:warningInterval="warningInterval"
@update:startDate="updateStartDate"
@update:endDate="updateEndDate"
@update:maxDate="updateMaxDate"
@update:warningInterval="updateWarningInterval"
/>
<div>
<div class="m-md-3">
<DateInputs
:startDate="startDate"
:endDate="endDate"
:maxDate="maxDate"
:warningInterval="warningInterval"
@update:startDate="updateStartDate"
@update:endDate="updateEndDate"
@update:maxDate="updateMaxDate"
@update:warningInterval="updateWarningInterval"
/>
<TimeSpentInput
:timeSpent="timeSpent"
:timeSpentChoices="timeSpentChoices"
@update:timeSpent="updateTimeSpent"
/>
<TimeSpentInput
:timeSpent="timeSpent"
:timeSpentChoices="timeSpentChoices"
@update:timeSpent="updateTimeSpent"
/>
<CommentInput :comment="comment" @update:comment="updateComment" />
<CommentInput :comment="comment" @update:comment="updateComment" />
<DocumentsList
v-if="evaluation.documents.length > 0"
:documents="evaluation.documents"
:docAnchorId="docAnchorId"
:evaluation="evaluation"
:accompanyingPeriodId="store.state.work.accompanyingPeriod.id"
@inputDocumentTitle="onInputDocumentTitle"
@removeDocument="removeDocument"
@duplicateDocument="duplicateDocument"
@duplicate-document-to-evaluation="
duplicateDocumentToEvaluation
"
@move-document-to-evaluation="moveDocumentToEvaluation"
@statusDocumentChanged="onStatusDocumentChanged"
@goToGenerateNotification="goToGenerateDocumentNotification"
/>
<DocumentsList
v-if="evaluation.documents.length > 0"
:documents="evaluation.documents"
:docAnchorId="docAnchorId"
:evaluation="evaluation"
:accompanyingPeriodId="store.state.work.accompanyingPeriod.id"
@inputDocumentTitle="onInputDocumentTitle"
@removeDocument="removeDocument"
@duplicateDocument="duplicateDocument"
@duplicate-document-to-evaluation="duplicateDocumentToEvaluation"
@move-document-to-evaluation="moveDocumentToEvaluation"
@statusDocumentChanged="onStatusDocumentChanged"
@goToGenerateNotification="goToGenerateDocumentNotification"
/>
<DocumentActions
:evaluation="evaluation"
:templates="getTemplatesAvailables"
@addDocument="addDocument"
/>
</div>
<DocumentActions
:evaluation="evaluation"
:templates="getTemplatesAvailables"
@addDocument="addDocument"
/>
</div>
</div>
</template>
<script setup>
@@ -55,9 +53,9 @@ import CommentInput from "./CommentInput.vue";
import DocumentsList from "./DocumentsList.vue";
import DocumentActions from "./DocumentActions.vue";
import {
trans,
EVALUATION_DOCUMENT_DUPLICATE_SUCCESS,
EVALUATION_DOCUMENT_MOVE_SUCCESS,
trans,
EVALUATION_DOCUMENT_DUPLICATE_SUCCESS,
EVALUATION_DOCUMENT_MOVE_SUCCESS,
} from "translator";
import { useToast } from "vue-toast-notification";
import { buildLinkCreate as buildLinkCreateNotification } from "ChillMainAssets/lib/entity-notification/api";
@@ -68,359 +66,357 @@ const store = useStore();
const $toast = useToast();
const timeSpentValues = [
60,
120,
180,
240,
300,
600,
900,
1200,
1500,
1800,
2700,
3600,
4500,
5400,
6300,
7200,
9000,
10800,
12600,
14400,
16200,
18000,
19800,
21600,
23400,
25200,
27000,
28800,
43200,
57600,
72000,
86400,
100800,
115200,
129600,
144000, // goes from 1 minute to 40 hours
60,
120,
180,
240,
300,
600,
900,
1200,
1500,
1800,
2700,
3600,
4500,
5400,
6300,
7200,
9000,
10800,
12600,
14400,
16200,
18000,
19800,
21600,
23400,
25200,
27000,
28800,
43200,
57600,
72000,
86400,
100800,
115200,
129600,
144000, // goes from 1 minute to 40 hours
];
const formatDuration = (seconds, locale) => {
const currentLocale = locale || navigator.language || "fr";
const currentLocale = locale || navigator.language || "fr";
const totalHours = Math.floor(seconds / 3600);
const remainingMinutes = Math.floor((seconds % 3600) / 60);
const totalHours = Math.floor(seconds / 3600);
const remainingMinutes = Math.floor((seconds % 3600) / 60);
if (totalHours >= 8) {
const days = Math.floor(totalHours / 8);
const remainingHours = totalHours % 8;
if (totalHours >= 8) {
const days = Math.floor(totalHours / 8);
const remainingHours = totalHours % 8;
const parts = [];
if (days > 0) {
parts.push(
new Intl.NumberFormat(currentLocale, {
style: "unit",
unit: "day",
unitDisplay: "long",
}).format(days),
);
}
if (remainingHours > 0) {
parts.push(
new Intl.NumberFormat(currentLocale, {
style: "unit",
unit: "hour",
unitDisplay: "long",
}).format(remainingHours),
);
}
return parts.join(" ");
}
// For less than 8 hours, use hour and minute format
const parts = [];
if (totalHours > 0) {
parts.push(
new Intl.NumberFormat(currentLocale, {
style: "unit",
unit: "hour",
unitDisplay: "long",
}).format(totalHours),
);
if (days > 0) {
parts.push(
new Intl.NumberFormat(currentLocale, {
style: "unit",
unit: "day",
unitDisplay: "long",
}).format(days),
);
}
if (remainingMinutes > 0) {
parts.push(
new Intl.NumberFormat(currentLocale, {
style: "unit",
unit: "minute",
unitDisplay: "long",
}).format(remainingMinutes),
);
if (remainingHours > 0) {
parts.push(
new Intl.NumberFormat(currentLocale, {
style: "unit",
unit: "hour",
unitDisplay: "long",
}).format(remainingHours),
);
}
console.log(parts);
console.log(parts.join(" "));
return parts.join(" ");
}
// For less than 8 hours, use hour and minute format
const parts = [];
if (totalHours > 0) {
parts.push(
new Intl.NumberFormat(currentLocale, {
style: "unit",
unit: "hour",
unitDisplay: "long",
}).format(totalHours),
);
}
if (remainingMinutes > 0) {
parts.push(
new Intl.NumberFormat(currentLocale, {
style: "unit",
unit: "minute",
unitDisplay: "long",
}).format(remainingMinutes),
);
}
console.log(parts);
console.log(parts.join(" "));
return parts.join(" ");
};
const timeSpentChoices = computed(() => {
const locale = "fr";
return timeSpentValues.map((value) => ({
text: formatDuration(value, locale),
value: parseInt(value),
}));
const locale = "fr";
return timeSpentValues.map((value) => ({
text: formatDuration(value, locale),
value: parseInt(value),
}));
});
const startDate = computed({
get() {
return props.evaluation.startDate;
},
set(v) {
store.commit("setEvaluationStartDate", {
key: props.evaluation.key,
date: v,
});
},
get() {
return props.evaluation.startDate;
},
set(v) {
store.commit("setEvaluationStartDate", {
key: props.evaluation.key,
date: v,
});
},
});
const endDate = computed({
get() {
return props.evaluation.endDate;
},
set(v) {
store.commit("setEvaluationEndDate", {
key: props.evaluation.key,
date: v,
});
},
get() {
return props.evaluation.endDate;
},
set(v) {
store.commit("setEvaluationEndDate", {
key: props.evaluation.key,
date: v,
});
},
});
const maxDate = computed({
get() {
return props.evaluation.maxDate;
},
set(v) {
store.commit("setEvaluationMaxDate", {
key: props.evaluation.key,
date: v,
});
},
get() {
return props.evaluation.maxDate;
},
set(v) {
store.commit("setEvaluationMaxDate", {
key: props.evaluation.key,
date: v,
});
},
});
const warningInterval = computed({
get() {
return props.evaluation.warningInterval;
},
set(v) {
store.commit("setEvaluationWarningInterval", {
key: props.evaluation.key,
days: v,
});
},
get() {
return props.evaluation.warningInterval;
},
set(v) {
store.commit("setEvaluationWarningInterval", {
key: props.evaluation.key,
days: v,
});
},
});
const timeSpent = computed({
get() {
return props.evaluation.timeSpent;
},
set(v) {
store.commit("setEvaluationTimeSpent", {
key: props.evaluation.key,
time: v,
});
},
get() {
return props.evaluation.timeSpent;
},
set(v) {
store.commit("setEvaluationTimeSpent", {
key: props.evaluation.key,
time: v,
});
},
});
const comment = computed({
get() {
return props.evaluation.comment;
},
set(v) {
store.commit("setEvaluationComment", {
key: props.evaluation.key,
comment: v,
});
},
get() {
return props.evaluation.comment;
},
set(v) {
store.commit("setEvaluationComment", {
key: props.evaluation.key,
comment: v,
});
},
});
const getTemplatesAvailables = computed(() => {
return store.getters.getTemplatesAvailablesForEvaluation(
props.evaluation.evaluation,
);
return store.getters.getTemplatesAvailablesForEvaluation(
props.evaluation.evaluation,
);
});
// const getAccompanyingPeriod = computed(() => store.work)
function updateStartDate(value) {
startDate.value = value;
startDate.value = value;
}
function updateEndDate(value) {
endDate.value = value;
endDate.value = value;
}
function updateMaxDate(value) {
maxDate.value = value;
maxDate.value = value;
}
function updateWarningInterval(value) {
warningInterval.value = value;
warningInterval.value = value;
}
function updateTimeSpent(value) {
timeSpent.value = parseInt(value);
timeSpent.value = parseInt(value);
}
function updateComment(value) {
comment.value = value;
comment.value = value;
}
function onInputDocumentTitle(event) {
const id = Number(event.target.id);
const key = Number(event.target.dataset.key) + 1;
const title = event.target.value;
store.commit("updateDocumentTitle", {
id: id,
key: key,
evaluationKey: props.evaluation.key,
title: title,
});
const id = Number(event.target.id);
const key = Number(event.target.dataset.key) + 1;
const title = event.target.value;
store.commit("updateDocumentTitle", {
id: id,
key: key,
evaluationKey: props.evaluation.key,
title: title,
});
}
function addDocument({ stored_object, stored_object_version, file_name }) {
let document = {
type: "accompanying_period_work_evaluation_document",
storedObject: stored_object,
title: file_name,
};
store.commit("addDocument", {
key: props.evaluation.key,
document,
stored_object_version,
});
let document = {
type: "accompanying_period_work_evaluation_document",
storedObject: stored_object,
title: file_name,
};
store.commit("addDocument", {
key: props.evaluation.key,
document,
stored_object_version,
});
}
function removeDocument(document) {
if (
window.confirm(
'Êtes-vous sûr·e de vouloir supprimer le document qui a pour titre "' +
document.title +
'" ?',
)
) {
store.commit("removeDocument", {
key: props.evaluation.key,
document: document,
});
}
if (
window.confirm(
'Êtes-vous sûr·e de vouloir supprimer le document qui a pour titre "' +
document.title +
'" ?',
)
) {
store.commit("removeDocument", {
key: props.evaluation.key,
document: document,
});
}
}
function duplicateDocument(document) {
store.dispatch("duplicateDocument", {
evaluation_key: props.evaluation.key,
document: document,
});
store.dispatch("duplicateDocument", {
evaluation_key: props.evaluation.key,
document: document,
});
}
function duplicateDocumentToEvaluation({ evaluation, document }) {
store
.dispatch("duplicateDocumentToEvaluation", {
evaluation: evaluation,
document: document,
})
.then(() => {
$toast.open({
message: trans(EVALUATION_DOCUMENT_DUPLICATE_SUCCESS),
});
})
.catch((e) => {
console.log(e);
});
store
.dispatch("duplicateDocumentToEvaluation", {
evaluation: evaluation,
document: document,
})
.then(() => {
$toast.open({
message: trans(EVALUATION_DOCUMENT_DUPLICATE_SUCCESS),
});
})
.catch((e) => {
console.log(e);
});
}
function moveDocumentToEvaluation({ evaluationDest, document }) {
console.log("dest eval in formEvaluation", evaluationDest);
store
.dispatch("moveDocumentToEvaluation", {
evaluationInitial: props.evaluation,
evaluationDest: evaluationDest,
document: document,
})
.then(() => {
$toast.open({
message: trans(EVALUATION_DOCUMENT_MOVE_SUCCESS),
});
})
.catch((e) => {
console.log(e);
});
console.log("dest eval in formEvaluation", evaluationDest);
store
.dispatch("moveDocumentToEvaluation", {
evaluationInitial: props.evaluation,
evaluationDest: evaluationDest,
document: document,
})
.then(() => {
$toast.open({
message: trans(EVALUATION_DOCUMENT_MOVE_SUCCESS),
});
})
.catch((e) => {
console.log(e);
});
}
function onStatusDocumentChanged(newStatus) {
store.commit("statusDocumentChanged", {
key: props.evaluation.key,
newStatus: newStatus,
});
store.commit("statusDocumentChanged", {
key: props.evaluation.key,
newStatus: newStatus,
});
}
function goToGenerateDocumentNotification(document, tos) {
const callback = (data) => {
let evaluation = data.accompanyingPeriodWorkEvaluations.find(
(e) => e.key === props.evaluation.key,
);
let updatedDocument = evaluation.documents.find(
(d) => d.key === document.key,
);
window.location.assign(
buildLinkCreateNotification(
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
updatedDocument.id,
tos === true
? store.state.work.accompanyingPeriod.user.id
: null,
window.location.pathname +
window.location.search +
window.location.hash,
),
);
};
store.dispatch("submit", callback).catch((e) => {
console.log(e);
throw e;
});
const callback = (data) => {
let evaluation = data.accompanyingPeriodWorkEvaluations.find(
(e) => e.key === props.evaluation.key,
);
let updatedDocument = evaluation.documents.find(
(d) => d.key === document.key,
);
window.location.assign(
buildLinkCreateNotification(
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
updatedDocument.id,
tos === true ? store.state.work.accompanyingPeriod.user.id : null,
window.location.pathname +
window.location.search +
window.location.hash,
),
);
};
store.dispatch("submit", callback).catch((e) => {
console.log(e);
throw e;
});
}
</script>
<style lang="scss" scoped>
input.document-title {
font-weight: bold;
font-size: 1rem;
font-weight: bold;
font-size: 1rem;
}
.bg-blink {
color: #050000;
padding: 10px;
display: inline-block;
border-radius: 5px;
animation: blinkingBackground 2.2s infinite;
animation-iteration-count: 2;
color: #050000;
padding: 10px;
display: inline-block;
border-radius: 5px;
animation: blinkingBackground 2.2s infinite;
animation-iteration-count: 2;
}
@keyframes blinkingBackground {
0% {
background-color: #ed776d;
}
50% {
background-color: #ffffff;
}
100% {
background-color: #ed776d;
}
0% {
background-color: #ed776d;
}
50% {
background-color: #ffffff;
}
100% {
background-color: #ed776d;
}
}
</style>

View File

@@ -1,27 +1,27 @@
<template>
<div class="row mb-3">
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
{{ trans(EVALUATION_TIME_SPENT) }}
</label>
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
<select
class="form-control form-control-sm"
:value="timeSpent"
@input="$emit('update:timeSpent', $event.target.value)"
>
<option disabled value="">
{{ trans(EVALUATION_TIME_SPENT) }}
</option>
<option
v-for="time in timeSpentChoices"
:value="time.value"
:key="time.value"
>
{{ time.text }}
</option>
</select>
</div>
<div class="row mb-3">
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
{{ trans(EVALUATION_TIME_SPENT) }}
</label>
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
<select
class="form-control form-control-sm"
:value="timeSpent"
@input="$emit('update:timeSpent', $event.target.value)"
>
<option disabled value="">
{{ trans(EVALUATION_TIME_SPENT) }}
</option>
<option
v-for="time in timeSpentChoices"
:value="time.value"
:key="time.value"
>
{{ time.text }}
</option>
</select>
</div>
</div>
</template>
<script setup>

View File

@@ -1,8 +1,9 @@
import { fetchResults, makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import {Center, Civility, Gender, SetCenter} from "ChillMainAssets/types";
import { Center, Civility, Gender, SetCenter } from "ChillMainAssets/types";
import {
AltName,
Person, PersonIdentifier,
Person,
PersonIdentifier,
PersonIdentifierWorker,
PersonWrite,
} from "ChillPersonAssets/types";
@@ -26,21 +27,43 @@ export const personToWritePerson = (person: Person): PersonWrite => {
type: "person",
firstName: person.firstName,
lastName: person.lastName,
altNames: person.altNames.map((altName) => ({key: altName.key, value: altName.label})),
altNames: person.altNames.map((altName) => ({
key: altName.key,
value: altName.label,
})),
addressId: null,
birthdate: null === person.birthdate ? null : {datetime: person.birthdate.datetime8601},
deathdate: null === person.deathdate ? null : {datetime: person.deathdate.datetime8601},
birthdate:
null === person.birthdate
? null
: { datetime: person.birthdate.datetime8601 },
deathdate:
null === person.deathdate
? null
: { datetime: person.deathdate.datetime8601 },
phonenumber: person.phonenumber,
mobilenumber: person.mobilenumber,
center: null === person.centers ? null : person.centers
.map((center): SetCenter => ({id: center.id, type: "center"}))
.find(() => true) || null,
center:
null === person.centers
? null
: person.centers
.map((center): SetCenter => ({ id: center.id, type: "center" }))
.find(() => true) || null,
email: person.email,
civility: null === person.civility ? null : {id: person.civility.id, type: "chill_main_civility"},
gender: null === person.gender ? null : {id: person.gender.id, type: "chill_main_gender"},
identifiers: person.identifiers.map((identifier: PersonIdentifier) => ({type: "person_identifier", definition_id: identifier.definition.id, value: identifier.value})),
}
}
civility:
null === person.civility
? null
: { id: person.civility.id, type: "chill_main_civility" },
gender:
null === person.gender
? null
: { id: person.gender.id, type: "chill_main_gender" },
identifiers: person.identifiers.map((identifier: PersonIdentifier) => ({
type: "person_identifier",
definition_id: identifier.definition.id,
value: identifier.value,
})),
};
};
export const getPersonAltNames = async (): Promise<AltName[]> =>
fetch("/api/1.0/person/config/alt_names.json").then((response) => {
@@ -65,10 +88,12 @@ export const getPersonIdentifiers = async (): Promise<
PersonIdentifierWorker[]
> => fetchResults("/api/1.0/person/identifiers/workers");
export interface WritePersonViolationMap
extends Record<string, Record<string, string>> {
export interface WritePersonViolationMap extends Record<
string,
Record<string, string>
> {
firstName: {
"{{ value }}": string
"{{ value }}": string;
};
lastName: {
"{{ value }}": string;
@@ -96,7 +121,7 @@ export interface WritePersonViolationMap
birthdate: {};
identifiers: {
"{{ value }}": string;
"definition_id": string;
definition_id: string;
};
}
export const createPerson = async (person: PersonWrite): Promise<Person> => {
@@ -107,10 +132,13 @@ export const createPerson = async (person: PersonWrite): Promise<Person> => {
);
};
export const editPerson = async (person: PersonWrite, personId: number): Promise<Person> => {
export const editPerson = async (
person: PersonWrite,
personId: number,
): Promise<Person> => {
return makeFetch<PersonWrite, Person, WritePersonViolationMap>(
"PATCH",
`/api/1.0/person/person/${personId}.json`,
person,
);
}
};

View File

@@ -11,21 +11,21 @@ export const duplicate = async (
};
export const duplicateDocumentToEvaluation = async (
document_id: number,
evaluation_id: number,
document_id: number,
evaluation_id: number,
): Promise<AccompanyingPeriodWorkEvaluationDocument> => {
return makeFetch<null, AccompanyingPeriodWorkEvaluationDocument>(
"POST",
`/api/1.0/person/accompanying-course-work-evaluation-document/${document_id}/evaluation/${evaluation_id}/duplicate`,
);
return makeFetch<null, AccompanyingPeriodWorkEvaluationDocument>(
"POST",
`/api/1.0/person/accompanying-course-work-evaluation-document/${document_id}/evaluation/${evaluation_id}/duplicate`,
);
};
export const moveDocumentToEvaluation = async (
document_id: number,
evaluation_id: number,
document_id: number,
evaluation_id: number,
): Promise<AccompanyingPeriodWorkEvaluationDocument> => {
return makeFetch<null, AccompanyingPeriodWorkEvaluationDocument>(
"POST",
`/api/1.0/person/accompanying-course-work-evaluation-document/${document_id}/evaluation/${evaluation_id}/move`,
);
return makeFetch<null, AccompanyingPeriodWorkEvaluationDocument>(
"POST",
`/api/1.0/person/accompanying-course-work-evaluation-document/${document_id}/evaluation/${evaluation_id}/move`,
);
};

View File

@@ -1,53 +1,47 @@
<template>
<div class="container">
<div class="item-bloc">
<div class="item-row">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
<span>
{{ trans(EVALUATION) }}:
<span class="badge bg-light text-dark">
{{ eval?.evaluation?.title.fr }}
</span>
</span>
<div class="container">
<div class="item-bloc">
<div class="item-row">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
<span>
{{ trans(EVALUATION) }}:
<span class="badge bg-light text-dark">
{{ eval?.evaluation?.title.fr }}
</span>
</span>
<ul class="small_in_title columns mt-1">
<li>
<span class="item-key">
{{
trans(
ACCOMPANYING_COURSE_WORK_START_DATE,
)
}}
:
</span>
<b>{{ formatDate(eval.startDate) }}</b>
</li>
<ul class="small_in_title columns mt-1">
<li>
<span class="item-key">
{{ trans(ACCOMPANYING_COURSE_WORK_START_DATE) }}
:
</span>
<b>{{ formatDate(eval.startDate) }}</b>
</li>
<li v-if="eval.endDate">
<span class="item-key">
{{
trans(ACCOMPANYING_COURSE_WORK_END_DATE)
}}
:
</span>
<b>{{ formatDate(eval.endDate) }}</b>
</li>
</ul>
</span>
</h2>
</div>
</div>
<li v-if="eval.endDate">
<span class="item-key">
{{ trans(ACCOMPANYING_COURSE_WORK_END_DATE) }}
:
</span>
<b>{{ formatDate(eval.endDate) }}</b>
</li>
</ul>
</span>
</h2>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {
ACCOMPANYING_COURSE_WORK_END_DATE,
ACCOMPANYING_COURSE_WORK_START_DATE,
EVALUATION,
trans,
ACCOMPANYING_COURSE_WORK_END_DATE,
ACCOMPANYING_COURSE_WORK_START_DATE,
EVALUATION,
trans,
} from "translator";
import { ISOToDate } from "ChillMainAssets/chill/js/date";
import { DateTime } from "ChillMainAssets/types";
@@ -56,15 +50,15 @@ import { AccompanyingPeriodWorkEvaluation } from "../../../types";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const props = defineProps<{ eval: AccompanyingPeriodWorkEvaluation }>();
const formatDate = (dateObject: DateTime) => {
if (dateObject) {
const parsedDate = ISOToDate(dateObject.datetime);
if (parsedDate) {
return new Intl.DateTimeFormat("default", {
dateStyle: "short",
}).format(parsedDate);
} else {
return "";
}
if (dateObject) {
const parsedDate = ISOToDate(dateObject.datetime);
if (parsedDate) {
return new Intl.DateTimeFormat("default", {
dateStyle: "short",
}).format(parsedDate);
} else {
return "";
}
}
};
</script>

View File

@@ -1,24 +1,24 @@
<template>
<div class="results">
<div
v-for="evaluation in evaluations"
:key="evaluation.id"
class="list-item"
>
<label class="acpw-item">
<div>
<input
type="radio"
:value="evaluation"
v-model="selectedEvaluation"
name="item"
/>
</div>
<accompanying-period-work-evaluation-item :eval="evaluation" />
</label>
<div class="results">
<div
v-for="evaluation in evaluations"
:key="evaluation.id"
class="list-item"
>
<label class="acpw-item">
<div>
<input
type="radio"
:value="evaluation"
v-model="selectedEvaluation"
name="item"
/>
</div>
<accompanying-period-work-evaluation-item :eval="evaluation" />
</label>
</div>
</div>
</template>
<script setup lang="ts">
@@ -28,7 +28,7 @@ import AccompanyingPeriodWorkEvaluationItem from "ChillPersonAssets/vuejs/_compo
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const props = defineProps<{
evaluations: AccompanyingPeriodWorkEvaluation[];
evaluations: AccompanyingPeriodWorkEvaluation[];
}>();
const selectedEvaluation = ref<AccompanyingPeriodWorkEvaluation | null>(null);
@@ -36,12 +36,12 @@ const selectedEvaluation = ref<AccompanyingPeriodWorkEvaluation | null>(null);
const emit = defineEmits();
watch(selectedEvaluation, (newValue) => {
emit("update:selectedEvaluation", newValue);
emit("update:selectedEvaluation", newValue);
});
</script>
<style>
.acpw-item {
display: flex;
display: flex;
}
</style>

View File

@@ -27,22 +27,22 @@ import { AccompanyingPeriodWork } from "../../../types";
import { defineProps, ref, watch } from "vue";
const props = defineProps<{
accompanyingPeriodWorks: AccompanyingPeriodWork[];
selectedAcpw?: AccompanyingPeriodWork | null;
accompanyingPeriodWorks: AccompanyingPeriodWork[];
selectedAcpw?: AccompanyingPeriodWork | null;
}>();
const selectedAcpw = ref<AccompanyingPeriodWork | null>(
props.selectedAcpw ?? null,
props.selectedAcpw ?? null,
);
const emit = defineEmits<{
"update:selectedAcpw": [value: AccompanyingPeriodWork | null];
"update:selectedAcpw": [value: AccompanyingPeriodWork | null];
}>();
watch(
() => props.selectedAcpw,
(val) => {
selectedAcpw.value = val ?? null;
},
() => props.selectedAcpw,
(val) => {
selectedAcpw.value = val ?? null;
},
);
watch(selectedAcpw, (newValue) => {

View File

@@ -20,26 +20,28 @@
</li>
</ul>
<teleport to="body">
<modal
v-if="showModal"
@close="closeModal"
modal-dialog-class="modal-dialog-scrollable modal-xl"
>
<template #header>
<h3>
{{ getModalTitle() }}
</h3>
</template>
<teleport to="body">
<modal
v-if="showModal"
@close="closeModal"
modal-dialog-class="modal-dialog-scrollable modal-xl"
>
<template #header>
<h3>
{{ getModalTitle() }}
</h3>
</template>
<template #body>
<accompanying-period-work-list
v-if="evaluations.length === 0":accompanying-period-works="accompanyingPeriodWorks"
v-model:selectedAcpw="selectedAcpw"/>
<accompanying-period-work-evaluation-list
v-if="evaluations.length > 0"
:evaluations="evaluations"
v-model:selectedEvaluation="selectedEvaluation"
v-if="evaluations.length === 0"
:accompanying-period-works="accompanyingPeriodWorks"
v-model:selectedAcpw="selectedAcpw"
/>
<accompanying-period-work-evaluation-list
v-if="evaluations.length > 0"
:evaluations="evaluations"
v-model:selectedEvaluation="selectedEvaluation"
/>
</template>
@@ -59,13 +61,14 @@ import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import AccompanyingPeriodWorkList from "./AccompanyingPeriodWorkList.vue";
import { AccompanyingPeriodWork } from "../../../types";
import {
trans,ACPW_DUPLICATE_SELECT_ACCOMPANYING_PERIOD_WORK,
ACPW_DUPLICATE_SELECT_AN_EVALUATION,CONFIRM,
trans,
ACPW_DUPLICATE_SELECT_ACCOMPANYING_PERIOD_WORK,
ACPW_DUPLICATE_SELECT_AN_EVALUATION,
CONFIRM,
} from "translator";
import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
import AccompanyingPeriodWorkEvaluationList from "ChillPersonAssets/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkEvaluationList.vue";
import { AccompanyingPeriodWorkEvaluation } from "../../../types";
import { AccompanyingPeriodWorkEvaluation } from "../../../types";
const selectedAcpw = ref<AccompanyingPeriodWork | null>(null);
const selectedEvaluation = ref<AccompanyingPeriodWorkEvaluation | null>(null);
@@ -74,21 +77,21 @@ const accompanyingPeriodWorks = ref<AccompanyingPeriodWork[]>([]);
const evaluations = ref<AccompanyingPeriodWorkEvaluation[]>([]);
const props = defineProps<{
accompanyingPeriodId: string;
isEvaluationSelector: boolean;
ignoreAccompanyingPeriodWorkIds: number[];
accompanyingPeriodId: string;
isEvaluationSelector: boolean;
ignoreAccompanyingPeriodWorkIds: number[];
}>();
const emit = defineEmits<{
pickWork: [payload: { work: AccompanyingPeriodWork | null }];
closeModal: [];
"update:selectedEvaluation": [evaluation: AccompanyingPeriodWorkEvaluation];
closeModal: [];
"update:selectedEvaluation": [evaluation: AccompanyingPeriodWorkEvaluation];
}>();
const getModalTitle = () =>
evaluations.value.length > 0
? trans(ACPW_DUPLICATE_SELECT_AN_EVALUATION)
: trans(ACPW_DUPLICATE_SELECT_ACCOMPANYING_PERIOD_WORK);
evaluations.value.length > 0
? trans(ACPW_DUPLICATE_SELECT_AN_EVALUATION)
: trans(ACPW_DUPLICATE_SELECT_ACCOMPANYING_PERIOD_WORK);
onMounted(() => {
if (props.accompanyingPeriodId) {
@@ -97,70 +100,70 @@ onMounted(() => {
console.error("No accompanyingperiod id was given");
}
showModal.value = true;
showModal.value = true;
});
const getAccompanyingPeriodWorks = async (periodId: number) => {
const url = `/api/1.0/person/accompanying-course/${periodId}/works.json`;
const accompanyingPeriodWorksFetched =
await fetchResults<AccompanyingPeriodWork>(url);
await fetchResults<AccompanyingPeriodWork>(url);
if (props.isEvaluationSelector) {
accompanyingPeriodWorks.value = accompanyingPeriodWorksFetched.filter(
(acpw: AccompanyingPeriodWork) =>
acpw.accompanyingPeriodWorkEvaluations.length > 0 &&
typeof acpw.id !== "undefined" &&
!props.ignoreAccompanyingPeriodWorkIds.includes(acpw.id),
);
} else {
accompanyingPeriodWorks.value = accompanyingPeriodWorksFetched;
}
(acpw: AccompanyingPeriodWork) =>
acpw.accompanyingPeriodWorkEvaluations.length > 0 &&
typeof acpw.id !== "undefined" &&
!props.ignoreAccompanyingPeriodWorkIds.includes(acpw.id),
);
} else {
accompanyingPeriodWorks.value = accompanyingPeriodWorksFetched;
}
};
watch(selectedAcpw, (newValue) => {
const inputField = document.getElementById(
"find_accompanying_period_work_acpw",
) as HTMLInputElement;
if (inputField) {
inputField.value = String(newValue?.id || "");
}
const inputField = document.getElementById(
"find_accompanying_period_work_acpw",
) as HTMLInputElement;
if (inputField) {
inputField.value = String(newValue?.id || "");
}
/* if (!props.isEvaluationSelector) {
/* if (!props.isEvaluationSelector) {
console.log("Emitting from watch:", { work: newValue });
emit("pickWork", { work: newValue });
}*/
});
const openModal = () => {
showModal.value = true;
showModal.value = true;
};
const closeModal = () => {
showModal.value = false;
selectedEvaluation.value = null;
// selectedAcpw.value = null;
emit("closeModal");
showModal.value = false;
selectedEvaluation.value = null;
// selectedAcpw.value = null;
emit("closeModal");
};
const confirmSelection = () => {
selectedAcpw.value = selectedAcpw.value;
console.log("selectedAcpw", selectedAcpw.value);
console.log("selectedAcpw", selectedAcpw.value);
if (!props.isEvaluationSelector) {
if (selectedAcpw.value) {
// only emit if something is actually selected!emit("pickWork", { work: selectedAcpw.value });
closeModal();}
// optionally show some error or warning if not selected
return;
if (!props.isEvaluationSelector) {
if (selectedAcpw.value) {
// only emit if something is actually selected!emit("pickWork", { work: selectedAcpw.value });
closeModal();
}
// optionally show some error or warning if not selected
return;
}
if (selectedAcpw.value && props.isEvaluationSelector) {
evaluations.value =
selectedAcpw.value.accompanyingPeriodWorkEvaluations;
}
if (selectedAcpw.value && props.isEvaluationSelector) {
evaluations.value = selectedAcpw.value.accompanyingPeriodWorkEvaluations;
}
if (selectedEvaluation.value && props.isEvaluationSelector) {
// console.log('evaluation log in modal', selectedEvaluation.value)
emit("update:selectedEvaluation", selectedEvaluation.value);
closeModal();
}
if (selectedEvaluation.value && props.isEvaluationSelector) {
// console.log('evaluation log in modal', selectedEvaluation.value)
emit("update:selectedEvaluation", selectedEvaluation.value);
closeModal();
}
};
</script>

View File

@@ -25,7 +25,11 @@
/>
<CreateModal
v-if="creatableEntityTypes.length > 0 && showModalCreate && null == thirdPartyParentAddContact"
v-if="
creatableEntityTypes.length > 0 &&
showModalCreate &&
null == thirdPartyParentAddContact
"
action="create"
:allowed-types="creatableEntityTypes"
:query="query"
@@ -50,7 +54,7 @@
</template>
<script setup lang="ts">
import {ref, computed, nextTick, useTemplateRef} from "vue";
import { ref, computed, nextTick, useTemplateRef } from "vue";
import PersonChooseModal from "./AddPersons/PersonChooseModal.vue";
import type {
Suggestion,
@@ -62,7 +66,10 @@ import type {
import { marked } from "marked";
import options = marked.options;
import CreateModal from "ChillMainAssets/vuejs/OnTheFly/components/CreateModal.vue";
import {Thirdparty, ThirdpartyCompany} from "../../../../../ChillThirdPartyBundle/Resources/public/types";
import {
Thirdparty,
ThirdpartyCompany,
} from "../../../../../ChillThirdPartyBundle/Resources/public/types";
interface AddPersonsConfig {
suggested?: Suggestion[];
@@ -80,13 +87,13 @@ const props = withDefaults(defineProps<AddPersonsConfig>(), {
});
const emit =
defineEmits<{
(e: "addNewPersons", payload: { selected: Suggestion[] }): void;
}
defineEmits<
(e: "addNewPersons", payload: { selected: Suggestion[] }) => void
>();
type PersonChooseModalType = InstanceType<typeof PersonChooseModal>;
const personChooseModal = useTemplateRef<PersonChooseModalType>('personChooseModal');
const personChooseModal =
useTemplateRef<PersonChooseModalType>("personChooseModal");
/**
* Flag to show/hide the modal "choose".
@@ -106,7 +113,7 @@ const query = ref("");
/**
* Temporarily store the thirdparty company when calling "addContact"
*/
const thirdPartyParentAddContact = ref<ThirdpartyCompany|null>(null);
const thirdPartyParentAddContact = ref<ThirdpartyCompany | null>(null);
/**
* Contains the selected elements.
@@ -156,7 +163,7 @@ function closeModalChoose(): void {
}
function closeModalCreate(): void {
if (null !== thirdPartyParentAddContact) {
if (null !== thirdPartyParentAddContact.value) {
thirdPartyParentAddContact.value = null;
}
showModalCreate.value = false;
@@ -165,7 +172,10 @@ function closeModalCreate(): void {
/**
* Called by PersonSuggestion's updateSelection event, when an element is checked/unchecked
*/
function updateSelected(payload: {suggestion: Suggestion, isSelected: boolean}): void {
function updateSelected(payload: {
suggestion: Suggestion;
isSelected: boolean;
}): void {
if (payload.isSelected) {
addSuggestionToSelected(payload.suggestion);
} else {
@@ -194,7 +204,7 @@ function onPickEntities(): void {
closeModalChoose();
}
function triggerAddContact({parent}: {parent: ThirdpartyCompany}): void {
function triggerAddContact({ parent }: { parent: ThirdpartyCompany }): void {
closeModalChoose();
openModalChoose();
thirdPartyParentAddContact.value = parent;
@@ -216,7 +226,7 @@ function onPersonCreated(payload: { person: Person }): void {
}
}
function onThirdPartyCreated(payload: {thirdParty: Thirdparty}): void {
function onThirdPartyCreated(payload: { thirdParty: Thirdparty }): void {
showModalCreate.value = false;
const suggestion = {
result: payload.thirdParty,
@@ -236,7 +246,7 @@ function resetSearch(): void {
personChooseModal.value?.resetSearch();
}
defineExpose({resetSearch})
defineExpose({ resetSearch });
</script>
<style lang="scss" scoped>

View File

@@ -82,7 +82,11 @@
v-if="props.allowCreate && query.length > 0"
class="create-button"
>
<button type="button" class="btn btn-submit" @click="emit('onAskForCreate', { query })">
<button
type="button"
class="btn btn-submit"
@click="emit('onAskForCreate', { query })"
>
{{ trans(ONTHEFLY_CREATE_BUTTON, { q: query }) }}
</button>
</div>
@@ -125,7 +129,7 @@ import type {
SearchOptions,
Entities,
} from "ChillPersonAssets/types";
import {ThirdpartyCompany} from "../../../../../../ChillThirdPartyBundle/Resources/public/types";
import { ThirdpartyCompany } from "../../../../../../ChillThirdPartyBundle/Resources/public/types";
interface Props {
modalTitle: string;
@@ -147,7 +151,10 @@ const emit = defineEmits<{
(e: "onPickEntities"): void;
(e: "onAskForCreate", payload: { query: string }): void;
(e: "triggerAddContact", payload: { parent: ThirdpartyCompany }): void;
(e: "updateSelected", payload: {suggestion: Suggestion, isSelected: boolean}): void;
(
e: "updateSelected",
payload: { suggestion: Suggestion; isSelected: boolean },
): void;
(e: "cleanSelected"): void;
}>();
@@ -181,27 +188,32 @@ const queryLength = computed(() => search.query.length);
const suggestedCounter = computed(() => suggested.value.length);
const selectedCounter = computed(() => props.selected.size);
const checkUniq = computed(() =>
props.options.uniq ? "radio" : "checkbox",
const checkUniq = computed(() => (props.options.uniq ? "radio" : "checkbox"));
const selectedAndSuggested = computed<(Suggestion & { isSelected: boolean })[]>(
() => {
const selectedAndSuggested = [];
// add selected that are not in the search results
for (const selected of props.selected.values()) {
if (!suggested.value.some((s: Suggestion) => s.key === selected.key)) {
selectedAndSuggested.push({ ...selected, isSelected: false });
}
}
for (const suggestion of suggested.value) {
selectedAndSuggested.push({
...suggestion,
isSelected: props.selected.has(suggestion.key),
});
}
return selectedAndSuggested;
},
);
const selectedAndSuggested = computed<(Suggestion & {isSelected: boolean})[]>(() => {
const selectedAndSuggested = [];
// add selected that are not in the search results
for (const selected of props.selected.values()) {
if (!suggested.value.some((s: Suggestion) => s.key === selected.key)) {
selectedAndSuggested.push({...selected, isSelected: false});
}
}
for (const suggestion of suggested.value) {
selectedAndSuggested.push({...suggestion, isSelected: props.selected.has(suggestion.key)})
}
return selectedAndSuggested;
});
const hasNoResult = computed(() => search.hasPreviousQuery && suggested.value.length === 0);
const hasNoResult = computed(
() => search.hasPreviousQuery && suggested.value.length === 0,
);
function setQuery(q: string) {
search.query = q;
@@ -240,14 +252,15 @@ function setQuery(q: string) {
}, delay);
}
function loadSuggestions(suggestedArr: {relevance: number, result: Entities}[]): void {
function loadSuggestions(
suggestedArr: { relevance: number; result: Entities }[],
): void {
suggested.value = suggestedArr.map((item) => {
return {
key: item.result.type + item.result.id,
relevance: item.relevance,
result: item.result
}
result: item.result,
};
});
}
@@ -267,11 +280,11 @@ function resetSearch() {
function selectAll() {
suggested.value.forEach((suggestion: Suggestion) => {
emit("updateSelected", {suggestion, isSelected: true})
emit("updateSelected", { suggestion, isSelected: true });
});
}
function triggerAddContact(payload: {parent: ThirdpartyCompany}) {
function triggerAddContact(payload: { parent: ThirdpartyCompany }) {
emit("triggerAddContact", payload);
}
@@ -279,7 +292,7 @@ function triggerAddContact(payload: {parent: ThirdpartyCompany}) {
* Triggered when the user clicks on the "add" button.
*/
function pickEntities(): void {
emit("onPickEntities", );
emit("onPickEntities");
search.query = "";
emit("close");
}

View File

@@ -1,14 +1,14 @@
<template>
<div class="list-item" :class="{ checked: props.isSelected }">
<label>
<input
:type="type"
:value="props.item.key"
name="item"
:id="props.item.key"
:checked="props.isSelected"
@click="onUpdateValue"
/>
<input
:type="type"
:value="props.item.key"
name="item"
:id="props.item.key"
:checked="props.isSelected"
@click="onUpdateValue"
/>
<suggestion-person
v-if="isSuggestionForPerson(item)"
@@ -53,23 +53,27 @@ import SuggestionUserGroup from "./TypeUserGroup.vue";
import {
isSuggestionForHousehold,
isSuggestionForPerson,
isSuggestionForThirdParty, isSuggestionForUser,
isSuggestionForThirdParty,
isSuggestionForUser,
isSuggestionForUserGroup,
Suggestion
Suggestion,
} from "ChillPersonAssets/types";
import {ThirdpartyCompany} from "../../../../../../ChillThirdPartyBundle/Resources/public/types";
import { ThirdpartyCompany } from "../../../../../../ChillThirdPartyBundle/Resources/public/types";
const props = defineProps<{
item: Suggestion;
isSelected: boolean;
type: "radio"|"checkbox";
type: "radio" | "checkbox";
}>();
const emit = defineEmits<{
(e: "updateSelected", payload: {suggestion: Suggestion, isSelected: boolean}): void;
(
e: "updateSelected",
payload: { suggestion: Suggestion; isSelected: boolean },
): void;
(e: "triggerAddContact", payload: { parent: ThirdpartyCompany }): void;
}>();
const isChecked = computed<boolean>(() => props.isSelected)
const isChecked = computed<boolean>(() => props.isSelected);
const onUpdateValue = (event: Event) => {
const target = event?.target;
@@ -77,10 +81,13 @@ const onUpdateValue = (event: Event) => {
console.error("the value of checked is not an HTMLInputElement");
return;
}
emit("updateSelected", {suggestion: props.item, isSelected: props.type === "radio" ? true : target.checked});
}
emit("updateSelected", {
suggestion: props.item,
isSelected: props.type === "radio" ? true : target.checked,
});
};
function triggerAddContact(payload: {parent: ThirdpartyCompany}) {
function triggerAddContact(payload: { parent: ThirdpartyCompany }) {
emit("triggerAddContact", payload);
}
</script>

View File

@@ -16,10 +16,10 @@ import { defineProps } from "vue";
import BadgeEntity from "ChillMainAssets/vuejs/_components/BadgeEntity.vue";
import HouseholdRenderBox from "ChillPersonAssets/vuejs/_components/Entity/HouseholdRenderBox.vue";
import { Suggestion } from "ChillPersonAssets/types";
import {Household} from "ChillMainAssets/types";
import { Household } from "ChillMainAssets/types";
interface TypeHouseholdProps {
item: Suggestion & {result: Household};
item: Suggestion & { result: Household };
}
defineProps<TypeHouseholdProps>();

View File

@@ -23,7 +23,7 @@ import { computed, defineProps } from "vue";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
import BadgeEntity from "ChillMainAssets/vuejs/_components/BadgeEntity.vue";
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
import {Person, Suggestion} from "ChillPersonAssets/types";
import { Person, Suggestion } from "ChillPersonAssets/types";
function formatDate(dateString: string | undefined, format: string) {
if (!dateString) return "";
@@ -36,7 +36,7 @@ function formatDate(dateString: string | undefined, format: string) {
}
const props = defineProps<{
item: Suggestion & { result: Person },
item: Suggestion & { result: Person };
}>();
const hasBirthdate = computed(() => props.item.result.birthdate !== null);

View File

@@ -1,9 +1,15 @@
<template>
<div class="container tpartycontainer">
<div class="tparty-identification">
<span v-if="(isThirdpartyChild(item.result) || isThirdpartyContact(item.result)) && item.result.profession" class="profession">{{
item.result.profession
}}</span>
<span
v-if="
(isThirdpartyChild(item.result) ||
isThirdpartyContact(item.result)) &&
item.result.profession
"
class="profession"
>{{ item.result.profession }}</span
>
<span class="name"> {{ item.result.text }}&nbsp; </span>
<span class="location">
<template v-if="hasAddress">
@@ -12,7 +18,10 @@
</template>
</span>
</div>
<div class="tpartyparent" v-if="isThirdpartyChild(item.result) && null !== item.result.parent">
<div
class="tpartyparent"
v-if="isThirdpartyChild(item.result) && null !== item.result.parent"
>
<span class="name"> &gt; {{ item.result.parent.text }} </span>
</div>
</div>
@@ -22,8 +31,9 @@
<a
v-if="item.result.type === 'thirdparty' && item.result.kind === 'company'"
class="btn btn-tpchild"
@click="emit('triggerAddContact', {parent: item.result})"
><i class="bi bi-person-fill-add"></i></a>
@click="emit('triggerAddContact', { parent: item.result })"
><i class="bi bi-person-fill-add"></i
></a>
<on-the-fly type="thirdparty" :id="item.result.id" action="show" />
</div>
</template>
@@ -38,16 +48,20 @@ import { Result, Suggestion } from "ChillPersonAssets/types";
import {
isThirdpartyChild,
isThirdpartyContact,
Thirdparty, ThirdpartyCompany
Thirdparty,
ThirdpartyCompany,
} from "./../../../../../../ChillThirdPartyBundle/Resources/public/types";
interface TypeThirdPartyProps {
item: Suggestion & {result: Thirdparty};
item: Suggestion & { result: Thirdparty };
}
const props = defineProps<TypeThirdPartyProps>();
const emit = defineEmits<(e: "triggerAddContact", payload: {parent: ThirdpartyCompany}) => void>();
const emit =
defineEmits<
(e: "triggerAddContact", payload: { parent: ThirdpartyCompany }) => void
>();
const onTheFly = ref<InstanceType<typeof OnTheFly> | null>(null);
const toast = useToast();
@@ -56,7 +70,10 @@ const hasAddress = computed(() => {
if (props.item.result.address !== null) {
return true;
}
if (isThirdpartyChild(props.item.result) && props.item.result.parent !== null) {
if (
isThirdpartyChild(props.item.result) &&
props.item.result.parent !== null
) {
return props.item.result.parent.address !== null;
}
@@ -67,7 +84,11 @@ const getAddress = computed(() => {
if (props.item.result.address !== null) {
return props.item.result.address;
}
if (isThirdpartyChild(props.item.result) && props.item.result.parent !== null && props.item.result.parent.address !== null) {
if (
isThirdpartyChild(props.item.result) &&
props.item.result.parent !== null &&
props.item.result.parent.address !== null
) {
return props.item.result.parent.address;
}
return null;

View File

@@ -14,14 +14,13 @@ import { computed, defineProps } from "vue";
import BadgeEntity from "ChillMainAssets/vuejs/_components/BadgeEntity.vue";
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue";
import { Suggestion } from "ChillPersonAssets/types";
import {User} from "ChillMainAssets/types";
import { User } from "ChillMainAssets/types";
interface TypeUserProps {
item: Suggestion & {result: User};
item: Suggestion & { result: User };
}
const props = defineProps<TypeUserProps>();
</script>
<style lang="scss" scoped>

View File

@@ -15,7 +15,7 @@ import UserGroupRenderBox from "ChillMainAssets/vuejs/_components/Entity/UserGro
import { Suggestion } from "ChillPersonAssets/types";
interface TypeUserGroupProps {
item: Suggestion & {result: UserGroup};
item: Suggestion & { result: UserGroup };
}
const props = defineProps<TypeUserGroupProps>();

View File

@@ -23,35 +23,39 @@
</div>
<p>
<span
v-if="options.addId == true"
:title="person.personId"
<span v-if="options.addId == true" :title="person.personId"
><i class="bi bi-info-circle"></i> {{ person.personId }}</span
>
>
</p>
<p v-if="options.addInfo === true" class="moreinfo">
<gender-icon-render-box
v-if="person.gender"
:gender="person.gender"
/> <span
v-if="person.birthdate"
>
{{ trans(RENDERBOX_BIRTHDAY_STATEMENT, {gender: toGenderTranslation(person.gender), birthdate: ISOToDate(person.birthdate?.datetime)}) }}
/>
<span v-if="person.birthdate">
{{
trans(RENDERBOX_BIRTHDAY_STATEMENT, {
gender: toGenderTranslation(person.gender),
birthdate: ISOToDate(person.birthdate?.datetime),
})
}}
</span>
<span v-if="options.addAge && person.birthdate" class="age">
({{ trans(RENDERBOX_YEARS_OLD, {n: person.age}) }})
({{ trans(RENDERBOX_YEARS_OLD, { n: person.age }) }})
</span>
</p>
<p>
<span
v-if="person.deathdate"
>
{{ trans(RENDERBOX_DEATHDATE_STATEMENT, {gender: toGenderTranslation(person.gender), deathdate: ISOToDate(person.deathdate?.datetime)}) }}
<span v-if="person.deathdate">
{{
trans(RENDERBOX_DEATHDATE_STATEMENT, {
gender: toGenderTranslation(person.gender),
deathdate: ISOToDate(person.deathdate?.datetime),
})
}}
</span>
</p>
</div>
</div>
@@ -59,11 +63,11 @@
<div class="float-button bottom">
<div class="box">
<div class="action">
<slot name="record-actions"/>
<slot name="record-actions" />
</div>
<ul class="list-content fa-ul">
<li v-if="person.current_household_id">
<i class="fa fa-li fa-map-marker"/>
<i class="fa fa-li fa-map-marker" />
<address-render-box
v-if="person.current_household_address"
:address="person.current_household_address"
@@ -84,7 +88,7 @@
</a>
</li>
<li v-else-if="options.addNoData">
<i class="fa fa-li fa-map-marker"/>
<i class="fa fa-li fa-map-marker" />
<p class="chill-no-data-statement">
{{ trans(RENDERBOX_NO_DATA) }}
</p>
@@ -100,7 +104,7 @@
v-for="(addr, i) in person.current_residential_addresses"
:key="i"
>
<i class="fa fa-li fa-map-marker"/>
<i class="fa fa-li fa-map-marker" />
<div v-if="addr.address">
<span class="item-key">
{{ trans(RENDERBOX_RESIDENTIAL_ADDRESS) }}:
@@ -145,36 +149,36 @@
</template>
<li v-if="person.email">
<i class="fa fa-li fa-envelope-o"/>
<i class="fa fa-li fa-envelope-o" />
<a :href="'mailto: ' + person.email">{{ person.email }}</a>
</li>
<li v-else-if="options.addNoData">
<i class="fa fa-li fa-envelope-o"/>
<i class="fa fa-li fa-envelope-o" />
<p class="chill-no-data-statement">
{{ trans(RENDERBOX_NO_DATA) }}
</p>
</li>
<li v-if="person.mobilenumber">
<i class="fa fa-li fa-mobile"/>
<i class="fa fa-li fa-mobile" />
<a :href="'tel: ' + person.mobilenumber">
{{ person.mobilenumber }}
</a>
</li>
<li v-else-if="options.addNoData">
<i class="fa fa-li fa-mobile"/>
<i class="fa fa-li fa-mobile" />
<p class="chill-no-data-statement">
{{ trans(RENDERBOX_NO_DATA) }}
</p>
</li>
<li v-if="person.phonenumber">
<i class="fa fa-li fa-phone"/>
<i class="fa fa-li fa-phone" />
<a :href="'tel: ' + person.phonenumber">
{{ person.phonenumber }}
</a>
</li>
<li v-else-if="options.addNoData">
<i class="fa fa-li fa-phone"/>
<i class="fa fa-li fa-phone" />
<p class="chill-no-data-statement">
{{ trans(RENDERBOX_NO_DATA) }}
</p>
@@ -187,25 +191,25 @@
options.addCenter
"
>
<i class="fa fa-li fa-long-arrow-right"/>
<i class="fa fa-li fa-long-arrow-right" />
<template v-for="c in person.centers">
{{ c.name }}
</template>
</li>
<li v-else-if="options.addNoData">
<i class="fa fa-li fa-long-arrow-right"/>
<i class="fa fa-li fa-long-arrow-right" />
<p class="chill-no-data-statement">
{{ trans(RENDERBOX_NO_DATA) }}
</p>
</li>
<slot name="custom-zone"/>
<slot name="custom-zone" />
</ul>
</div>
</div>
</div>
</div>
<slot name="end-bloc"/>
<slot name="end-bloc" />
</section>
</div>
@@ -219,11 +223,11 @@
class="fa-stack fa-holder"
:title="trans(RENDERBOX_HOLDER)"
>
<i class="fa fa-circle fa-stack-1x text-success"/>
<i class="fa fa-circle fa-stack-1x text-success" />
<i class="fa fa-stack-1x">T</i>
</span>
<person-text :person="person"/>
<person-text :person="person" />
</a>
<span v-else>
<span
@@ -231,18 +235,18 @@
class="fa-stack fa-holder"
:title="trans(RENDERBOX_HOLDER)"
>
<i class="fa fa-circle fa-stack-1x text-success"/>
<i class="fa fa-circle fa-stack-1x text-success" />
<i class="fa fa-stack-1x">T</i>
</span>
<person-text :person="person"/>
<person-text :person="person" />
</span>
<slot name="post-badge"/>
<slot name="post-badge" />
</span>
</template>
<script setup lang="ts">
import {computed} from "vue";
import {ISOToDate} from "ChillMainAssets/chill/js/date";
import { computed } from "vue";
import { ISOToDate } from "ChillMainAssets/chill/js/date";
import AddressRenderBox from "ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue";
import GenderIconRenderBox from "ChillMainAssets/vuejs/_components/Entity/GenderIconRenderBox.vue";
import BadgeEntity from "ChillMainAssets/vuejs/_components/BadgeEntity.vue";
@@ -260,8 +264,8 @@ import {
// PERSONS_ASSOCIATED_SHOW_HOUSEHOLD_NUMBER,
RENDERBOX_YEARS_OLD,
} from "translator";
import {Person} from "ChillPersonAssets/types";
import {toGenderTranslation} from "ChillMainAssets/lib/api/genderHelper";
import { Person } from "ChillPersonAssets/types";
import { toGenderTranslation } from "ChillMainAssets/lib/api/genderHelper";
interface RenderOptions {
addInfo?: boolean;
@@ -302,7 +306,7 @@ const props = withDefaults(defineProps<Props>(), {
addNoData: true,
isMultiline: true,
isHolder: false,
addHouseholdLink: true
addHouseholdLink: true,
}),
});

View File

@@ -44,7 +44,7 @@ interface Props {
query?: string;
}
const props = withDefaults(defineProps<Props>(), {query: ""});
const props = withDefaults(defineProps<Props>(), { query: "" });
const person = ref<Person | null>(null);

View File

@@ -81,11 +81,12 @@
value=""
@input="onAltNameInput($event, a.key)"
/>
<label :for="'label_' + a.key">{{ localizeString(a.labels) }}</label>
<label :for="'label_' + a.key">{{
localizeString(a.labels)
}}</label>
</div>
</div>
</div>
</template>
<template v-if="action === 'create'">
@@ -98,18 +99,30 @@
<div class="form-floating">
<input
class="form-control form-control-lg"
:class="{'is-invalid': violations.hasViolationWithParameter('identifiers', 'definition_id', worker.definition_id.toString())}"
:class="{
'is-invalid': violations.hasViolationWithParameter(
'identifiers',
'definition_id',
worker.definition_id.toString(),
),
}"
type="text"
:name="'worker_' + worker.definition_id"
:placeholder="localizeString(worker.label)"
@input="onIdentifierInput($event, worker.definition_id)"
/>
<label :for="'worker_' + worker.definition_id" :class="{required: worker.presence == 'REQUIRED'}">{{
localizeString(worker.label)
}}</label>
<label
:for="'worker_' + worker.definition_id"
:class="{ required: worker.presence == 'REQUIRED' }"
>{{ localizeString(worker.label) }}</label
>
</div>
<div
v-for="err in violations.violationTitlesWithParameter('identifiers', 'definition_id', worker.definition_id.toString())"
v-for="err in violations.violationTitlesWithParameter(
'identifiers',
'definition_id',
worker.definition_id.toString(),
)"
class="invalid-feedback was-validated-force"
>
{{ err }}
@@ -163,7 +176,9 @@
{{ c.name }}
</option>
</select>
<label for="center" class="required">{{ trans(PERSON_MESSAGES_PERSON_CENTER_TITLE) }}</label>
<label for="center" class="required">{{
trans(PERSON_MESSAGES_PERSON_CENTER_TITLE)
}}</label>
</div>
<div
v-for="err in violations.violationTitles('center')"
@@ -190,7 +205,9 @@
{{ localizeString(c.name) }}
</option>
</select>
<label for="civility">{{ trans(PERSON_MESSAGES_PERSON_CIVILITY_TITLE) }}</label>
<label for="civility">{{
trans(PERSON_MESSAGES_PERSON_CIVILITY_TITLE)
}}</label>
</div>
<div
v-for="err in violations.violationTitles('civility')"
@@ -215,7 +232,7 @@
v-model="birthDate"
:placeholder="trans(BIRTHDATE)"
:aria-label="trans(BIRTHDATE)"
/>
/>
<label for="birthdate">{{ trans(BIRTHDATE) }}</label>
</div>
<div
@@ -241,7 +258,9 @@
:aria-label="trans(PERSON_MESSAGES_PERSON_PHONENUMBER)"
aria-describedby="phonenumber"
/>
<label for="phonenumber">{{ trans(PERSON_MESSAGES_PERSON_PHONENUMBER) }}</label>
<label for="phonenumber">{{
trans(PERSON_MESSAGES_PERSON_PHONENUMBER)
}}</label>
</div>
<div
v-for="err in violations.violationTitles('phonenumber')"
@@ -266,7 +285,9 @@
:aria-label="trans(PERSON_MESSAGES_PERSON_MOBILENUMBER)"
aria-describedby="mobilenumber"
/>
<label for="mobilenumber">{{ trans(PERSON_MESSAGES_PERSON_MOBILENUMBER) }}</label>
<label for="mobilenumber">{{
trans(PERSON_MESSAGES_PERSON_MOBILENUMBER)
}}</label>
</div>
<div
v-for="err in violations.violationTitles('mobilenumber')"
@@ -326,11 +347,8 @@
ref="addAddress"
/>
</div>
</div>
<div v-else>
</div>
<div v-else></div>
</template>
<script setup lang="ts">
@@ -338,7 +356,8 @@ import { ref, reactive, computed, onMounted } from "vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
import {
createPerson, editPerson,
createPerson,
editPerson,
getCentersForPersonCreation,
getCivilities,
getGenders,
@@ -366,23 +385,20 @@ import {
PERSON_MESSAGES_PERSON_ADDRESS_SHOW_ADDRESS_FORM,
PERSON_MESSAGES_PERSON_ADDRESS_WARNING,
} from "translator";
import {
Center,
Civility,
Gender,
} from "ChillMainAssets/types";
import { Center, Civility, Gender } from "ChillMainAssets/types";
import {
AltName,
Person,
PersonWrite,
PersonIdentifierWorker,
} from "ChillPersonAssets/types";
import { isValidationException } from "ChillMainAssets/lib/api/apiMethods";
import { useToast } from "vue-toast-notification";
import {
isValidationException,
} from "ChillMainAssets/lib/api/apiMethods";
import {useToast} from "vue-toast-notification";
import {getTimezoneOffsetString, ISOToDate} from "ChillMainAssets/chill/js/date";
import {useViolationList} from "ChillMainAssets/vuejs/_composables/violationList";
getTimezoneOffsetString,
ISOToDate,
} from "ChillMainAssets/chill/js/date";
import { useViolationList } from "ChillMainAssets/vuejs/_composables/violationList";
interface PersonEditComponentConfig {
id?: number | null;
@@ -486,13 +502,16 @@ const birthDate = computed({
person.birthdate = null;
return;
}
const offset = getTimezoneOffsetString(date, Intl.DateTimeFormat().resolvedOptions().timeZone);
const offset = getTimezoneOffsetString(
date,
Intl.DateTimeFormat().resolvedOptions().timeZone,
);
if (person.birthdate) {
person.birthdate.datetime = value + "T00:00:00" + offset;
} else {
person.birthdate = { datetime: value + "T00:00:00" + offset };
}
}
},
});
const phonenumber = computed({
get: () => person.phonenumber,
@@ -566,7 +585,7 @@ async function loadData() {
const w = personToWritePerson(p);
person.firstName = w.firstName;
person.lastName = w.lastName;
person.altNames.push(...w.altNames)
person.altNames.push(...w.altNames);
person.civility = w.civility;
person.addressId = w.addressId;
person.birthdate = w.birthdate;
@@ -633,7 +652,7 @@ function submitNewAddress(payload: { addressId: number }) {
async function postPerson(): Promise<Person> {
try {
if (props.action === 'create') {
if (props.action === "create") {
const createdPerson = await createPerson(person);
emit("onPersonCreated", { person: createdPerson });
@@ -667,8 +686,8 @@ onMounted(() => {
getPersonIdentifiers().then((identifiers) => {
config.identifiers = identifiers.filter(
(w: PersonIdentifierWorker) =>
w.presence === 'ON_CREATION' || w.presence === 'REQUIRED'
);
w.presence === "ON_CREATION" || w.presence === "REQUIRED",
);
});
if (props.action !== "create") {
loadData();

View File

@@ -1,64 +1,63 @@
const personMessages = {
fr: {
add_persons: {
title: "Ajouter des usagers",
suggested_counter:
"Pas de résultats | 1 résultat | {count} résultats",
selected_counter: " 1 sélectionné | {count} sélectionnés",
search_some_persons: "Rechercher des personnes..",
},
item: {
type_person: "Usager",
type_user: "TMS",
type_thirdparty: "Tiers professionnel",
type_household: "Ménage",
},
person: {
firstname: "Prénom",
lastname: "Nom",
born: (ctx: { gender: "man" | "woman" | "neutral" }) => {
if (ctx.gender === "man") {
return "Né le";
} else if (ctx.gender === "woman") {
return "Née le";
} else {
return "Né·e le";
}
},
center_id: "Identifiant du territoire",
center_type: "Type de territoire",
center_name: "Territoire", // vendée
phonenumber: "Téléphone",
mobilenumber: "Mobile",
altnames: "Autres noms",
email: "Courriel",
gender: {
title: "Genre",
placeholder: "Choisissez le genre de l'usager",
woman: "Féminin",
man: "Masculin",
neutral: "Neutre, non binaire",
unknown: "Non renseigné",
undefined: "Non renseigné",
},
civility: {
title: "Civilité",
placeholder: "Choisissez la civilité",
},
address: {
create_address: "Ajouter une adresse",
show_address_form:
"Ajouter une adresse pour un usager non suivi et seul dans un ménage",
warning:
"Un nouveau ménage va être créé. L'usager sera membre de ce ménage.",
},
center: {
placeholder: "Choisissez un territoire",
title: "territoire",
},
},
error_only_one_person: "Une seule personne peut être sélectionnée !",
fr: {
add_persons: {
title: "Ajouter des usagers",
suggested_counter: "Pas de résultats | 1 résultat | {count} résultats",
selected_counter: " 1 sélectionné | {count} sélectionnés",
search_some_persons: "Rechercher des personnes..",
},
item: {
type_person: "Usager",
type_user: "TMS",
type_thirdparty: "Tiers professionnel",
type_household: "Ménage",
},
person: {
firstname: "Prénom",
lastname: "Nom",
born: (ctx: { gender: "man" | "woman" | "neutral" }) => {
if (ctx.gender === "man") {
return "Né le";
} else if (ctx.gender === "woman") {
return "Née le";
} else {
return "Né·e le";
}
},
center_id: "Identifiant du territoire",
center_type: "Type de territoire",
center_name: "Territoire", // vendée
phonenumber: "Téléphone",
mobilenumber: "Mobile",
altnames: "Autres noms",
email: "Courriel",
gender: {
title: "Genre",
placeholder: "Choisissez le genre de l'usager",
woman: "Féminin",
man: "Masculin",
neutral: "Neutre, non binaire",
unknown: "Non renseigné",
undefined: "Non renseigné",
},
civility: {
title: "Civilité",
placeholder: "Choisissez la civilité",
},
address: {
create_address: "Ajouter une adresse",
show_address_form:
"Ajouter une adresse pour un usager non suivi et seul dans un ménage",
warning:
"Un nouveau ménage va être créé. L'usager sera membre de ce ménage.",
},
center: {
placeholder: "Choisissez un territoire",
title: "territoire",
},
},
error_only_one_person: "Une seule personne peut être sélectionnée !",
},
};
export { personMessages };

View File

@@ -2,7 +2,9 @@ import {
Address,
Center,
Civility,
DateTime, SetAddress, SetCivility,
DateTime,
SetAddress,
SetCivility,
User,
} from "ChillMainAssets/types";
@@ -10,7 +12,7 @@ export type ThirdPartyKind = "contact" | "child" | "company";
export interface BaseThirdParty {
type: "thirdparty";
kind: ""|ThirdPartyKind;
kind: "" | ThirdPartyKind;
text: string;
acronym: string | null;
active: boolean;
@@ -35,7 +37,10 @@ function isBaseThirdParty(t: unknown): t is BaseThirdParty {
(o as any).type === "thirdparty" &&
typeof o.id === "number" &&
typeof o.text === "string" &&
(o.kind === "" || o.kind === "contact" || o.kind === "child" || o.kind === "company") &&
(o.kind === "" ||
o.kind === "contact" ||
o.kind === "child" ||
o.kind === "company") &&
typeof o.active === "boolean"
);
}
@@ -51,13 +56,8 @@ export interface ThirdpartyCompany extends BaseThirdParty {
}
// Type guard to distinguish a ThirdpartyCompany
export function isThirdpartyCompany(
t: BaseThirdParty
): t is ThirdpartyCompany {
return (
t.type === "thirdparty" &&
t.kind === "company"
);
export function isThirdpartyCompany(t: BaseThirdParty): t is ThirdpartyCompany {
return t.type === "thirdparty" && t.kind === "company";
}
export interface ThirdpartyChild extends BaseThirdParty {
@@ -75,13 +75,8 @@ export interface ThirdpartyChild extends BaseThirdParty {
}
// Type guard to distinguish a ThirdpartyChild
export function isThirdpartyChild(
t: BaseThirdParty
): t is ThirdpartyChild {
return (
t.type === "thirdparty" &&
t.kind === "child"
);
export function isThirdpartyChild(t: BaseThirdParty): t is ThirdpartyChild {
return t.type === "thirdparty" && t.kind === "child";
}
export interface ThirdpartyContact extends BaseThirdParty {
@@ -99,24 +94,23 @@ export interface ThirdpartyContact extends BaseThirdParty {
}
// Type guard to distinguish a ThirdpartyContact
export function isThirdpartyContact(
t: BaseThirdParty
): t is ThirdpartyContact {
return (
t.type === "thirdparty" &&
t.kind === "contact"
);
export function isThirdpartyContact(t: BaseThirdParty): t is ThirdpartyContact {
return t.type === "thirdparty" && t.kind === "contact";
}
export type Thirdparty = ThirdpartyCompany | ThirdpartyContact | ThirdpartyChild;
export type Thirdparty =
| ThirdpartyCompany
| ThirdpartyContact
| ThirdpartyChild;
export function isThirdparty(t: unknown): t is Thirdparty {
if (!isBaseThirdParty(t)) {
return false;
}
return (isThirdpartyCompany(t) || isThirdpartyContact(t) || isThirdpartyChild(t));
return (
isThirdpartyCompany(t) || isThirdpartyContact(t) || isThirdpartyChild(t)
);
}
interface ThirdpartyType {
@@ -153,7 +147,7 @@ export interface ThirdPartyWrite {
email: string;
telephone: string;
telephone2: string;
address: null|SetAddress;
address: null | SetAddress;
comment: string;
parent: SetThirdParty|null;
parent: SetThirdParty | null;
}

View File

@@ -1,10 +1,16 @@
/*
* GET a thirdparty by id
*/
import {isThirdpartyChild, isThirdpartyCompany, isThirdpartyContact, Thirdparty, ThirdPartyWrite} from '../../types';
import {makeFetch} from "ChillMainAssets/lib/api/apiMethods";
import {
isThirdpartyChild,
isThirdpartyCompany,
isThirdpartyContact,
Thirdparty,
ThirdPartyWrite,
} from "../../types";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
export const getThirdparty = async (id: number) : Promise<Thirdparty> => {
export const getThirdparty = async (id: number): Promise<Thirdparty> => {
const url = `/api/1.0/thirdparty/thirdparty/${id}.json`;
return fetch(url).then((response) => {
if (response.ok) {
@@ -21,40 +27,41 @@ export const thirdpartyToWriteThirdParty = (t: Thirdparty): ThirdPartyWrite => {
const isChild = isThirdpartyChild(t);
return {
type: 'thirdparty',
type: "thirdparty",
kind: t.kind,
civility:
(isContact || isChild) && t.civility
? { type: 'chill_main_civility', id: t.civility.id }
? { type: "chill_main_civility", id: t.civility.id }
: null,
profession: (isContact || isChild) ? (t.profession ?? '') : '',
firstname: isCompany ? '' : (t.firstname ?? ''),
name: isCompany
? (t.nameCompany ?? '')
: (t.name ?? ''),
email: t.email ?? '',
telephone: t.telephone ?? '',
telephone2: t.telephone2 ?? '',
profession: isContact || isChild ? (t.profession ?? "") : "",
firstname: isCompany ? "" : (t.firstname ?? ""),
name: isCompany ? (t.nameCompany ?? "") : (t.name ?? ""),
email: t.email ?? "",
telephone: t.telephone ?? "",
telephone2: t.telephone2 ?? "",
address: null,
comment: isChild ? (t.comment ?? '') : '',
parent: isChild && t.parent ? { type: 'thirdparty', id: t.parent.id } : null,
comment: isChild ? (t.comment ?? "") : "",
parent:
isChild && t.parent ? { type: "thirdparty", id: t.parent.id } : null,
};
};
export interface WriteThirdPartyViolationMap
extends Record<string, Record<string, string>> {
export interface WriteThirdPartyViolationMap extends Record<
string,
Record<string, string>
> {
email: {
"{{ value }}": string;
},
};
name: {
"{{ value }}": string;
},
};
telephone: {
"{{ value }}": string;
}
};
telephone2: {
"{{ value }}": string;
}
};
}
/*
@@ -63,13 +70,16 @@ extends Record<string, Record<string, string>> {
export const createThirdParty = async (body: ThirdPartyWrite) => {
const url = `/api/1.0/thirdparty/thirdparty.json`;
return makeFetch<ThirdPartyWrite, Thirdparty>('POST', url, body);
return makeFetch<ThirdPartyWrite, Thirdparty>("POST", url, body);
};
/*
* PATCH an existing thirdparty
*/
export const patchThirdparty = async (id: number, body: ThirdPartyWrite): Promise<Thirdparty> => {
export const patchThirdparty = async (
id: number,
body: ThirdPartyWrite,
): Promise<Thirdparty> => {
const url = `/api/1.0/thirdparty/thirdparty/${id}.json`;
return makeFetch('PATCH', url, body);
return makeFetch("PATCH", url, body);
};

View File

@@ -46,7 +46,7 @@
</li>
<li v-if="isThirdpartyChild(props.thirdparty)">
<i class="fa fa-li fa-hand-o-right" />
<b class="me-2">{{ trans(THIRDPARTY_MESSAGES_CHILD_OF)}}</b>
<b class="me-2">{{ trans(THIRDPARTY_MESSAGES_CHILD_OF) }}</b>
<on-the-fly
:type="props.thirdparty.parent.type"
:id="props.thirdparty.parent.id"
@@ -133,13 +133,14 @@ import { computed, defineAsyncComponent } from "vue";
import AddressRenderBox from "ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue";
import Confidential from "ChillMainAssets/vuejs/_components/Confidential.vue";
import BadgeEntity from "ChillMainAssets/vuejs/_components/BadgeEntity.vue";
import {isThirdpartyChild, isThirdpartyCompany, Thirdparty} from "../../../types";
import {localizeString} from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
import {
THIRDPARTY_MESSAGES_CHILD_OF,
trans
} from "translator";
isThirdpartyChild,
isThirdpartyCompany,
Thirdparty,
} from "../../../types";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
import { THIRDPARTY_MESSAGES_CHILD_OF, trans } from "translator";
// Async to avoid recursive resolution issues
/*
@@ -160,7 +161,9 @@ interface RenderOptions {
const props = defineProps<{ thirdparty: Thirdparty; options: RenderOptions }>();
const isMultiline = computed<boolean>(() => props.options?.isMultiline ?? false);
const isMultiline = computed<boolean>(
() => props.options?.isMultiline ?? false,
);
const hasParent = computed<boolean>(() => {
return isThirdpartyChild(props.thirdparty);

View File

@@ -20,48 +20,53 @@
action === 'edit' || action === 'create' || action === 'addContact'
"
>
<ThirdPartyEdit :id="id" :type="type" :action="action" :query="query" :parent="parent" />
<ThirdPartyEdit
:id="id"
:type="type"
:action="action"
:query="query"
:parent="parent"
/>
</div>
</template>
<script setup lang="ts">
import {onMounted, ref} from 'vue'
import ThirdPartyRenderBox from '../Entity/ThirdPartyRenderBox.vue'
import ThirdPartyEdit from './ThirdPartyEdit.vue'
import { getThirdparty } from '../../_api/OnTheFly'
import {Thirdparty, ThirdpartyCompany} from '../../../types'
import { onMounted, ref } from "vue";
import ThirdPartyRenderBox from "../Entity/ThirdPartyRenderBox.vue";
import ThirdPartyEdit from "./ThirdPartyEdit.vue";
import { getThirdparty } from "../../_api/OnTheFly";
import { Thirdparty, ThirdpartyCompany } from "../../../types";
type ThirdPartyProp = {
id: number,
type: 'thirdparty',
action: 'show'|'edit'|'create',
parent?: null,
interface ThirdPartyProp {
id: number;
type: "thirdparty";
action: "show" | "edit" | "create";
parent?: null;
}
type ThirdPartyAddContact = {
id: number,
type: 'thirdparty',
action: 'addContact',
parent: ThirdpartyCompany,
interface ThirdPartyAddContact {
id: number;
type: "thirdparty";
action: "addContact";
parent: ThirdpartyCompany;
}
type ThirdPartyProps = ThirdPartyProp|ThirdPartyAddContact;
type ThirdPartyProps = ThirdPartyProp | ThirdPartyAddContact;
const props = withDefaults(defineProps<ThirdPartyProps>(), {
parent: null,
});
const thirdparty = ref<Thirdparty|null>(null);
const thirdparty = ref<Thirdparty | null>(null);
async function loadData() {
thirdparty.value = await getThirdparty(props.id);
}
onMounted(() => {
if (props.action === 'show' && props.id) {
loadData()
if (props.action === "show" && props.id) {
loadData();
}
})
});
</script>
<style lang="scss" scoped>

View File

@@ -2,9 +2,11 @@
<div>
<div v-if="resolvedParent">
<div class="parent-info">
<i class="fa fa-li fa-hand-o-right"/>
<i class="fa fa-li fa-hand-o-right" />
<b class="me-2">{{ trans(THIRDPARTY_MESSAGES_CHILD_OF) }}</b>
<span class="chill-entity badge-thirdparty">{{ resolvedParent.text }}</span>
<span class="chill-entity badge-thirdparty">{{
resolvedParent.text
}}</span>
</div>
</div>
<div class="form-floating mb-3" v-else-if="props.action === 'create'">
@@ -77,7 +79,9 @@
{{ localizeString(civility.name) }}
</option>
</select>
<label for="civility">{{ trans(THIRDPARTY_MESSAGES_THIRDPARTY_CIVILITY) }}</label>
<label for="civility">{{
trans(THIRDPARTY_MESSAGES_THIRDPARTY_CIVILITY)
}}</label>
</div>
</div>
</div>
@@ -93,7 +97,9 @@
:aria-label="trans(THIRDPARTY_MESSAGES_THIRDPARTY_PROFESSION)"
aria-describedby="profession"
/>
<label for="profession">{{ trans(THIRDPARTY_MESSAGES_THIRDPARTY_PROFESSION) }}</label>
<label for="profession">{{
trans(THIRDPARTY_MESSAGES_THIRDPARTY_PROFESSION)
}}</label>
</div>
</div>
</div>
@@ -110,8 +116,8 @@
:placeholder="trans(THIRDPARTY_MESSAGES_THIRDPARTY_FIRSTNAME)"
/>
<label for="firstname">{{
trans(THIRDPARTY_MESSAGES_THIRDPARTY_FIRSTNAME)
}}</label>
trans(THIRDPARTY_MESSAGES_THIRDPARTY_FIRSTNAME)
}}</label>
</div>
</div>
<div v-if="queryItems">
@@ -139,8 +145,8 @@
:placeholder="trans(THIRDPARTY_MESSAGES_THIRDPARTY_LASTNAME)"
/>
<label for="name">{{
trans(THIRDPARTY_MESSAGES_THIRDPARTY_LASTNAME)
}}</label>
trans(THIRDPARTY_MESSAGES_THIRDPARTY_LASTNAME)
}}</label>
</div>
</div>
<div
@@ -177,8 +183,8 @@
:placeholder="trans(THIRDPARTY_MESSAGES_THIRDPARTY_NAME)"
/>
<label for="name">{{
trans(THIRDPARTY_MESSAGES_THIRDPARTY_NAME)
}}</label>
trans(THIRDPARTY_MESSAGES_THIRDPARTY_NAME)
}}</label>
</div>
</div>
@@ -212,7 +218,7 @@
<div class="mb-3">
<div class="input-group has-validation">
<span id="email" class="input-group-text">
<i class="fa fa-fw fa-at"/>
<i class="fa fa-fw fa-at" />
</span>
<div class="form-floating">
<input
@@ -224,7 +230,9 @@
:aria-label="trans(THIRDPARTY_MESSAGES_THIRDPARTY_EMAIL)"
aria-describedby="email"
/>
<label for="email">{{ trans(THIRDPARTY_MESSAGES_THIRDPARTY_EMAIL) }}</label>
<label for="email">{{
trans(THIRDPARTY_MESSAGES_THIRDPARTY_EMAIL)
}}</label>
</div>
</div>
<div
@@ -238,19 +246,21 @@
<div class="mb-3">
<div class="input-group has-validation">
<span class="input-group-text" id="phonenumber">
<i class="fa fa-fw fa-phone"/>
<i class="fa fa-fw fa-phone" />
</span>
<div class="form-floating">
<input
class="form-control form-control-lg"
:class="{'is-invalid': violations.hasViolation('telephone') }"
:class="{ 'is-invalid': violations.hasViolation('telephone') }"
name="phonenumber"
v-model="thirdParty.telephone"
:placeholder="trans(THIRDPARTY_MESSAGES_THIRDPARTY_PHONENUMBER)"
:aria-label="trans(THIRDPARTY_MESSAGES_THIRDPARTY_PHONENUMBER)"
aria-describedby="phonenumber"
/>
<label for="phonenumber">{{ trans(THIRDPARTY_MESSAGES_THIRDPARTY_PHONENUMBER) }}</label>
<label for="phonenumber">{{
trans(THIRDPARTY_MESSAGES_THIRDPARTY_PHONENUMBER)
}}</label>
</div>
</div>
<div
@@ -263,7 +273,9 @@
<div class="mb-3">
<div class="input-group has-validation">
<span class="input-group-text" id="phonenumber2"><i class="fa fa-fw fa-phone"/></span>
<span class="input-group-text" id="phonenumber2"
><i class="fa fa-fw fa-phone"
/></span>
<div class="form-floating">
<input
class="form-control form-control-lg"
@@ -274,7 +286,9 @@
:aria-label="trans(THIRDPARTY_MESSAGES_THIRDPARTY_PHONENUMBER2)"
aria-describedby="phonenumber2"
/>
<label for="phonenumber2">{{ trans(THIRDPARTY_MESSAGES_THIRDPARTY_PHONENUMBER2) }}</label>
<label for="phonenumber2">{{
trans(THIRDPARTY_MESSAGES_THIRDPARTY_PHONENUMBER2)
}}</label>
</div>
</div>
<div
@@ -288,7 +302,7 @@
<div v-if="props.action !== 'addContact'">
<div class="input-group mb-3">
<span class="input-group-text" id="comment"
><i class="fa fa-fw fa-pencil"
><i class="fa fa-fw fa-pencil"
/></span>
<textarea
class="form-control form-control-lg"
@@ -301,17 +315,18 @@
</template>
<script setup lang="ts">
import {ref, reactive, computed, onMounted, getCurrentInstance} from 'vue'
import ThirdPartyRenderBox from '../Entity/ThirdPartyRenderBox.vue'
import { ref, reactive, computed, onMounted, getCurrentInstance } from "vue";
import ThirdPartyRenderBox from "../Entity/ThirdPartyRenderBox.vue";
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
import {
createThirdParty,
getThirdparty, patchThirdparty,
getThirdparty,
patchThirdparty,
thirdpartyToWriteThirdParty,
WriteThirdPartyViolationMap
} from '../../_api/OnTheFly'
import BadgeEntity from 'ChillMainAssets/vuejs/_components/BadgeEntity.vue'
import {localizeString as _localizeString} from 'ChillMainAssets/lib/localizationHelper/localizationHelper'
WriteThirdPartyViolationMap,
} from "../../_api/OnTheFly";
import BadgeEntity from "ChillMainAssets/vuejs/_components/BadgeEntity.vue";
import { localizeString as _localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import {
trans,
THIRDPARTY_MESSAGES_THIRDPARTY_FIRSTNAME,
@@ -323,11 +338,13 @@ import {
THIRDPARTY_MESSAGES_THIRDPARTY_COMMENT,
THIRDPARTY_MESSAGES_THIRDPARTY_PROFESSION,
THIRDPARTY_MESSAGES_THIRDPARTY_CIVILITY,
THIRDPARTY_MESSAGES_CHILD_OF, PERSON_EDIT_ERROR_WHILE_SAVING,
} from 'translator'
THIRDPARTY_MESSAGES_CHILD_OF,
PERSON_EDIT_ERROR_WHILE_SAVING,
} from "translator";
import {
createPerson,
getCivilities, WritePersonViolationMap,
getCivilities,
WritePersonViolationMap,
} from "ChillPersonAssets/vuejs/_api/OnTheFly";
import {
isThirdpartyChild,
@@ -335,44 +352,49 @@ import {
Thirdparty,
ThirdpartyCompany,
ThirdPartyKind,
ThirdPartyWrite
ThirdPartyWrite,
} from "../../../types";
import {Civility, SetCivility} from "ChillMainAssets/types";
import {isValidationException} from "ChillMainAssets/lib/api/apiMethods";
import {useViolationList} from "ChillMainAssets/vuejs/_composables/violationList";
import {useToast} from "vue-toast-notification";
import { Civility, SetCivility } from "ChillMainAssets/types";
import { isValidationException } from "ChillMainAssets/lib/api/apiMethods";
import { useViolationList } from "ChillMainAssets/vuejs/_composables/violationList";
import { useToast } from "vue-toast-notification";
interface ThirdPartyEditWriteProps {
id?: number;
type?: 'thirdparty';
action: 'edit' | 'create'
type?: "thirdparty";
action: "edit" | "create";
query?: string;
parent?: null;
}
interface ThirdPartyAddContactProps {
id?: null;
type?: 'thirdparty';
action: 'addContact';
type?: "thirdparty";
action: "addContact";
query?: "";
parent: ThirdpartyCompany;
}
type ThirdPartyEditProps = ThirdPartyAddContactProps | ThirdPartyEditWriteProps;
const props = withDefaults(defineProps<ThirdPartyEditProps>(), {type: 'thirdparty', query: "", parent: null});
const props = withDefaults(defineProps<ThirdPartyEditProps>(), {
type: "thirdparty",
query: "",
parent: null,
});
const emit =
defineEmits<(e: "onThirdPartyCreated", payload: { thirdParty: Thirdparty }) => void>();
defineEmits<
(e: "onThirdPartyCreated", payload: { thirdParty: Thirdparty }) => void
>();
defineExpose({postThirdParty});
defineExpose({ postThirdParty });
const toast = useToast();
const thirdParty = ref<ThirdPartyWrite>({
type: "thirdparty",
kind: 'company',
kind: "company",
address: null,
civility: null,
email: "",
@@ -385,7 +407,7 @@ const thirdParty = ref<ThirdPartyWrite>({
parent: null,
});
const originalThirdParty = ref<Thirdparty|null>(null);
const originalThirdParty = ref<Thirdparty | null>(null);
const civility = computed<number | null>({
get: () => {
@@ -397,21 +419,21 @@ const civility = computed<number | null>({
},
set: (value: number | null) => {
thirdParty.value.civility =
value !== null ? {id: value, type: "chill_main_civility"} : null;
value !== null ? { id: value, type: "chill_main_civility" } : null;
},
});
const civilities = ref<Civility[]>([])
const civilities = ref<Civility[]>([]);
const addAddress = reactive({
options: {
openPanesInModal: true,
onlyButton: false,
button: {size: 'btn-sm'},
title: {create: 'add_an_address_title', edit: 'edit_address'},
button: { size: "btn-sm" },
title: { create: "add_an_address_title", edit: "edit_address" },
},
})
const addAddressRef = ref<any>(null)
});
const addAddressRef = ref<any>(null);
/**
* We need a specific computed for the kind
@@ -421,31 +443,34 @@ const kind = computed<ThirdPartyKind>({
return thirdParty.value.kind;
},
set(v) {
thirdParty.value.kind = v
thirdParty.value.kind = v;
},
});
const context = computed(() => {
const ctx: any = {
target: {name: props.type, id: props.id},
target: { name: props.type, id: props.id },
edit: false,
addressId: null as number | null,
defaults: (window as any).addaddress,
}
};
if (thirdParty.value.address) {
ctx.addressId = thirdParty.value.address.id
ctx.edit = true
ctx.addressId = thirdParty.value.address.id;
ctx.edit = true;
}
return ctx
})
return ctx;
});
const resolvedParent = computed<null|ThirdpartyCompany>(() => {
if (null !== originalThirdParty.value && isThirdpartyChild(originalThirdParty.value)) {
const resolvedParent = computed<null | ThirdpartyCompany>(() => {
if (
null !== originalThirdParty.value &&
isThirdpartyChild(originalThirdParty.value)
) {
return originalThirdParty.value.parent;
}
return props.parent ?? null;
})
});
/**
* Find the query items to display for suggestion
@@ -461,7 +486,10 @@ const queryItems = computed(() => {
.trim()
.toLowerCase()
.split(" ");
const lastNameWords = (thirdParty.value.name || "").trim().toLowerCase().split(" ");
const lastNameWords = (thirdParty.value.name || "")
.trim()
.toLowerCase()
.split(" ");
return words
.filter((word) => !firstNameWords.includes(word.toLowerCase()))
@@ -469,20 +497,19 @@ const queryItems = computed(() => {
});
function localizeString(str: any) {
return _localizeString(str)
return _localizeString(str);
}
onMounted(() => {
getCivilities().then((cv) => {
civilities.value = cv;
});
if (props.action === "edit") {
loadData();
} else if (props.action === 'addContact') {
thirdParty.value.kind = 'child';
} else if (props.action === "addContact") {
thirdParty.value.kind = "child";
thirdParty.value.address = null;
thirdParty.value.parent = {id: props.parent.id, type: 'thirdparty'};
thirdParty.value.parent = { id: props.parent.id, type: "thirdparty" };
}
});
@@ -495,38 +522,38 @@ async function loadData(): Promise<void> {
function submitAddress(payload: { addressId: number }) {
console.log(payload);
thirdParty.value.address = {id: payload.addressId};
thirdParty.value.address = { id: payload.addressId };
}
function addQueryItem(field: 'name' | 'firstName', queryItem: string) {
function addQueryItem(field: "name" | "firstName", queryItem: string) {
switch (field) {
case 'name':
case "name":
if (thirdParty.value.name) {
thirdParty.value.name += ` ${queryItem}`
thirdParty.value.name += ` ${queryItem}`;
} else {
thirdParty.value.name = queryItem
thirdParty.value.name = queryItem;
}
break
case 'firstName':
thirdParty.value.firstname = queryItem
break
break;
case "firstName":
thirdParty.value.firstname = queryItem;
break;
}
}
function addQuery(query: string) {
thirdParty.value.name = query
thirdParty.value.name = query;
}
const violations = useViolationList<WriteThirdPartyViolationMap>();
async function postThirdParty(): Promise<Thirdparty> {
try {
if (props.action === 'edit' && props.id) {
if (props.action === "edit" && props.id) {
const tp = await patchThirdparty(props.id, thirdParty.value);
return Promise.resolve(tp);
} else if (props.action === 'addContact' || props.action === 'create') {
} else if (props.action === "addContact" || props.action === "create") {
const tp = await createThirdParty(thirdParty.value);
emit('onThirdPartyCreated', {thirdParty: tp});
emit("onThirdPartyCreated", { thirdParty: tp });
return Promise.resolve(tp);
}
} catch (e: unknown) {
@@ -539,7 +566,6 @@ async function postThirdParty(): Promise<Thirdparty> {
}
throw "'action' is not edit with and id, or addContact or create";
}
</script>
<style scoped lang="scss">

View File

@@ -148,9 +148,8 @@ export type TicketHistoryLine =
| CallerStateEvent;
interface BaseTicket<
T extends
| "ticket_ticket:simple"
| "ticket_ticket:extended" = "ticket_ticket:simple",
T extends "ticket_ticket:simple" | "ticket_ticket:extended" =
"ticket_ticket:simple",
> {
type_extended: T;
type: "ticket_ticket";