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", "@hotwired/stimulus": "^3.0.0",
"@luminateone/eslint-baseline": "^1.0.9", "@luminateone/eslint-baseline": "^1.0.9",
"@symfony/stimulus-bridge": "^3.2.0", "@symfony/stimulus-bridge": "^3.2.0",
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
"@symfony/webpack-encore": "^4.1.0", "@symfony/webpack-encore": "^4.1.0",
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",

View File

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

View File

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

View File

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

View File

@@ -1,166 +1,148 @@
<template> <template>
<teleport to="#mainUser"> <teleport to="#mainUser">
<h2 class="chill-red">Utilisateur principal</h2> <h2 class="chill-red">Utilisateur principal</h2>
<div> <div>
<div> <div>
<div v-if="null !== this.$store.getters.getMainUser"> <div v-if="null !== this.$store.getters.getMainUser">
<calendar-active :user="this.$store.getters.getMainUser" /> <calendar-active :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>
</div> </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"> <teleport to="#schedule">
<div class="row mb-3" v-if="activity.startDate !== null"> <div class="row mb-3" v-if="activity.startDate !== null">
<label class="col-form-label col-sm-4">Date</label> <label class="col-form-label col-sm-4">Date</label>
<div class="col-sm-8"> <div class="col-sm-8">
{{ $d(activity.startDate, "long") }} - {{ $d(activity.startDate, "long") }} -
{{ $d(activity.endDate, "hoursOnly") }} {{ $d(activity.endDate, "hoursOnly") }}
<span v-if="activity.calendarRange === null" <span v-if="activity.calendarRange === null"
>(Pas de plage de disponibilité sélectionnée)</span >(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"
> >
<div class="col-sm-9 col-xs-12"> <span v-else>(Une plage de disponibilité sélectionnée)</span>
<div class="input-group mb-3"> </div>
<label class="input-group-text" for="slotDuration" </div>
>Durée des créneaux</label </teleport>
>
<select <location />
v-model="slotDuration"
id="slotDuration" <teleport to="#fullCalendar">
class="form-select" <div class="calendar-actives">
> <template v-for="u in getActiveUsers" :key="u.id">
<option value="00:05:00">5 minutes</option> <calendar-active
<option value="00:10:00">10 minutes</option> :user="u"
<option value="00:15:00">15 minutes</option> :invite="this.$store.getters.getInviteForUser(u)"
<option value="00:30:00">30 minutes</option> />
<option value="00:45:00">45 minutes</option> </template>
<option value="00:60:00">60 minutes</option> </div>
</select> <div
<label class="input-group-text" for="slotMinTime">De</label> class="display-options row justify-content-between"
<select style="margin-top: 1rem"
v-model="slotMinTime" >
id="slotMinTime" <div class="col-sm-9 col-xs-12">
class="form-select" <div class="input-group mb-3">
> <label class="input-group-text" for="slotDuration"
<option value="00:00:00">0h</option> >Durée des créneaux</label
<option value="01:00:00">1h</option> >
<option value="02:00:00">2h</option> <select v-model="slotDuration" id="slotDuration" class="form-select">
<option value="03:00:00">3h</option> <option value="00:05:00">5 minutes</option>
<option value="04:00:00">4h</option> <option value="00:10:00">10 minutes</option>
<option value="05:00:00">5h</option> <option value="00:15:00">15 minutes</option>
<option value="06:00:00">6h</option> <option value="00:30:00">30 minutes</option>
<option value="07:00:00">7h</option> <option value="00:45:00">45 minutes</option>
<option value="08:00:00">8h</option> <option value="00:60:00">60 minutes</option>
<option value="09:00:00">9h</option> </select>
<option value="10:00:00">10h</option> <label class="input-group-text" for="slotMinTime">De</label>
<option value="11:00:00">11h</option> <select v-model="slotMinTime" id="slotMinTime" class="form-select">
<option value="12:00:00">12h</option> <option value="00:00:00">0h</option>
</select> <option value="01:00:00">1h</option>
<label class="input-group-text" for="slotMaxTime">À</label> <option value="02:00:00">2h</option>
<select <option value="03:00:00">3h</option>
v-model="slotMaxTime" <option value="04:00:00">4h</option>
id="slotMaxTime" <option value="05:00:00">5h</option>
class="form-select" <option value="06:00:00">6h</option>
> <option value="07:00:00">7h</option>
<option value="12:00:00">12h</option> <option value="08:00:00">8h</option>
<option value="13:00:00">13h</option> <option value="09:00:00">9h</option>
<option value="14:00:00">14h</option> <option value="10:00:00">10h</option>
<option value="15:00:00">15h</option> <option value="11:00:00">11h</option>
<option value="16:00:00">16h</option> <option value="12:00:00">12h</option>
<option value="17:00:00">17h</option> </select>
<option value="18:00:00">18h</option> <label class="input-group-text" for="slotMaxTime">À</label>
<option value="19:00:00">19h</option> <select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
<option value="20:00:00">20h</option> <option value="12:00:00">12h</option>
<option value="21:00:00">21h</option> <option value="13:00:00">13h</option>
<option value="22:00:00">22h</option> <option value="14:00:00">14h</option>
<option value="23:00:00">23h</option> <option value="15:00:00">15h</option>
<option value="23:59:59">24h</option> <option value="16:00:00">16h</option>
</select> <option value="17:00:00">17h</option>
</div> <option value="18:00:00">18h</option>
</div> <option value="19:00:00">19h</option>
<div class="col-sm-3 col-xs-12"> <option value="20:00:00">20h</option>
<div class="float-end"> <option value="21:00:00">21h</option>
<div class="form-check input-group"> <option value="22:00:00">22h</option>
<span class="input-group-text"> <option value="23:00:00">23h</option>
<input <option value="23:59:59">24h</option>
id="showHideWE" </select>
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> </div>
<FullCalendar ref="fullCalendar" :options="calendarOptions"> </div>
<template #eventContent="arg"> <div class="col-sm-3 col-xs-12">
<span> <div class="float-end">
<b v-if="arg.event.extendedProps.is === 'remote'">{{ <div class="form-check input-group">
arg.event.title <span class="input-group-text">
}}</b> <input
<b v-else-if="arg.event.extendedProps.is === 'range'" id="showHideWE"
>{{ arg.timeText }} class="mt-0"
{{ arg.event.extendedProps.locationName }} type="checkbox"
<small>{{ v-model="hideWeekends"
arg.event.extendedProps.userLabel />
}}</small></b </span>
> <label for="showHideWE" class="form-check-label input-group-text"
<b v-else-if="arg.event.extendedProps.is === 'current'" >Week-ends</label
>{{ arg.timeText }} {{ $t("current_selected") }} >
</b> </div>
<b v-else-if="arg.event.extendedProps.is === 'local'">{{ </div>
arg.event.title </div>
}}</b> </div>
<b v-else <FullCalendar ref="fullCalendar" :options="calendarOptions">
>{{ arg.timeText }} {{ $t("current_selected") }} <template #eventContent="arg">
</b> <span>
</span> <b v-if="arg.event.extendedProps.is === 'remote'">{{
</template> arg.event.title
</FullCalendar> }}</b>
</teleport> <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> </template>
<script> <script>
@@ -177,219 +159,210 @@ import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
import { mapGetters, mapState } from "vuex"; import { mapGetters, mapState } from "vuex";
export default { export default {
name: "App", name: "App",
components: { components: {
ConcernedGroups, ConcernedGroups,
Location, Location,
FullCalendar, FullCalendar,
CalendarActive, CalendarActive,
PickEntity, 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() { calendarOptions() {
return { return {
errorMsg: [], locale: frLocale,
showMyCalendar: false, plugins: [
slotDuration: "00:05:00", dayGridPlugin,
slotMinTime: "09:00:00", interactionPlugin,
slotMaxTime: "18:00:00", timeGridPlugin,
hideWeekEnds: true, dayGridPlugin,
previousUser: [], 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: { getActiveUsers() {
...mapGetters(["getMainUser"]), const users = [];
...mapState(["activity"]), for (const id of this.$store.state.currentView.users.keys()) {
events() { users.push(this.$store.getters.getUserDataById(id).user);
return this.$store.getters.getEventSources; }
}, return users;
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;
},
}, },
methods: { suggestedUsers() {
setMainUser({ entity }) { const suggested = [];
const user = entity;
console.log("setMainUser APP", entity);
if ( this.$data.previousUser.forEach((u) => {
user.id !== this.$store.getters.getMainUser && if (u.id !== this.$store.getters.getMainUser.id) {
(this.$store.state.activity.calendarRange !== null || suggested.push(u);
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) return suggested;
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,
});
},
}, },
},
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> </script>
<style> <style>
.calendar-actives { .calendar-actives {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
} }
.display-options { .display-options {
margin-top: 1rem; margin-top: 1rem;
} }
/* for events which are range */ /* for events which are range */
.fc-event.isrange { .fc-event.isrange {
border-width: 3px; border-width: 3px;
} }
</style> </style>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
StoredObject, StoredObject,
StoredObjectPointInTime, StoredObjectPointInTime,
StoredObjectVersionWithPointInTime, StoredObjectVersionWithPointInTime,
} from "ChillDocStoreAssets/types"; } from "ChillDocStoreAssets/types";
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue"; import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue";
import { ISOToDatetime } from "ChillMainAssets/chill/js/date"; import { ISOToDatetime } from "ChillMainAssets/chill/js/date";
@@ -12,185 +12,173 @@ import DownloadButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/Downloa
import { computed } from "vue"; import { computed } from "vue";
interface HistoryButtonListItemConfig { interface HistoryButtonListItemConfig {
version: StoredObjectVersionWithPointInTime; version: StoredObjectVersionWithPointInTime;
storedObject: StoredObject; storedObject: StoredObject;
canEdit: boolean; canEdit: boolean;
isCurrent: boolean; isCurrent: boolean;
} }
const emit = defineEmits<{ const emit = defineEmits<{
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime]; restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
}>(); }>();
const props = defineProps<HistoryButtonListItemConfig>(); const props = defineProps<HistoryButtonListItemConfig>();
const onRestore = ({ const onRestore = ({
newVersion, newVersion,
}: { }: {
newVersion: StoredObjectVersionWithPointInTime; newVersion: StoredObjectVersionWithPointInTime;
}) => { }) => {
emit("restoreVersion", { newVersion }); emit("restoreVersion", { newVersion });
}; };
const isKeptBeforeConversion = computed<boolean>(() => { const isKeptBeforeConversion = computed<boolean>(() => {
if ("point-in-times" in props.version) { if ("point-in-times" in props.version) {
return props.version["point-in-times"].reduce( return props.version["point-in-times"].reduce(
(accumulator: boolean, pit: StoredObjectPointInTime) => (accumulator: boolean, pit: StoredObjectPointInTime) =>
accumulator || "keep-before-conversion" === pit.reason, accumulator || "keep-before-conversion" === pit.reason,
false, false,
); );
} else { } else {
return false; return false;
} }
}); });
const isRestored = computed<boolean>( 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>( 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<{ const classes = computed<{
row: true; row: true;
"row-hover": true; "row-hover": true;
"blinking-1": boolean; "blinking-1": boolean;
"blinking-2": boolean; "blinking-2": boolean;
}>(() => ({ }>(() => ({
row: true, row: true,
"row-hover": true, "row-hover": true,
"blinking-1": props.isRestored && 0 === props.version.version % 2, "blinking-1": props.isRestored && 0 === props.version.version % 2,
"blinking-2": props.isRestored && 1 === props.version.version % 2, "blinking-2": props.isRestored && 1 === props.version.version % 2,
})); }));
</script> </script>
<template> <template>
<div :class="classes"> <div :class="classes">
<div <div
class="col-12 tags" class="col-12 tags"
v-if=" v-if="isCurrent || isKeptBeforeConversion || isRestored || isDuplicated"
isCurrent || >
isKeptBeforeConversion || <span class="badge bg-success" v-if="isCurrent">Version actuelle</span>
isRestored || <span class="badge bg-info" v-if="isKeptBeforeConversion"
isDuplicated >Conservée avant conversion dans un autre format</span
" >
> <span class="badge bg-info" v-if="isRestored"
<span class="badge bg-success" v-if="isCurrent" >Restaurée depuis la version
>Version actuelle</span {{ version["from-restored"]?.version + 1 }}</span
> >
<span class="badge bg-info" v-if="isKeptBeforeConversion" <span class="badge bg-info" v-if="isDuplicated"
>Conservée avant conversion dans un autre format</span >Dupliqué depuis un autre document</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> </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> </template>
<style scoped lang="scss"> <style scoped lang="scss">
div.tags { div.tags {
span.badge:not(:last-child) { span.badge:not(:last-child) {
margin-right: 0.5rem; margin-right: 0.5rem;
} }
} }
// to make the animation restart, we have the same animation twice, // to make the animation restart, we have the same animation twice,
// and alternate between both // and alternate between both
.blinking-1 { .blinking-1 {
animation-name: backgroundColorPalette-1; animation-name: backgroundColorPalette-1;
animation-duration: 8s; animation-duration: 8s;
animation-iteration-count: 1; animation-iteration-count: 1;
animation-direction: normal; animation-direction: normal;
animation-timing-function: linear; animation-timing-function: linear;
} }
@keyframes backgroundColorPalette-1 { @keyframes backgroundColorPalette-1 {
0% { 0% {
background: var(--bs-chill-green-dark); background: var(--bs-chill-green-dark);
} }
25% { 25% {
background: var(--bs-chill-green); background: var(--bs-chill-green);
} }
65% { 65% {
background: var(--bs-chill-beige); background: var(--bs-chill-beige);
} }
100% { 100% {
background: unset; background: unset;
} }
} }
.blinking-2 { .blinking-2 {
animation-name: backgroundColorPalette-2; animation-name: backgroundColorPalette-2;
animation-duration: 8s; animation-duration: 8s;
animation-iteration-count: 1; animation-iteration-count: 1;
animation-direction: normal; animation-direction: normal;
animation-timing-function: linear; animation-timing-function: linear;
} }
@keyframes backgroundColorPalette-2 { @keyframes backgroundColorPalette-2 {
0% { 0% {
background: var(--bs-chill-green-dark); background: var(--bs-chill-green-dark);
} }
25% { 25% {
background: var(--bs-chill-green); background: var(--bs-chill-green);
} }
65% { 65% {
background: var(--bs-chill-beige); background: var(--bs-chill-beige);
} }
100% { 100% {
background: unset; background: unset;
} }
} }
</style> </style>

View File

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

View File

@@ -3,7 +3,7 @@ import {
Scope, Scope,
ValidationExceptionInterface, ValidationExceptionInterface,
ValidationProblemFromMap, ValidationProblemFromMap,
ViolationFromMap ViolationFromMap,
} from "../../types"; } from "../../types";
export type body = Record<string, boolean | string | number | null>; export type body = Record<string, boolean | string | number | null>;
@@ -32,11 +32,11 @@ export interface TransportExceptionInterface {
} }
export class ValidationException< export class ValidationException<
M extends Record<string, Record<string, string|number>> = Record< M extends Record<string, Record<string, string | number>> = Record<
string, string,
Record<string, string|number> Record<string, string | number>
>, >,
> >
extends Error extends Error
implements ValidationExceptionInterface<M> implements ValidationExceptionInterface<M>
{ {
@@ -68,7 +68,10 @@ export class ValidationException<
this.byProperty = problem.violations.reduce( this.byProperty = problem.violations.reduce(
(acc, v) => { (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); (acc[key] ||= []).push(v.title);
return acc; return acc;
}, },
@@ -80,28 +83,27 @@ export class ValidationException<
} }
} }
violationsByNormalizedProperty(property: Extract<keyof M, string>): ViolationFromMap<M>[] { violationsByNormalizedProperty(
return this.violationsList.filter((v) => v.propertyPath.replace(/\[\d+\]$/, "") === property); property: Extract<keyof M, string>,
): ViolationFromMap<M>[] {
return this.violationsList.filter(
(v) => v.propertyPath.replace(/\[\d+\]$/, "") === property,
);
} }
violationsByNormalizedPropertyAndParams< violationsByNormalizedPropertyAndParams<
P extends Extract<keyof M, string>, 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]): ViolationFromMap<M>[] {
property: P,
param: K,
param_value: M[P][K]
): ViolationFromMap<M>[]
{
const list = this.violationsByNormalizedProperty(property); const list = this.violationsByNormalizedProperty(property);
return list.filter( return list.filter(
(v): boolean => (v): boolean =>
!!v.parameters && !!v.parameters &&
// `with_parameter in v.parameters` check indexing // `with_parameter in v.parameters` check indexing
param in v.parameters && param in v.parameters &&
// the cast is safe, because we have overloading that bind the types // 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 * Check that the exception is a ValidationExceptionInterface
* @param x * @param x
*/ */
export function isValidationException<M extends Record<string, Record<string, string|number>>>( export function isValidationException<
x: unknown, M extends Record<string, Record<string, string | number>>,
): x is ValidationExceptionInterface<M> { >(x: unknown): x is ValidationExceptionInterface<M> {
return ( return (
x instanceof ValidationException || x instanceof ValidationException ||
(typeof x === "object" && (typeof x === "object" &&
@@ -147,8 +149,7 @@ export interface AccessExceptionInterface extends TransportExceptionInterface {
violations: string[]; violations: string[];
} }
export interface NotFoundExceptionInterface export interface NotFoundExceptionInterface extends TransportExceptionInterface {
extends TransportExceptionInterface {
name: "NotFoundException"; name: "NotFoundException";
} }
@@ -159,8 +160,7 @@ export interface ServerExceptionInterface extends TransportExceptionInterface {
body: string; body: string;
} }
export interface ConflictHttpExceptionInterface export interface ConflictHttpExceptionInterface extends TransportExceptionInterface {
extends TransportExceptionInterface {
name: "ConflictHttpException"; name: "ConflictHttpException";
violations: string[]; violations: string[];
} }
@@ -306,9 +306,9 @@ export interface ConflictHttpExceptionInterface
export const makeFetch = async < export const makeFetch = async <
Input, Input,
Output, Output,
M extends Record<string, Record<string, string|number>> = Record< M extends Record<string, Record<string, string | number>> = Record<
string, string,
Record<string, string|number> Record<string, string | number>
>, >,
>( >(
method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE", 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. * 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, * @return {GenderTranslation} Returns the gender translation string corresponding to the provided gender,
* or "unknown" if the gender is null. * or "unknown" if the gender is null.
*/ */
export function toGenderTranslation(gender: Gender|null): GenderTranslation export function toGenderTranslation(gender: Gender | null): GenderTranslation {
{
if (null === gender) { if (null === gender) {
return "unknown"; 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. * @return {string} The "returnPath" from the query string, or the fallback path if "returnPath" is not present.
*/ */
export function returnPathOr(fallbackPath: string): string { export function returnPathOr(fallbackPath: string): string {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const returnPath = urlParams.get("returnPath"); 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"; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
export const fetchWorkflow = async ( export const fetchWorkflow = async (
workflowId: number, workflowId: number,
): Promise<EntityWorkflow> => { ): Promise<EntityWorkflow> => {
try { try {
return await makeFetch<null, EntityWorkflow>( return await makeFetch<null, EntityWorkflow>(
"GET", "GET",
`/api/1.0/main/workflow/${workflowId}.json`, `/api/1.0/main/workflow/${workflowId}.json`,
); );
} catch (error) { } catch (error) {
console.error(`Failed to fetch workflow ${workflowId}:`, error); console.error(`Failed to fetch workflow ${workflowId}:`, error);
throw error; throw error;
} }
}; };

View File

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

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
trans, trans,
EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING, EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING,
EXPORT_GENERATION_TOO_MANY_RETRIES, EXPORT_GENERATION_TOO_MANY_RETRIES,
EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT, EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT,
EXPORT_GENERATION_EXPORT_READY, EXPORT_GENERATION_EXPORT_READY,
} from "translator"; } from "translator";
import { computed, onMounted, ref } from "vue"; import { computed, onMounted, ref } from "vue";
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types"; import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
@@ -14,9 +14,9 @@ import WaitingScreen from "../_components/WaitingScreen.vue";
import { ExportGeneration, WaitingScreenState } from "ChillMainAssets/types"; import { ExportGeneration, WaitingScreenState } from "ChillMainAssets/types";
interface AppProps { interface AppProps {
exportGenerationId: string; exportGenerationId: string;
title: string; title: string;
createdDate: string; createdDate: string;
} }
const props = defineProps<AppProps>(); const props = defineProps<AppProps>();
@@ -24,25 +24,25 @@ const props = defineProps<AppProps>();
const exportGeneration = ref<ExportGeneration | null>(null); const exportGeneration = ref<ExportGeneration | null>(null);
const status = computed<StoredObjectStatus>( const status = computed<StoredObjectStatus>(
() => exportGeneration.value?.status ?? "pending", () => exportGeneration.value?.status ?? "pending",
); );
const storedObject = computed<null | StoredObject>(() => { const storedObject = computed<null | StoredObject>(() => {
if (exportGeneration.value === null) { if (exportGeneration.value === null) {
return null; return null;
} }
return exportGeneration.value?.storedObject; return exportGeneration.value?.storedObject;
}); });
const isPending = computed<boolean>(() => status.value === "pending"); const isPending = computed<boolean>(() => status.value === "pending");
const filename = computed<string>(() => `${props.title}-${props.createdDate}`); const filename = computed<string>(() => `${props.title}-${props.createdDate}`);
const state = computed<WaitingScreenState>((): WaitingScreenState => { const state = computed<WaitingScreenState>((): WaitingScreenState => {
if (status.value === "empty") { if (status.value === "empty") {
return "pending"; return "pending";
} }
return status.value; return status.value;
}); });
/** /**
@@ -56,69 +56,69 @@ let tryiesForReady = ref<number>(0);
const maxTryiesForReady = 120; const maxTryiesForReady = 120;
const checkForReady = function (): void { const checkForReady = function (): void {
if ( if (
"ready" === status.value || "ready" === status.value ||
"empty" === status.value || "empty" === status.value ||
"failure" === status.value || "failure" === status.value ||
// stop reloading if the page stays opened for a long time // stop reloading if the page stays opened for a long time
tryiesForReady.value > maxTryiesForReady tryiesForReady.value > maxTryiesForReady
) { ) {
return; return;
} }
tryiesForReady.value = tryiesForReady.value + 1; tryiesForReady.value = tryiesForReady.value + 1;
setTimeout(onObjectNewStatusCallback, 5000); setTimeout(onObjectNewStatusCallback, 5000);
}; };
const onObjectNewStatusCallback = async function (): Promise<void> { const onObjectNewStatusCallback = async function (): Promise<void> {
exportGeneration.value = await fetchExportGenerationStatus( exportGeneration.value = await fetchExportGenerationStatus(
props.exportGenerationId, props.exportGenerationId,
); );
if (isPending.value) {
checkForReady();
return Promise.resolve();
}
if (isPending.value) {
checkForReady();
return Promise.resolve(); return Promise.resolve();
}
return Promise.resolve();
}; };
onMounted(() => { onMounted(() => {
onObjectNewStatusCallback(); onObjectNewStatusCallback();
}); });
</script> </script>
<template> <template>
<WaitingScreen :state="state"> <WaitingScreen :state="state">
<template v-slot:pending> <template v-slot:pending>
<p> <p>
{{ trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING) }} {{ trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING) }}
</p> </p>
</template> </template>
<template v-slot:stopped> <template v-slot:stopped>
<p> <p>
{{ trans(EXPORT_GENERATION_TOO_MANY_RETRIES) }} {{ trans(EXPORT_GENERATION_TOO_MANY_RETRIES) }}
</p> </p>
</template> </template>
<template v-slot:failure> <template v-slot:failure>
<p> <p>
{{ trans(EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT) }} {{ trans(EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT) }}
</p> </p>
</template> </template>
<template v-slot:ready> <template v-slot:ready>
<p> <p>
{{ trans(EXPORT_GENERATION_EXPORT_READY) }} {{ trans(EXPORT_GENERATION_EXPORT_READY) }}
</p> </p>
<p v-if="storedObject !== null"> <p v-if="storedObject !== null">
<document-action-buttons-group <document-action-buttons-group
:stored-object="storedObject" :stored-object="storedObject"
:filename="filename" :filename="filename"
></document-action-buttons-group> ></document-action-buttons-group>
</p> </p>
</template> </template>
</WaitingScreen> </WaitingScreen>
</template> </template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,50 +2,50 @@ import { createApp } from "vue";
import App from "./App.vue"; import App from "./App.vue";
function mountApp(): void { function mountApp(): void {
const el = document.querySelector<HTMLDivElement>(".screen-wait"); const el = document.querySelector<HTMLDivElement>(".screen-wait");
if (!el) { if (!el) {
console.error( console.error(
"WaitPostProcessWorkflow: mount element .screen-wait not found", "WaitPostProcessWorkflow: mount element .screen-wait not found",
); );
return; return;
} }
const workflowIdAttr = el.getAttribute("data-workflow-id"); const workflowIdAttr = el.getAttribute("data-workflow-id");
const expectedStep = el.getAttribute("data-expected-step") || ""; const expectedStep = el.getAttribute("data-expected-step") || "";
if (!workflowIdAttr) { if (!workflowIdAttr) {
console.error( console.error(
"WaitPostProcessWorkflow: data-workflow-id attribute missing on mount element", "WaitPostProcessWorkflow: data-workflow-id attribute missing on mount element",
); );
return; return;
} }
if (!expectedStep) { if (!expectedStep) {
console.error( console.error(
"WaitPostProcessWorkflow: data-expected-step attribute missing on mount element", "WaitPostProcessWorkflow: data-expected-step attribute missing on mount element",
); );
return; return;
} }
const workflowId = Number(workflowIdAttr); const workflowId = Number(workflowIdAttr);
if (Number.isNaN(workflowId)) { if (Number.isNaN(workflowId)) {
console.error( console.error(
"WaitPostProcessWorkflow: data-workflow-id is not a valid number:", "WaitPostProcessWorkflow: data-workflow-id is not a valid number:",
workflowIdAttr, workflowIdAttr,
); );
return; return;
} }
const app = createApp(App, { const app = createApp(App, {
workflowId, workflowId,
expectedStep, expectedStep,
}); });
app.mount(el); app.mount(el);
} }
if (document.readyState === "loading") { if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", mountApp); document.addEventListener("DOMContentLoaded", mountApp);
} else { } 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"; import { trans, WORKFLOW_ATTACHMENTS_ADD_AN_ATTACHMENT } from "translator";
interface AppConfig { interface AppConfig {
workflowId: number; workflowId: number;
accompanyingPeriodId: number; accompanyingPeriodId: number;
attachments: WorkflowAttachment[]; attachments: WorkflowAttachment[];
} }
const emit = defineEmits<{ const emit = defineEmits<{
( (
e: "pickGenericDoc", e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod }, payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void; ): void;
(e: "removeAttachment", payload: { attachment: WorkflowAttachment }): void; (e: "removeAttachment", payload: { attachment: WorkflowAttachment }): void;
}>(); }>();
type PickGenericModalType = InstanceType<typeof PickGenericDocModal>; type PickGenericModalType = InstanceType<typeof PickGenericDocModal>;
@@ -28,66 +28,66 @@ const pickDocModal = useTemplateRef<PickGenericModalType>("pickDocModal");
const props = defineProps<AppConfig>(); const props = defineProps<AppConfig>();
const attachedGenericDoc = computed<GenericDocForAccompanyingPeriod[]>( const attachedGenericDoc = computed<GenericDocForAccompanyingPeriod[]>(
() => () =>
props.attachments props.attachments
.map((a: WorkflowAttachment) => a.genericDoc) .map((a: WorkflowAttachment) => a.genericDoc)
.filter( .filter(
(g: GenericDoc | null) => g !== null, (g: GenericDoc | null) => g !== null,
) as GenericDocForAccompanyingPeriod[], ) as GenericDocForAccompanyingPeriod[],
); );
const workflow = ref<EntityWorkflow | null>(null); const workflow = ref<EntityWorkflow | null>(null);
onMounted(async () => { onMounted(async () => {
workflow.value = await fetchWorkflow(Number(props.workflowId)); workflow.value = await fetchWorkflow(Number(props.workflowId));
console.log("workflow", workflow.value); console.log("workflow", workflow.value);
}); });
const openModal = function () { const openModal = function () {
pickDocModal.value?.openModal(); pickDocModal.value?.openModal();
}; };
const onPickGenericDoc = ({ const onPickGenericDoc = ({
genericDoc, genericDoc,
}: { }: {
genericDoc: GenericDocForAccompanyingPeriod; genericDoc: GenericDocForAccompanyingPeriod;
}) => { }) => {
emit("pickGenericDoc", { genericDoc }); emit("pickGenericDoc", { genericDoc });
}; };
const onRemoveAttachment = (payload: { attachment: WorkflowAttachment }) => { const onRemoveAttachment = (payload: { attachment: WorkflowAttachment }) => {
emit("removeAttachment", payload); emit("removeAttachment", payload);
}; };
const canEditAttachement = computed<boolean>(() => { const canEditAttachement = computed<boolean>(() => {
if (null === workflow.value) { if (null === workflow.value) {
return false; return false;
} }
return workflow.value._permissions.CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT; return workflow.value._permissions.CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT;
}); });
</script> </script>
<template> <template>
<pick-generic-doc-modal <pick-generic-doc-modal
:workflow="workflow" :workflow="workflow"
:accompanying-period-id="props.accompanyingPeriodId" :accompanying-period-id="props.accompanyingPeriodId"
:to-remove="attachedGenericDoc" :to-remove="attachedGenericDoc"
ref="pickDocModal" ref="pickDocModal"
@pickGenericDoc="onPickGenericDoc" @pickGenericDoc="onPickGenericDoc"
></pick-generic-doc-modal> ></pick-generic-doc-modal>
<attachment-list <attachment-list
:workflow="workflow" :workflow="workflow"
:attachments="props.attachments" :attachments="props.attachments"
@removeAttachment="onRemoveAttachment" @removeAttachment="onRemoveAttachment"
></attachment-list> ></attachment-list>
<ul v-if="canEditAttachement" class="record_actions"> <ul v-if="canEditAttachement" class="record_actions">
<li> <li>
<button type="button" class="btn btn-create" @click="openModal"> <button type="button" class="btn btn-create" @click="openModal">
{{ trans(WORKFLOW_ATTACHMENTS_ADD_AN_ATTACHMENT) }} {{ trans(WORKFLOW_ATTACHMENTS_ADD_AN_ATTACHMENT) }}
</button> </button>
</li> </li>
</ul> </ul>
</template> </template>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
GenericDoc, GenericDoc,
GenericDocForAccompanyingPeriod, GenericDocForAccompanyingPeriod,
} from "ChillDocStoreAssets/types/generic_doc"; } from "ChillDocStoreAssets/types/generic_doc";
import PickGenericDocItem from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocItem.vue"; import PickGenericDocItem from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocItem.vue";
import { fetch_generic_docs_by_accompanying_period } from "ChillDocStoreAssets/js/generic-doc-api"; 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"; import { EntityWorkflow } from "ChillMainAssets/types";
interface PickGenericDocProps { interface PickGenericDocProps {
workflow: EntityWorkflow | null; workflow: EntityWorkflow | null;
accompanyingPeriodId: number; accompanyingPeriodId: number;
pickedList: GenericDocForAccompanyingPeriod[]; pickedList: GenericDocForAccompanyingPeriod[];
toRemove: GenericDocForAccompanyingPeriod[]; toRemove: GenericDocForAccompanyingPeriod[];
} }
const props = defineProps<PickGenericDocProps>(); const props = defineProps<PickGenericDocProps>();
const emit = defineEmits<{ const emit = defineEmits<{
( (
e: "pickGenericDoc", e: "pickGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod }, payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void; ): void;
( (
e: "removeGenericDoc", e: "removeGenericDoc",
payload: { genericDoc: GenericDocForAccompanyingPeriod }, payload: { genericDoc: GenericDocForAccompanyingPeriod },
): void; ): void;
}>(); }>();
const genericDocs = ref<GenericDocForAccompanyingPeriod[]>([]); const genericDocs = ref<GenericDocForAccompanyingPeriod[]>([]);
const loaded = ref(false); const loaded = ref(false);
const isPicked = (genericDoc: GenericDocForAccompanyingPeriod): boolean => const isPicked = (genericDoc: GenericDocForAccompanyingPeriod): boolean =>
props.pickedList.findIndex( props.pickedList.findIndex(
(element: GenericDocForAccompanyingPeriod) => (element: GenericDocForAccompanyingPeriod) =>
element.uniqueKey === genericDoc.uniqueKey, element.uniqueKey === genericDoc.uniqueKey,
) !== -1; ) !== -1;
onMounted(async () => { onMounted(async () => {
const fetchedGenericDocs = await fetch_generic_docs_by_accompanying_period( const fetchedGenericDocs = await fetch_generic_docs_by_accompanying_period(
props.accompanyingPeriodId, props.accompanyingPeriodId,
); );
const documentClasses = [ const documentClasses = [
"Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument", "Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument",
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument", "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
"Chill\\DocStoreBundle\\Entity\\PersonDocument", "Chill\\DocStoreBundle\\Entity\\PersonDocument",
]; ];
genericDocs.value = fetchedGenericDocs.filter( genericDocs.value = fetchedGenericDocs.filter(
(doc) => (doc) =>
!documentClasses.includes( !documentClasses.includes(props.workflow?.relatedEntityClass || "") ||
props.workflow?.relatedEntityClass || "", props.workflow?.relatedEntityId !== doc.identifiers.id,
) || props.workflow?.relatedEntityId !== doc.identifiers.id, );
); loaded.value = true;
loaded.value = true;
}); });
const textFilter = ref<string>(""); const textFilter = ref<string>("");
@@ -62,229 +61,213 @@ const dateToFilter = ref<string | null>(null);
const placesFilter = ref<string[]>([]); const placesFilter = ref<string[]>([]);
const availablePlaces = computed<string[]>(() => { const availablePlaces = computed<string[]>(() => {
const places = new Set<string>( const places = new Set<string>(
genericDocs.value.map((genericDoc: GenericDoc) => genericDoc.key), 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 => { const placeTrans = (str: string): string => {
switch (str) { switch (str) {
case "accompanying_course_document": case "accompanying_course_document":
return "Documents du parcours"; return "Documents du parcours";
case "person_document": case "person_document":
return "Documents de l'usager"; return "Documents de l'usager";
case "accompanying_period_calendar_document": case "accompanying_period_calendar_document":
return "Document des rendez-vous des parcours"; return "Document des rendez-vous des parcours";
case "accompanying_period_activity_document": case "accompanying_period_activity_document":
return "Document des échanges des parcours"; return "Document des échanges des parcours";
case "accompanying_period_work_evaluation_document": case "accompanying_period_work_evaluation_document":
return "Document des actions d'accompagnement"; return "Document des actions d'accompagnement";
default: default:
return str; return str;
} }
}; };
const onPickDocument = (payload: { const onPickDocument = (payload: {
genericDoc: GenericDocForAccompanyingPeriod; genericDoc: GenericDocForAccompanyingPeriod;
}) => emit("pickGenericDoc", payload); }) => emit("pickGenericDoc", payload);
const onRemoveGenericDoc = (payload: { const onRemoveGenericDoc = (payload: {
genericDoc: GenericDocForAccompanyingPeriod; genericDoc: GenericDocForAccompanyingPeriod;
}) => emit("removeGenericDoc", payload); }) => emit("removeGenericDoc", payload);
const filteredDocuments = computed<GenericDocForAccompanyingPeriod[]>(() => { const filteredDocuments = computed<GenericDocForAccompanyingPeriod[]>(() => {
if (false === loaded.value) { if (false === loaded.value) {
return []; return [];
} }
return genericDocs.value return genericDocs.value
.filter( .filter(
(genericDoc: GenericDocForAccompanyingPeriod) => (genericDoc: GenericDocForAccompanyingPeriod) =>
!props.toRemove !props.toRemove
.map((g: GenericDocForAccompanyingPeriod) => g.uniqueKey) .map((g: GenericDocForAccompanyingPeriod) => g.uniqueKey)
.includes(genericDoc.uniqueKey), .includes(genericDoc.uniqueKey),
) )
.filter((genericDoc: GenericDocForAccompanyingPeriod) => { .filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (textFilter.value === "") { if (textFilter.value === "") {
return true; return true;
} }
const needles = textFilter.value const needles = textFilter.value
.trim() .trim()
.split(" ") .split(" ")
.map((str: string) => str.trim().toLowerCase()) .map((str: string) => str.trim().toLowerCase())
.filter((str: string) => str.length > 0); .filter((str: string) => str.length > 0);
const title: string = const title: string =
"title" in genericDoc.metadata "title" in genericDoc.metadata
? (genericDoc.metadata.title as string) ? (genericDoc.metadata.title as string)
: ""; : "";
if (title === "") { if (title === "") {
return false; return false;
} }
return needles.every((n: string) => return needles.every((n: string) => title.toLowerCase().includes(n));
title.toLowerCase().includes(n), })
); .filter((genericDoc: GenericDocForAccompanyingPeriod) => {
}) if (placesFilter.value.length === 0) {
.filter((genericDoc: GenericDocForAccompanyingPeriod) => { return true;
if (placesFilter.value.length === 0) { }
return true;
}
return placesFilter.value.includes(genericDoc.key); return placesFilter.value.includes(genericDoc.key);
}) })
.filter((genericDoc: GenericDocForAccompanyingPeriod) => { .filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (dateToFilter.value === null) { if (dateToFilter.value === null) {
return true; return true;
} }
return genericDoc.doc_date.datetime8601 < dateToFilter.value; return genericDoc.doc_date.datetime8601 < dateToFilter.value;
}) })
.filter((genericDoc: GenericDocForAccompanyingPeriod) => { .filter((genericDoc: GenericDocForAccompanyingPeriod) => {
if (dateFromFilter.value === null) { if (dateFromFilter.value === null) {
return true; return true;
} }
return genericDoc.doc_date.datetime8601 > dateFromFilter.value; return genericDoc.doc_date.datetime8601 > dateFromFilter.value;
}); });
}); });
</script> </script>
<template> <template>
<div v-if="loaded"> <div v-if="loaded">
<div> <div>
<form name="f" method="get"> <form name="f" method="get">
<div class="accordion my-3" id="filterOrderAccordion"> <div class="accordion my-3" id="filterOrderAccordion">
<h2 class="accordion-header" id="filterOrderHeading"> <h2 class="accordion-header" id="filterOrderHeading">
<button <button
class="accordion-button" class="accordion-button"
type="button" type="button"
data-bs-toggle="collapse" data-bs-toggle="collapse"
data-bs-target="#filterOrderCollapse" data-bs-target="#filterOrderCollapse"
aria-expanded="true" aria-expanded="true"
aria-controls="filterOrderCollapse" aria-controls="filterOrderCollapse"
> >
<strong <strong
><i class="fa fa-fw fa-filter"></i>Filtrer la ><i class="fa fa-fw fa-filter"></i>Filtrer la liste</strong
liste</strong >
> </button>
</button> </h2>
</h2> <div
<div class="accordion-collapse collapse"
class="accordion-collapse collapse" id="filterOrderCollapse"
id="filterOrderCollapse" aria-labelledby="filterOrderHeading"
aria-labelledby="filterOrderHeading" data-bs-parent="#filterOrderAccordion"
data-bs-parent="#filterOrderAccordion" style=""
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 <div
class="spinner-border ms-auto" class="accordion-body chill_filter_order container-xxl p-5 py-2"
role="status" >
aria-hidden="true" <div class="row my-2">
></div> <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> </div>
</form>
</div> </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> </template>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>

View File

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

View File

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

View File

@@ -1,79 +1,71 @@
<template> <template>
<div class="d-grid gap-2 my-3"> <div class="d-grid gap-2 my-3">
<button <button
class="btn btn-outline-primary text-start d-flex align-items-center" class="btn btn-outline-primary text-start d-flex align-items-center"
:class="{ active: subscriberFinal }" :class="{ active: subscriberFinal }"
type="button" type="button"
@click=" @click="
subscribeTo( subscribeTo(subscriberFinal ? 'unsubscribe' : 'subscribe', 'final')
subscriberFinal ? 'unsubscribe' : 'subscribe', "
'final', >
) <i
" class="fa fa-fw me-2"
> :class="subscriberFinal ? 'fa-check-square-o' : 'fa-square-o'"
<i ></i>
class="fa fa-fw me-2" <span>{{ trans(WORKFLOW_SUBSCRIBE_FINAL) }}</span>
:class="subscriberFinal ? 'fa-check-square-o' : 'fa-square-o'" </button>
></i> <button
<span>{{ trans(WORKFLOW_SUBSCRIBE_FINAL) }}</span> class="btn btn-outline-primary text-start d-flex align-items-center"
</button> :class="{ active: subscriberStep }"
<button type="button"
class="btn btn-outline-primary text-start d-flex align-items-center" @click="subscribeTo(subscriberStep ? 'unsubscribe' : 'subscribe', 'step')"
:class="{ active: subscriberStep }" >
type="button" <i
@click=" class="fa fa-fw me-2"
subscribeTo( :class="subscriberStep ? 'fa-check-square-o' : 'fa-square-o'"
subscriberStep ? 'unsubscribe' : 'subscribe', ></i>
'step', <span>{{ trans(WORKFLOW_SUBSCRIBE_ALL_STEPS) }}</span>
) </button>
" </div>
>
<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> </template>
<script setup> <script setup>
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts"; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
import { defineProps, defineEmits } from "vue"; import { defineProps, defineEmits } from "vue";
import { import {
trans, trans,
WORKFLOW_SUBSCRIBE_FINAL, WORKFLOW_SUBSCRIBE_FINAL,
WORKFLOW_SUBSCRIBE_ALL_STEPS, WORKFLOW_SUBSCRIBE_ALL_STEPS,
} from "translator"; } from "translator";
// props // props
const props = defineProps({ const props = defineProps({
entityWorkflowId: { entityWorkflowId: {
type: Number, type: Number,
required: true, required: true,
}, },
subscriberStep: { subscriberStep: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
subscriberFinal: { subscriberFinal: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
}); });
//methods //methods
const subscribeTo = (step, to) => { const subscribeTo = (step, to) => {
let params = new URLSearchParams(); let params = new URLSearchParams();
params.set("subscribe", to); params.set("subscribe", to);
const url = const url =
`/api/1.0/main/workflow/${props.entityWorkflowId}/${step}?` + `/api/1.0/main/workflow/${props.entityWorkflowId}/${step}?` +
params.toString(); params.toString();
makeFetch("POST", url).then((response) => { makeFetch("POST", url).then((response) => {
emit("subscriptionUpdated", response); emit("subscriptionUpdated", response);
}); });
}; };
// emit // emit

View File

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

View File

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

View File

@@ -1,23 +1,25 @@
import {ref} from "vue"; import { ref } from "vue";
import {ValidationExceptionInterface} from "ChillMainAssets/types"; 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>; 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[] { function violationTitles<P extends ViolationKey>(property: P): string[] {
if (null === violationsList.value) { if (null === violationsList.value) {
return []; return [];
} }
const r = violationsList.value.violationsByNormalizedProperty(property).map((v) => v.title); const r = violationsList.value
.violationsByNormalizedProperty(property)
.map((v) => v.title);
return r; return r;
} }
function violationTitlesWithParameter< function violationTitlesWithParameter<
P extends ViolationKey, P extends ViolationKey,
Param extends Extract<keyof T[P], string> Param extends Extract<keyof T[P], string>,
>( >(
property: P, property: P,
with_parameter: Param, with_parameter: Param,
@@ -26,26 +28,38 @@ export function useViolationList<T extends Record<string, Record<string, string>
if (violationsList.value === null) { if (violationsList.value === null) {
return []; 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); .map((v) => v.title);
} }
function hasViolation<P extends ViolationKey>(property: P): boolean { function hasViolation<P extends ViolationKey>(property: P): boolean {
return violationTitles(property).length > 0; return violationTitles(property).length > 0;
} }
function hasViolationWithParameter< function hasViolationWithParameter<
P extends ViolationKey, P extends ViolationKey,
Param extends Extract<keyof T[P], string> Param extends Extract<keyof T[P], string>,
>( >(
property: P, property: P,
with_parameter: Param, with_parameter: Param,
with_parameter_value: T[P][Param], with_parameter_value: T[P][Param],
): boolean { ): 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; violationsList.value = validationException;
} }
@@ -53,5 +67,12 @@ export function useViolationList<T extends Record<string, Record<string, string>
violationsList.value = null; 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, DateTimeWrite,
SetGender, SetGender,
SetCenter, SetCenter,
SetCivility, Gender, SetCivility,
Gender,
} from "ChillMainAssets/types"; } from "ChillMainAssets/types";
import { StoredObject } from "ChillDocStoreAssets/types"; import { StoredObject } from "ChillDocStoreAssets/types";
import { Thirdparty } from "../../../ChillThirdPartyBundle/Resources/public/types"; import { Thirdparty } from "../../../ChillThirdPartyBundle/Resources/public/types";
@@ -157,16 +158,16 @@ export interface AccompanyingPeriod {
} }
export interface AccompanyingPeriodWorkEvaluationDocument { export interface AccompanyingPeriodWorkEvaluationDocument {
id: number; id: number;
type: "accompanying_period_work_evaluation_document"; type: "accompanying_period_work_evaluation_document";
storedObject: StoredObject; storedObject: StoredObject;
title: string; title: string;
createdAt: DateTime | null; createdAt: DateTime | null;
createdBy: User | null; createdBy: User | null;
updatedAt: DateTime | null; updatedAt: DateTime | null;
updatedBy: User | null; updatedBy: User | null;
workflows_availables: WorkflowAvailable[]; workflows_availables: WorkflowAvailable[];
workflows: object[]; workflows: object[];
} }
export interface AccompanyingPeriodWork { export interface AccompanyingPeriodWork {
@@ -195,139 +196,139 @@ export interface AccompanyingPeriodWork {
} }
interface SocialAction { interface SocialAction {
id: number; id: number;
parent?: SocialAction | null; parent?: SocialAction | null;
children: SocialAction[]; children: SocialAction[];
issue?: SocialIssue | null; issue?: SocialIssue | null;
ordering: number; ordering: number;
title: { title: {
fr: string; fr: string;
}; };
text: string; text: string;
defaultNotificationDelay?: string | null; defaultNotificationDelay?: string | null;
desactivationDate?: string | null; desactivationDate?: string | null;
evaluations: Evaluation[]; evaluations: Evaluation[];
goals: Goal[]; goals: Goal[];
results: Result[]; results: Result[];
} }
export interface AccompanyingPeriodResource { export interface AccompanyingPeriodResource {
id: number; id: number;
accompanyingPeriod: AccompanyingPeriod; accompanyingPeriod: AccompanyingPeriod;
comment?: string | null; comment?: string | null;
person?: Person | null; person?: Person | null;
thirdParty?: Thirdparty | null; thirdParty?: Thirdparty | null;
} }
export interface Origin { export interface Origin {
id: number; id: number;
label: { label: {
fr: string; fr: string;
}; };
noActiveAfter: DateTime; noActiveAfter: DateTime;
} }
export interface ClosingMotive { export interface ClosingMotive {
id: number; id: number;
active: boolean; active: boolean;
name: { name: {
fr: string; fr: string;
}; };
ordering: number; ordering: number;
isCanceledAccompanyingPeriod: boolean; isCanceledAccompanyingPeriod: boolean;
parent?: ClosingMotive | null; parent?: ClosingMotive | null;
children: ClosingMotive[]; children: ClosingMotive[];
} }
export interface AccompanyingPeriodParticipation { export interface AccompanyingPeriodParticipation {
id: number; id: number;
startDate: DateTime; startDate: DateTime;
endDate?: DateTime | null; endDate?: DateTime | null;
accompanyingPeriod: AccompanyingPeriod; accompanyingPeriod: AccompanyingPeriod;
person: Person; person: Person;
} }
export interface AccompanyingPeriodLocationHistory { export interface AccompanyingPeriodLocationHistory {
id: number; id: number;
startDate: DateTime; startDate: DateTime;
endDate?: DateTime | null; endDate?: DateTime | null;
addressLocation?: Address | null; addressLocation?: Address | null;
period: AccompanyingPeriod; period: AccompanyingPeriod;
personLocation?: Person | null; personLocation?: Person | null;
} }
export interface SocialIssue { export interface SocialIssue {
id: number; id: number;
text: string; text: string;
parent?: SocialIssue | null; parent?: SocialIssue | null;
children: SocialIssue[]; children: SocialIssue[];
socialActions?: SocialAction[] | null; socialActions?: SocialAction[] | null;
ordering: number; ordering: number;
title: { title: {
fr: string; fr: string;
}; };
desactivationDate?: string | null; desactivationDate?: string | null;
} }
export interface Goal { export interface Goal {
id: number; id: number;
results: Result[]; results: Result[];
socialActions?: SocialAction[] | null; socialActions?: SocialAction[] | null;
title: { title: {
fr: string; fr: string;
}; };
} }
export interface Result { export interface Result {
id: number; id: number;
accompanyingPeriodWorks: AccompanyingPeriodWork[]; accompanyingPeriodWorks: AccompanyingPeriodWork[];
accompanyingPeriodWorkGoals: AccompanyingPeriodWorkGoal[]; accompanyingPeriodWorkGoals: AccompanyingPeriodWorkGoal[];
goals: Goal[]; goals: Goal[];
socialActions: SocialAction[]; socialActions: SocialAction[];
title: { title: {
fr: string; fr: string;
}; };
desactivationDate?: string | null; desactivationDate?: string | null;
} }
export interface AccompanyingPeriodWorkGoal { export interface AccompanyingPeriodWorkGoal {
id: number; id: number;
accompanyingPeriodWork: AccompanyingPeriodWork; accompanyingPeriodWork: AccompanyingPeriodWork;
goal: Goal; goal: Goal;
note: string; note: string;
results: Result[]; results: Result[];
} }
export interface AccompanyingPeriodWorkEvaluation { export interface AccompanyingPeriodWorkEvaluation {
type: 'accompanying_period_work_evaluation'; type: "accompanying_period_work_evaluation";
accompanyingPeriodWork: AccompanyingPeriodWork | null; accompanyingPeriodWork: AccompanyingPeriodWork | null;
comment: string; comment: string;
createdAt: DateTime | null; createdAt: DateTime | null;
createdBy: User | null; createdBy: User | null;
documents: AccompanyingPeriodWorkEvaluationDocument[]; documents: AccompanyingPeriodWorkEvaluationDocument[];
endDate: DateTime | null; endDate: DateTime | null;
evaluation: Evaluation | null; evaluation: Evaluation | null;
id: number | null; id: number | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
key: any; key: any;
maxDate: DateTime | null; maxDate: DateTime | null;
startDate: DateTime | null; startDate: DateTime | null;
updatedAt: DateTime | null; updatedAt: DateTime | null;
updatedBy: User | null; updatedBy: User | null;
warningInterval: string | null; warningInterval: string | null;
timeSpent: number | null; timeSpent: number | null;
} }
export interface Evaluation { export interface Evaluation {
id: number; id: number;
url: string; url: string;
socialActions: SocialAction[]; socialActions: SocialAction[];
title: { title: {
fr: string; fr: string;
}; };
active: boolean; active: boolean;
delay: string; delay: string;
notificationDelay: string; notificationDelay: string;
} }
export interface Step { export interface Step {
@@ -394,15 +395,15 @@ export interface AccompanyingCourse {
} }
export interface AccompanyingPeriodWorkReferrerHistory { export interface AccompanyingPeriodWorkReferrerHistory {
id: number; id: number;
accompanyingPeriodWork: AccompanyingPeriodWork; accompanyingPeriodWork: AccompanyingPeriodWork;
user: User; user: User;
startDate: DateTime; startDate: DateTime;
endDate: DateTime | null; endDate: DateTime | null;
createdAt: DateTime; createdAt: DateTime;
updatedAt: DateTime | null; updatedAt: DateTime | null;
createdBy: User; createdBy: User;
updatedBy: User | null; updatedBy: User | null;
} }
export interface AccompanyingPeriodWorkEvaluationDocument { export interface AccompanyingPeriodWorkEvaluationDocument {
@@ -432,7 +433,7 @@ export type EntityType =
| "user" | "user"
| "household"; | "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 { export function isEntityHousehold(e: Entities): e is Household {
return e.type === "household"; return e.type === "household";
@@ -440,25 +441,34 @@ export function isEntityHousehold(e: Entities): e is Household {
export type EntitiesOrMe = "me" | Entities; export type EntitiesOrMe = "me" | Entities;
// Type guards to discriminate Suggestions by their result kind // 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"; 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"; 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"; 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"; 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"; return (s as any)?.result?.type === "household";
} }
@@ -482,7 +492,7 @@ export interface SearchPagination {
export interface Search { export interface Search {
count: number; count: number;
pagination: SearchPagination; pagination: SearchPagination;
results: {relevance: number, result: Entities}[]; results: { relevance: number; result: Entities }[];
} }
export interface SearchOptions { 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 { export interface PersonIdentifierWorker {
type: "person_identifier_worker"; type: "person_identifier_worker";

View File

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

View File

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

View File

@@ -245,7 +245,7 @@ import Confidential from "ChillMainAssets/vuejs/_components/Confidential.vue";
import { mapState } from "vuex"; import { mapState } from "vuex";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue"; 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 { export default {
name: "Requestor", name: "Requestor",

View File

@@ -59,7 +59,7 @@ import { mapState } from "vuex";
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue"; import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
import ResourceItem from "./Resources/ResourceItem.vue"; import ResourceItem from "./Resources/ResourceItem.vue";
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.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 { export default {
name: "Resources", name: "Resources",

View File

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

View File

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

View File

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

View File

@@ -1,288 +1,205 @@
<template> <template>
<div class="row mb-3"> <div class="row mb-3">
<h5>{{ trans(EVALUATION_DOCUMENTS) }} :</h5> <h5>{{ trans(EVALUATION_DOCUMENTS) }} :</h5>
<div class="flex-table"> <div class="flex-table">
<div <div
class="item-bloc" class="item-bloc"
v-for="(d, i) in documents" v-for="(d, i) in documents"
:key="d.id" :key="d.id"
:class="[ :class="[parseInt(docAnchorId) === d.id ? 'bg-blink' : 'nothing']"
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">
<div :id="'document_' + d.id" class="item-row"> <label class="col-sm-3 col-form-label">Titre du document:</label>
<div class="input-group input-group-lg mb-3 row"> <div class="col-sm-9">
<label class="col-sm-3 col-form-label" <input
>Titre du document:</label class="form-control document-title"
> type="text"
<div class="col-sm-9"> :value="d.title"
<input :id="d.id"
class="form-control document-title" :data-key="i"
type="text" @input="$emit('inputDocumentTitle', $event)"
: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>
</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>
</div>
<AccompanyingPeriodWorkSelectorModal <AccompanyingPeriodWorkSelectorModal
v-if="showAccompanyingPeriodSelector" v-if="showAccompanyingPeriodSelector"
v-model:selectedAcpw="selectedAcpw" v-model:selectedAcpw="selectedAcpw"
:accompanying-period-id="accompanyingPeriodId" :accompanying-period-id="accompanyingPeriodId"
:is-evaluation-selector="true" :is-evaluation-selector="true"
:ignore-accompanying-period-work-ids="[]" :ignore-accompanying-period-work-ids="[]"
@close-modal="showAccompanyingPeriodSelector = false" @close-modal="showAccompanyingPeriodSelector = false"
@update:selectedEvaluation="selectedEvaluation = $event" @update:selectedEvaluation="selectedEvaluation = $event"
/> />
</template> </template>
<script setup> <script setup>
@@ -291,15 +208,15 @@ import ListWorkflowModal from "ChillMainAssets/vuejs/_components/EntityWorkflow/
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue"; import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
import DropFileModal from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileModal.vue"; import DropFileModal from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileModal.vue";
import { import {
EVALUATION_NOTIFICATION_NOTIFY_REFERRER, EVALUATION_NOTIFICATION_NOTIFY_REFERRER,
EVALUATION_NOTIFICATION_NOTIFY_ANY, EVALUATION_NOTIFICATION_NOTIFY_ANY,
EVALUATION_NOTIFICATION_SEND, EVALUATION_NOTIFICATION_SEND,
EVALUATION_DOCUMENTS, EVALUATION_DOCUMENTS,
EVALUATION_DOCUMENT_MOVE, EVALUATION_DOCUMENT_MOVE,
EVALUATION_DOCUMENT_DELETE, EVALUATION_DOCUMENT_DELETE,
EVALUATION_DOCUMENT_DUPLICATE_HERE, EVALUATION_DOCUMENT_DUPLICATE_HERE,
EVALUATION_DOCUMENT_DUPLICATE_TO_OTHER_EVALUATION, EVALUATION_DOCUMENT_DUPLICATE_TO_OTHER_EVALUATION,
trans, trans,
} from "translator"; } from "translator";
import { computed, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import AccompanyingPeriodWorkSelectorModal from "ChillPersonAssets/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue"; import AccompanyingPeriodWorkSelectorModal from "ChillPersonAssets/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue";
@@ -308,17 +225,17 @@ import { buildLinkCreate as buildLinkCreateNotification } from "ChillMainAssets/
import { useStore } from "vuex"; import { useStore } from "vuex";
const props = defineProps([ const props = defineProps([
"documents", "documents",
"docAnchorId", "docAnchorId",
"accompanyingPeriodId", "accompanyingPeriodId",
"evaluation", "evaluation",
]); ]);
const emit = defineEmits([ const emit = defineEmits([
"inputDocumentTitle", "inputDocumentTitle",
"removeDocument", "removeDocument",
"duplicateDocument", "duplicateDocument",
"statusDocumentChanged", "statusDocumentChanged",
"duplicateDocumentToWork", "duplicateDocumentToWork",
]); ]);
const store = useStore(); const store = useStore();
@@ -329,67 +246,67 @@ const selectedDocumentToDuplicate = ref(null);
const selectedDocumentToMove = ref(null); const selectedDocumentToMove = ref(null);
const AmIRefferer = computed(() => { const AmIRefferer = computed(() => {
return !( return !(
store.state.work.accompanyingPeriod.user && store.state.work.accompanyingPeriod.user &&
store.state.me && store.state.me &&
store.state.work.accompanyingPeriod.user.id !== store.state.me.id store.state.work.accompanyingPeriod.user.id !== store.state.me.id
); );
}); });
const prepareDocumentDuplicationToWork = (d) => { const prepareDocumentDuplicationToWork = (d) => {
selectedDocumentToDuplicate.value = d; selectedDocumentToDuplicate.value = d;
/** ensure selectedDocumentToMove is null */ /** ensure selectedDocumentToMove is null */
selectedDocumentToMove.value = null; selectedDocumentToMove.value = null;
showAccompanyingPeriodSelector.value = true; showAccompanyingPeriodSelector.value = true;
}; };
const prepareDocumentMoveToWork = (d) => { const prepareDocumentMoveToWork = (d) => {
selectedDocumentToMove.value = d; selectedDocumentToMove.value = d;
/** ensure selectedDocumentToDuplicate is null */ /** ensure selectedDocumentToDuplicate is null */
selectedDocumentToDuplicate.value = null; selectedDocumentToDuplicate.value = null;
showAccompanyingPeriodSelector.value = true; showAccompanyingPeriodSelector.value = true;
}; };
watch(selectedEvaluation, (val) => { watch(selectedEvaluation, (val) => {
if (selectedDocumentToDuplicate.value) { if (selectedDocumentToDuplicate.value) {
emit("duplicateDocumentToEvaluation", { emit("duplicateDocumentToEvaluation", {
evaluation: val, evaluation: val,
document: selectedDocumentToDuplicate.value, document: selectedDocumentToDuplicate.value,
}); });
} else { } else {
emit("moveDocumentToEvaluation", { emit("moveDocumentToEvaluation", {
evaluationDest: val, evaluationDest: val,
document: selectedDocumentToMove.value, document: selectedDocumentToMove.value,
}); });
} }
}); });
async function goToGenerateWorkflowEvaluationDocument({ async function goToGenerateWorkflowEvaluationDocument({
workflowName, workflowName,
payload, payload,
}) { }) {
const callback = (data) => { const callback = (data) => {
let evaluation = data.accompanyingPeriodWorkEvaluations.find( let evaluation = data.accompanyingPeriodWorkEvaluations.find(
(e) => e.key === props.evaluation.key, (e) => e.key === props.evaluation.key,
); );
let updatedDocument = evaluation.documents.find( let updatedDocument = evaluation.documents.find(
(d) => d.key === payload.doc.key, (d) => d.key === payload.doc.key,
); );
window.location.assign( window.location.assign(
buildLinkCreate( buildLinkCreate(
workflowName, workflowName,
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument", "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
updatedDocument.id, updatedDocument.id,
), ),
); );
}; };
return store.dispatch("submit", callback).catch((e) => { return store.dispatch("submit", callback).catch((e) => {
console.log(e); console.log(e);
throw e; throw e;
}); });
} }
/** /**
@@ -401,55 +318,53 @@ async function goToGenerateWorkflowEvaluationDocument({
* @return {void} * @return {void}
*/ */
async function replaceDocument(oldDocument, storedObject, storedObjectVersion) { async function replaceDocument(oldDocument, storedObject, storedObjectVersion) {
let document = { let document = {
type: "accompanying_period_work_evaluation_document", type: "accompanying_period_work_evaluation_document",
storedObject: storedObject, storedObject: storedObject,
title: oldDocument.title, title: oldDocument.title,
}; };
return store.commit("replaceDocument", { return store.commit("replaceDocument", {
key: props.evaluation.key, key: props.evaluation.key,
document, document,
oldDocument: oldDocument, oldDocument: oldDocument,
stored_object_version: storedObjectVersion, stored_object_version: storedObjectVersion,
}); });
} }
async function goToGenerateDocumentNotification(document, tos) { async function goToGenerateDocumentNotification(document, tos) {
const callback = (data) => { const callback = (data) => {
let evaluation = data.accompanyingPeriodWorkEvaluations.find( let evaluation = data.accompanyingPeriodWorkEvaluations.find(
(e) => e.key === props.evaluation.key, (e) => e.key === props.evaluation.key,
); );
let updatedDocument = evaluation.documents.find( let updatedDocument = evaluation.documents.find(
(d) => d.key === document.key, (d) => d.key === document.key,
); );
window.location.assign( window.location.assign(
buildLinkCreateNotification( buildLinkCreateNotification(
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument", "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
updatedDocument.id, updatedDocument.id,
tos === true tos === true ? store.state.work.accompanyingPeriod.user?.id : null,
? store.state.work.accompanyingPeriod.user?.id window.location.pathname +
: null, window.location.search +
window.location.pathname + window.location.hash,
window.location.search + ),
window.location.hash, );
), };
);
};
return store.dispatch("submit", callback).catch((e) => { return store.dispatch("submit", callback).catch((e) => {
console.log(e); console.log(e);
throw e; throw e;
}); });
} }
async function submitBeforeLeaveToEditor() { async function submitBeforeLeaveToEditor() {
console.log("submit beore edit 2"); console.log("submit beore edit 2");
// empty callback // empty callback
const callback = () => null; const callback = () => null;
return store.dispatch("submit", callback).catch((e) => { return store.dispatch("submit", callback).catch((e) => {
console.log(e); console.log(e);
throw e; throw e;
}); });
} }
</script> </script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,64 +1,63 @@
const personMessages = { const personMessages = {
fr: { fr: {
add_persons: { add_persons: {
title: "Ajouter des usagers", title: "Ajouter des usagers",
suggested_counter: suggested_counter: "Pas de résultats | 1 résultat | {count} résultats",
"Pas de résultats | 1 résultat | {count} résultats", selected_counter: " 1 sélectionné | {count} sélectionnés",
selected_counter: " 1 sélectionné | {count} sélectionnés", search_some_persons: "Rechercher des personnes..",
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 !",
}, },
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 }; export { personMessages };

View File

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

View File

@@ -1,10 +1,16 @@
/* /*
* GET a thirdparty by id * GET a thirdparty by id
*/ */
import {isThirdpartyChild, isThirdpartyCompany, isThirdpartyContact, Thirdparty, ThirdPartyWrite} from '../../types'; import {
import {makeFetch} from "ChillMainAssets/lib/api/apiMethods"; 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`; const url = `/api/1.0/thirdparty/thirdparty/${id}.json`;
return fetch(url).then((response) => { return fetch(url).then((response) => {
if (response.ok) { if (response.ok) {
@@ -21,40 +27,41 @@ export const thirdpartyToWriteThirdParty = (t: Thirdparty): ThirdPartyWrite => {
const isChild = isThirdpartyChild(t); const isChild = isThirdpartyChild(t);
return { return {
type: 'thirdparty', type: "thirdparty",
kind: t.kind, kind: t.kind,
civility: civility:
(isContact || isChild) && t.civility (isContact || isChild) && t.civility
? { type: 'chill_main_civility', id: t.civility.id } ? { type: "chill_main_civility", id: t.civility.id }
: null, : null,
profession: (isContact || isChild) ? (t.profession ?? '') : '', profession: isContact || isChild ? (t.profession ?? "") : "",
firstname: isCompany ? '' : (t.firstname ?? ''), firstname: isCompany ? "" : (t.firstname ?? ""),
name: isCompany name: isCompany ? (t.nameCompany ?? "") : (t.name ?? ""),
? (t.nameCompany ?? '') email: t.email ?? "",
: (t.name ?? ''), telephone: t.telephone ?? "",
email: t.email ?? '', telephone2: t.telephone2 ?? "",
telephone: t.telephone ?? '',
telephone2: t.telephone2 ?? '',
address: null, address: null,
comment: isChild ? (t.comment ?? '') : '', comment: isChild ? (t.comment ?? "") : "",
parent: isChild && t.parent ? { type: 'thirdparty', id: t.parent.id } : null, parent:
isChild && t.parent ? { type: "thirdparty", id: t.parent.id } : null,
}; };
}; };
export interface WriteThirdPartyViolationMap export interface WriteThirdPartyViolationMap extends Record<
extends Record<string, Record<string, string>> { string,
Record<string, string>
> {
email: { email: {
"{{ value }}": string; "{{ value }}": string;
}, };
name: { name: {
"{{ value }}": string; "{{ value }}": string;
}, };
telephone: { telephone: {
"{{ value }}": string; "{{ value }}": string;
} };
telephone2: { telephone2: {
"{{ value }}": string; "{{ value }}": string;
} };
} }
/* /*
@@ -63,13 +70,16 @@ extends Record<string, Record<string, string>> {
export const createThirdParty = async (body: ThirdPartyWrite) => { export const createThirdParty = async (body: ThirdPartyWrite) => {
const url = `/api/1.0/thirdparty/thirdparty.json`; 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 * 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`; 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>
<li v-if="isThirdpartyChild(props.thirdparty)"> <li v-if="isThirdpartyChild(props.thirdparty)">
<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> <b class="me-2">{{ trans(THIRDPARTY_MESSAGES_CHILD_OF) }}</b>
<on-the-fly <on-the-fly
:type="props.thirdparty.parent.type" :type="props.thirdparty.parent.type"
:id="props.thirdparty.parent.id" :id="props.thirdparty.parent.id"
@@ -133,13 +133,14 @@ import { computed, defineAsyncComponent } from "vue";
import AddressRenderBox from "ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue"; import AddressRenderBox from "ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue";
import Confidential from "ChillMainAssets/vuejs/_components/Confidential.vue"; import Confidential from "ChillMainAssets/vuejs/_components/Confidential.vue";
import BadgeEntity from "ChillMainAssets/vuejs/_components/BadgeEntity.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 { import {
THIRDPARTY_MESSAGES_CHILD_OF, isThirdpartyChild,
trans isThirdpartyCompany,
} from "translator"; 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 // Async to avoid recursive resolution issues
/* /*
@@ -160,7 +161,9 @@ interface RenderOptions {
const props = defineProps<{ thirdparty: Thirdparty; options: 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>(() => { const hasParent = computed<boolean>(() => {
return isThirdpartyChild(props.thirdparty); return isThirdpartyChild(props.thirdparty);

View File

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

View File

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

View File

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