Merge branch '71-task-feature-and-bug-by-status-for-boris' into 'ticket-app-master'

Misc: homepage widget with tickets, and improvements in ticket list

See merge request Chill-Projet/chill-bundles!879
This commit is contained in:
2025-09-16 11:16:57 +00:00
33 changed files with 1200 additions and 838 deletions

View File

@@ -1,15 +0,0 @@
import { createApp } from "vue";
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
import { appMessages } from "ChillMainAssets/vuejs/HomepageWidget/js/i18n";
import { store } from "ChillMainAssets/vuejs/HomepageWidget/js/store";
import App from "ChillMainAssets/vuejs/HomepageWidget/App";
const i18n = _createI18n(appMessages);
const app = createApp({
template: `<app></app>`,
})
.use(store)
.use(i18n)
.component("app", App)
.mount("#homepage_widget");

View File

@@ -0,0 +1,6 @@
import App from "ChillMainAssets/vuejs/HomepageWidget/App.vue";
import { createApp } from "vue";
import { store } from "ChillMainAssets/vuejs/HomepageWidget/store";
const _app = createApp(App);
_app.use(store).mount("#homepage_widget");

View File

@@ -1,5 +1,5 @@
<template> <template>
<h2>{{ $t("main_title") }}</h2> <h2>{{ trans(MAIN_TITLE) }}</h2>
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="nav-item"> <li class="nav-item">
@@ -11,14 +11,24 @@
<i class="fa fa-dashboard" /> <i class="fa fa-dashboard" />
</a> </a>
</li> </li>
<li class="nav-item">
<a
class="nav-link"
:class="{ active: activeTab === 'MyTickets' }"
@click="selectTab('MyTickets')"
>
{{ trans(MY_TICKETS_TAB) }}
<tab-counter :count="ticketListState.value?.count || 0" />
</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a <a
class="nav-link" class="nav-link"
:class="{ active: activeTab === 'MyNotifications' }" :class="{ active: activeTab === 'MyNotifications' }"
@click="selectTab('MyNotifications')" @click="selectTab('MyNotifications')"
> >
{{ $t("my_notifications.tab") }} {{ trans(MY_NOTIFICATIONS_TAB) }}
<tab-counter :count="state.notifications.count" /> <tab-counter :count="state.value?.notifications?.count || 0" />
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
@@ -27,25 +37,17 @@
:class="{ active: activeTab === 'MyAccompanyingCourses' }" :class="{ active: activeTab === 'MyAccompanyingCourses' }"
@click="selectTab('MyAccompanyingCourses')" @click="selectTab('MyAccompanyingCourses')"
> >
{{ $t("my_accompanying_courses.tab") }} {{ trans(MY_ACCOMPANYING_COURSES_TAB) }}
</a> </a>
</li> </li>
<!-- <li class="nav-item">
<a class="nav-link"
:class="{'active': activeTab === 'MyWorks'}"
@click="selectTab('MyWorks')">
{{ $t('my_works.tab') }}
<tab-counter :count="state.works.count"></tab-counter>
</a>
</li> -->
<li class="nav-item"> <li class="nav-item">
<a <a
class="nav-link" class="nav-link"
:class="{ active: activeTab === 'MyEvaluations' }" :class="{ active: activeTab === 'MyEvaluations' }"
@click="selectTab('MyEvaluations')" @click="selectTab('MyEvaluations')"
> >
{{ $t("my_evaluations.tab") }} {{ trans(MY_EVALUATIONS_TAB) }}
<tab-counter :count="state.evaluations.count" /> <tab-counter :count="state.value?.evaluations?.count || 0" />
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
@@ -54,9 +56,12 @@
:class="{ active: activeTab === 'MyTasks' }" :class="{ active: activeTab === 'MyTasks' }"
@click="selectTab('MyTasks')" @click="selectTab('MyTasks')"
> >
{{ $t("my_tasks.tab") }} {{ trans(MY_TASKS_TAB) }}
<tab-counter <tab-counter
:count="state.tasks.warning.count + state.tasks.alert.count" :count="
(state.value?.tasks?.warning?.count || 0) +
(state.value?.tasks?.alert?.count || 0)
"
/> />
</a> </a>
</li> </li>
@@ -66,19 +71,25 @@
:class="{ active: activeTab === 'MyWorkflows' }" :class="{ active: activeTab === 'MyWorkflows' }"
@click="selectTab('MyWorkflows')" @click="selectTab('MyWorkflows')"
> >
{{ $t("my_workflows.tab") }} {{ trans(MY_WORKFLOWS_TAB) }}
<tab-counter :count="state.workflows.count + state.workflowsCc.count" /> <tab-counter
:count="
(state.value?.workflows?.count || 0) +
(state.value?.workflowsCc?.count || 0)
"
/>
</a> </a>
</li> </li>
<li class="nav-item loading ms-auto py-2" v-if="loading"> <li class="nav-item loading ms-auto py-2" v-if="loading">
<i <i
class="fa fa-circle-o-notch fa-spin fa-lg text-chill-gray" class="fa fa-circle-o-notch fa-spin fa-lg text-chill-gray"
:title="$t('loading')" :title="trans(LOADING)"
/> />
</li> </li>
</ul> </ul>
<div class="my-4"> <div class="my-4">
<my-tickets v-if="activeTab == 'MyTickets'" />
<my-customs v-if="activeTab === 'MyCustoms'" /> <my-customs v-if="activeTab === 'MyCustoms'" />
<my-works v-else-if="activeTab === 'MyWorks'" /> <my-works v-else-if="activeTab === 'MyWorks'" />
<my-evaluations v-else-if="activeTab === 'MyEvaluations'" /> <my-evaluations v-else-if="activeTab === 'MyEvaluations'" />
@@ -91,63 +102,58 @@
</div> </div>
</template> </template>
<script> <script lang="ts" setup>
import MyCustoms from "./MyCustoms"; import { ref, computed, onMounted } from "vue";
import MyWorks from "./MyWorks"; import { useStore } from "vuex";
import MyEvaluations from "./MyEvaluations"; import MyCustoms from "./MyCustoms.vue";
import MyTasks from "./MyTasks"; import MyWorks from "./MyWorks.vue";
import MyAccompanyingCourses from "./MyAccompanyingCourses"; import MyEvaluations from "./MyEvaluations.vue";
import MyNotifications from "./MyNotifications"; import MyTasks from "./MyTasks.vue";
import MyAccompanyingCourses from "./MyAccompanyingCourses.vue";
import MyNotifications from "./MyNotifications.vue";
import MyWorkflows from "./MyWorkflows.vue"; import MyWorkflows from "./MyWorkflows.vue";
import TabCounter from "./TabCounter"; import MyTickets from "./MyTickets.vue";
import { mapState } from "vuex"; import TabCounter from "./TabCounter.vue";
import {
MAIN_TITLE,
MY_TICKETS_TAB,
MY_EVALUATIONS_TAB,
MY_TASKS_TAB,
MY_ACCOMPANYING_COURSES_TAB,
MY_NOTIFICATIONS_TAB,
MY_WORKFLOWS_TAB,
LOADING,
trans,
} from "translator";
const store = useStore();
export default { const activeTab = ref("MyCustoms");
name: "App",
components: { const loading = computed(() => store.state.loading);
MyCustoms,
MyWorks, const state = computed(() => store.state.homepage);
MyEvaluations, const ticketListState = computed(() => store.state.ticketList);
MyTasks,
MyWorkflows, function selectTab(tab: string) {
MyAccompanyingCourses, if (tab !== "MyCustoms") {
MyNotifications, store.dispatch("getByTab", { tab: tab });
TabCounter, }
}, activeTab.value = tab;
data() { }
return {
activeTab: "MyCustoms", onMounted(() => {
}; for (const m of [
}, "MyTickets",
computed: { "MyNotifications",
...mapState(["loading"]), "MyAccompanyingCourses",
// just to see all in devtool : // 'MyWorks',
...mapState({ "MyEvaluations",
state: (state) => state, "MyTasks",
}), "MyWorkflows",
}, ]) {
methods: { store.dispatch("getByTab", { tab: m, param: "countOnly=1" });
selectTab(tab) { }
if (tab !== "MyCustoms") { });
this.$store.dispatch("getByTab", { tab: tab });
}
this.activeTab = tab;
console.log(this.activeTab);
},
},
mounted() {
for (const m of [
"MyNotifications",
"MyAccompanyingCourses",
// 'MyWorks',
"MyEvaluations",
"MyTasks",
"MyWorkflows",
]) {
this.$store.dispatch("getByTab", { tab: m, param: "countOnly=1" });
}
},
};
</script> </script>
<style scoped> <style scoped>

View File

@@ -1,35 +1,35 @@
<template> <template>
<div class="alert alert-light"> <div class="alert alert-light">
{{ $t("my_accompanying_courses.description") }} {{ trans(MY_ACCOMPANYING_COURSES_DESCRIPTION) }}
</div> </div>
<span v-if="noResults" class="chill-no-data-statement">{{ <span v-if="noResults" class="chill-no-data-statement">
$t("no_data") {{ trans(NO_DATA) }}
}}</span> </span>
<tab-table v-else> <tab-table v-else>
<template #thead> <template #thead>
<th scope="col"> <th scope="col">
{{ $t("opening_date") }} {{ trans(OPENING_DATE) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("social_issues") }} {{ trans(SOCIAL_ISSUES) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("concerned_persons") }} {{ trans(CONCERNED_PERSONS) }}
</th> </th>
<th scope="col" /> <th scope="col" />
<th scope="col" /> <th scope="col" />
</template> </template>
<template #tbody> <template #tbody>
<tr v-for="(c, i) in accompanyingCourses.results" :key="`course-${i}`"> <tr v-for="(c, i) in accompanyingCourses.results" :key="`course-${i}`">
<td>{{ $d(c.openingDate.datetime, "short") }}</td> <td>{{ $d(new Date(c.openingDate.datetime), "short") }}</td>
<td> <td>
<span <span
v-for="(i, index) in c.socialIssues" v-for="(issue, index) in c.socialIssues"
:key="index" :key="index"
class="chill-entity entity-social-issue" class="chill-entity entity-social-issue"
> >
<span class="badge bg-chill-l-gray text-dark"> <span class="badge bg-chill-l-gray text-dark">
{{ localizeString(i.title) }} {{ localizeString(issue.title) }}
</span> </span>
</span> </span>
</td> </td>
@@ -46,15 +46,15 @@
</td> </td>
<td> <td>
<span v-if="c.emergency" class="badge rounded-pill bg-danger me-1">{{ <span v-if="c.emergency" class="badge rounded-pill bg-danger me-1">{{
$t("emergency") trans(EMERGENCY)
}}</span> }}</span>
<span v-if="c.confidential" class="badge rounded-pill bg-danger">{{ <span v-if="c.confidential" class="badge rounded-pill bg-danger">{{
$t("confidential") trans(CONFIDENTIAL)
}}</span> }}</span>
</td> </td>
<td> <td>
<a class="btn btn-sm btn-show" :href="getUrl(c)"> <a class="btn btn-sm btn-show" :href="getUrl(c)">
{{ $t("show_entity", { entity: $t("the_course") }) }} {{ trans(SHOW_ENTITY, { entity: trans(THE_COURSE) }) }}
</a> </a>
</td> </td>
</tr> </tr>
@@ -62,36 +62,45 @@
</tab-table> </tab-table>
</template> </template>
<script> <script lang="ts" setup>
import { mapState, mapGetters } from "vuex"; import { computed, ComputedRef } from "vue";
import TabTable from "./TabTable"; import { useStore } from "vuex";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly"; import TabTable from "./TabTable.vue";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper"; import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import { AccompanyingCourse } from "ChillPersonAssets/types";
import { PaginationResponse } from "ChillMainAssets/lib/api/apiMethods";
import {
MY_ACCOMPANYING_COURSES_DESCRIPTION,
OPENING_DATE,
SOCIAL_ISSUES,
CONCERNED_PERSONS,
SHOW_ENTITY,
THE_COURSE,
NO_DATA,
EMERGENCY,
CONFIDENTIAL,
trans,
} from "translator";
const store = useStore();
export default { const accompanyingCourses: ComputedRef<PaginationResponse<AccompanyingCourse>> =
name: "MyAccompanyingCourses", computed(() => store.state.homepage.accompanyingCourses);
components: { const isAccompanyingCoursesLoaded = computed(
TabTable, () => store.getters.isAccompanyingCoursesLoaded,
OnTheFly, );
},
computed: { const noResults = computed(() => {
...mapState(["accompanyingCourses"]), if (!isAccompanyingCoursesLoaded.value) {
...mapGetters(["isAccompanyingCoursesLoaded"]), return false;
noResults() { } else {
if (!this.isAccompanyingCoursesLoaded) { return accompanyingCourses.value.count === 0;
return false; }
} else { });
return this.accompanyingCourses.count === 0;
} function getUrl(c: { id: number }): string {
}, return `/fr/parcours/${c.id}`;
}, }
methods: {
localizeString,
getUrl(c) {
return `/fr/parcours/${c.id}`;
},
},
};
</script> </script>
<style scoped> <style scoped>

View File

@@ -1,93 +1,72 @@
<template> <template>
<span v-if="noResults" class="chill-no-data-statement">{{ <span v-if="noResults" class="chill-no-data-statement">
$t("no_dashboard") {{ trans(NO_DASHBOARD) }}
}}</span> </span>
<div v-else id="dashboards" class="container g-3"> <div v-else id="dashboards" class="container g-3">
<div class="row"> <div class="row">
<div class="mbloc col-xs-12 col-sm-4"> <div class="mbloc col-xs-12 col-sm-4">
<div class="custom1"> <div class="custom1">
<ul class="list-unstyled"> <ul class="list-unstyled">
<li v-if="counter.notifications > 0"> <li v-if="(counter.value?.notifications || 0) > 0">
<i18n-t <span :class="counterClass">
keypath="counter.unread_notifications" {{
tag="span" trans(COUNTER_UNREAD_NOTIFICATIONS, {
:class="counterClass" n: counter.value?.notifications || 0,
:plural="counter.notifications" })
> }}
<template #n> </span>
<span>{{ counter.notifications }}</span>
</template>
</i18n-t>
</li> </li>
<li v-if="counter.accompanyingCourses > 0"> <li v-if="(counter.value?.accompanyingCourses || 0) > 0">
<i18n-t <span :class="counterClass">
keypath="counter.assignated_courses" {{
tag="span" trans(COUNTER_ASSIGNATED_COURSES, {
:class="counterClass" n: counter.value?.accompanyingCourses || 0,
:plural="counter.accompanyingCourses" })
> }}
<template #n> </span>
<span>{{ counter.accompanyingCourses }}</span>
</template>
</i18n-t>
</li> </li>
<li v-if="counter.works > 0"> <li v-if="(counter.value?.works || 0) > 0">
<i18n-t <span :class="counterClass">
keypath="counter.assignated_actions" {{
tag="span" trans(COUNTER_ASSIGNATED_ACTIONS, {
:class="counterClass" n: counter.value?.works || 0,
:plural="counter.works" })
> }}
<template #n> </span>
<span>{{ counter.works }}</span>
</template>
</i18n-t>
</li> </li>
<li v-if="counter.evaluations > 0"> <li v-if="(counter.value?.evaluations || 0) > 0">
<i18n-t <span :class="counterClass">
keypath="counter.assignated_evaluations" {{
tag="span" trans(COUNTER_ASSIGNATED_EVALUATIONS, {
:class="counterClass" n: counter.value?.evaluations || 0,
:plural="counter.evaluations" })
> }}
<template #n> </span>
<span>{{ counter.evaluations }}</span>
</template>
</i18n-t>
</li> </li>
<li v-if="counter.tasksAlert > 0"> <li v-if="(counter.value?.tasksAlert || 0) > 0">
<i18n-t <span :class="counterClass">
keypath="counter.alert_tasks" {{
tag="span" trans(COUNTER_ALERT_TASKS, {
:class="counterClass" n: counter.value?.tasksAlert || 0,
:plural="counter.tasksAlert" })
> }}
<template #n> </span>
<span>{{ counter.tasksAlert }}</span>
</template>
</i18n-t>
</li> </li>
<li v-if="counter.tasksWarning > 0"> <li v-if="(counter.value?.tasksWarning || 0) > 0">
<i18n-t <span :class="counterClass">
keypath="counter.warning_tasks" {{
tag="span" trans(COUNTER_WARNING_TASKS, {
:class="counterClass" n: counter.value?.tasksWarning || 0,
:plural="counter.tasksWarning" })
> }}
<template #n> </span>
<span>{{ counter.tasksWarning }}</span>
</template>
</i18n-t>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<template v-if="this.hasDashboardItems"> <template v-if="hasDashboardItems">
<template <template v-for="(dashboardItem, index) in dashboardItems" :key="index">
v-for="(dashboardItem, index) in this.dashboardItems"
:key="index"
>
<div <div
class="mbloc col-xs-12 col-sm-8 news" class="mbloc col-xs-12 col-sm-8 news"
v-if="dashboardItem.type === 'news'" v-if="dashboardItem.type === 'news'"
@@ -100,44 +79,53 @@
</div> </div>
</template> </template>
<script> <script lang="ts" setup>
import { mapGetters } from "vuex"; import { ref, computed, onMounted } from "vue";
import { useStore } from "vuex";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import News from "./DashboardWidgets/News.vue"; import News from "./DashboardWidgets/News.vue";
import {
NO_DASHBOARD,
COUNTER_UNREAD_NOTIFICATIONS,
COUNTER_ASSIGNATED_COURSES,
COUNTER_ASSIGNATED_ACTIONS,
COUNTER_ASSIGNATED_EVALUATIONS,
COUNTER_ALERT_TASKS,
COUNTER_WARNING_TASKS,
trans,
} from "translator";
export default { interface MyCustom {
name: "MyCustoms", id: number;
components: { type: string;
News, metadata: Record<string, unknown>;
}, userId: number;
data() { position: string;
return { }
counterClass: {
counter: true, //hack to pass class 'counter' in i18n-t const store = useStore();
},
dashboardItems: [], const counter = computed(() => store.getters.counter);
masonry: null,
}; const counterClass = { counter: true };
},
computed: { const dashboardItems = ref<MyCustom[]>([]);
...mapGetters(["counter"]),
noResults() { const noResults = computed(() => false);
return false;
}, const hasDashboardItems = computed(() => dashboardItems.value.length > 0);
hasDashboardItems() {
return this.dashboardItems.length > 0; onMounted(async () => {
}, try {
}, const response: MyCustom[] = await makeFetch(
mounted() { "GET",
makeFetch("GET", "/api/1.0/main/dashboard-config-item.json") "/api/1.0/main/dashboard-config-item.json",
.then((response) => { );
this.dashboardItems = response; dashboardItems.value = response;
}) } catch (error) {
.catch((error) => { throw error;
throw error; }
}); });
},
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -1,44 +1,50 @@
<template> <template>
<div class="accompanying-course-work"> <div class="accompanying-course-work">
<div class="alert alert-light"> <div class="alert alert-light">
{{ $t("my_evaluations.description") }} {{ trans(MY_EVALUATIONS_DESCRIPTION) }}
</div> </div>
<span v-if="noResults" class="chill-no-data-statement">{{ <span v-if="noResults" class="chill-no-data-statement">
$t("no_data") {{ trans(NO_DATA) }}
}}</span> </span>
<tab-table v-else> <tab-table v-else>
<template #thead> <template #thead>
<th scope="col"> <th scope="col">
{{ $t("max_date") }} {{ trans(MAX_DATE) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("evaluation") }} {{ trans(EVALUATION) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("SocialAction") }} {{ trans(SOCIAL_ACTION) }}
</th> </th>
<th scope="col" /> <th scope="col" />
</template> </template>
<template #tbody> <template #tbody>
<tr v-for="(e, i) in evaluations.results" :key="`evaluation-${i}`"> <tr v-for="(e, i) in evaluations.results" :key="`evaluation-${i}`">
<td>{{ $d(e.maxDate.datetime, "short") }}</td>
<td> <td>
{{ localizeString(e.evaluation.title) }} {{
e.maxDate?.datetime
? $d(new Date(e.maxDate.datetime), "short")
: ""
}}
</td>
<td>
{{ localizeString(e.evaluation?.title ?? null) }}
</td> </td>
<td> <td>
<span class="chill-entity entity-social-issue"> <span class="chill-entity entity-social-issue">
<span class="badge bg-chill-l-gray text-dark"> <span class="badge bg-chill-l-gray text-dark">
{{ e.accompanyingPeriodWork.socialAction.issue.text }} {{ e.accompanyingPeriodWork?.socialAction?.issue?.text ?? "" }}
</span> </span>
</span> </span>
<h4 class="badge-title"> <h4 class="badge-title">
<span class="title_label" /> <span class="title_label" />
<span class="title_action"> <span class="title_action">
{{ e.accompanyingPeriodWork.socialAction.text }} {{ e.accompanyingPeriodWork?.socialAction?.text ?? "" }}
</span> </span>
</h4> </h4>
<span <span
v-for="person in e.accompanyingPeriodWork.persons" v-for="person in e.accompanyingPeriodWork?.persons ?? []"
class="me-1" class="me-1"
:key="person.id" :key="person.id"
> >
@@ -55,18 +61,22 @@
<div class="btn-group-vertical" role="group" aria-label="Actions"> <div class="btn-group-vertical" role="group" aria-label="Actions">
<a class="btn btn-sm btn-show" :href="getUrl(e)"> <a class="btn btn-sm btn-show" :href="getUrl(e)">
{{ {{
$t("show_entity", { trans(SHOW_ENTITY, {
entity: $t("the_evaluation"), entity: trans(THE_EVALUATION),
}) })
}} }}
</a> </a>
<a <a
class="btn btn-sm btn-show" class="btn btn-sm btn-show"
:href="getUrl(e.accompanyingPeriodWork.accompanyingPeriod)" :href="getUrl(e.accompanyingPeriodWork.accompanyingPeriod!)"
v-if="
e.accompanyingPeriodWork &&
e.accompanyingPeriodWork.accompanyingPeriod
"
> >
{{ {{
$t("show_entity", { trans(SHOW_ENTITY, {
entity: $t("the_course"), entity: trans(THE_COURSE),
}) })
}} }}
</a> </a>
@@ -78,46 +88,70 @@
</div> </div>
</template> </template>
<script> <script lang="ts" setup>
import { mapState, mapGetters } from "vuex"; import { computed } from "vue";
import TabTable from "./TabTable"; import { useStore } from "vuex";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly"; import TabTable from "./TabTable.vue";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
import { localizeString } from "../../lib/localizationHelper/localizationHelper"; import { localizeString } from "../../lib/localizationHelper/localizationHelper";
export default { const store = useStore();
name: "MyEvaluations",
components: { import type { ComputedRef } from "vue";
TabTable, import {
OnTheFly, AccompanyingPeriod,
}, AccompanyingPeriodWorkEvaluation,
computed: { AccompanyingPeriodWork,
...mapState(["evaluations"]), } from "ChillPersonAssets/types";
...mapGetters(["isEvaluationsLoaded"]), import { PaginationResponse } from "ChillMainAssets/lib/api/apiMethods";
noResults() { import {
if (!this.isEvaluationsLoaded) { MY_EVALUATIONS_DESCRIPTION,
return false; MAX_DATE,
} else { EVALUATION,
return this.evaluations.count === 0; SHOW_ENTITY,
} THE_COURSE,
}, THE_EVALUATION,
}, SOCIAL_ACTION,
methods: { NO_DATA,
localizeString, trans,
getUrl(e) { } from "translator";
switch (e.type) { const evaluations: ComputedRef<
case "accompanying_period_work_evaluation": PaginationResponse<AccompanyingPeriodWorkEvaluation>
let anchor = "#evaluations"; > = computed(() => store.state.homepage.evaluations);
return `/fr/person/accompanying-period/work/${e.accompanyingPeriodWork.id}/edit${anchor}`; const isEvaluationsLoaded = computed(() => store.getters.isEvaluationsLoaded);
case "accompanying_period_work":
return `/fr/person/accompanying-period/work/${e.id}/edit`; const noResults = computed(() => {
case "accompanying_period": if (!isEvaluationsLoaded.value) {
return `/fr/parcours/${e.id}`; return false;
default: } else {
throw "entity type unknown"; return evaluations.value.count === 0;
} }
}, });
},
}; function getUrl(
e:
| AccompanyingPeriodWorkEvaluation
| AccompanyingPeriod
| AccompanyingPeriodWork,
): string {
if (!e) {
throw "entity is undefined";
}
if ("type" in e && typeof e.type === "string") {
switch (e.type) {
case "accompanying_period_work_evaluation":
return `/fr/person/accompanying-period/work/${e.accompanyingPeriodWork?.id}/edit#evaluations`;
case "accompanying_period_work":
return `/fr/person/accompanying-period/work/${e.id}/edit`;
case "accompanying_period":
return `/fr/parcours/${e.id}`;
default:
throw "entity type unknown";
}
}
return "";
}
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -1,26 +1,26 @@
<template> <template>
<div class="alert alert-light"> <div class="alert alert-light">
{{ $t("my_notifications.description") }} {{ trans(MY_NOTIFICATIONS_DESCRIPTION) }}
</div> </div>
<span v-if="noResults" class="chill-no-data-statement">{{ <span v-if="noResults" class="chill-no-data-statement">
$t("no_data") {{ trans(NO_DATA) }}
}}</span> </span>
<tab-table v-else> <tab-table v-else>
<template #thead> <template #thead>
<th scope="col"> <th scope="col">
{{ $t("Date") }} {{ trans(DATE) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("Subject") }} {{ trans(SUBJECT) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("From") }} {{ trans(FROM) }}
</th> </th>
<th scope="col" /> <th scope="col" />
</template> </template>
<template #tbody> <template #tbody>
<tr v-for="(n, i) in notifications.results" :key="`notify-${i}`"> <tr v-for="(n, i) in notifications.results" :key="`notify-${i}`">
<td>{{ $d(n.date.datetime, "long") }}</td> <td>{{ $d(new Date(n.date.datetime), "long") }}</td>
<td> <td>
<span class="unread"> <span class="unread">
<i class="fa fa-envelope-o" /> <i class="fa fa-envelope-o" />
@@ -31,11 +31,11 @@
{{ n.sender.text }} {{ n.sender.text }}
</td> </td>
<td v-else> <td v-else>
{{ $t("automatic_notification") }} {{ trans(AUTOMATIC_NOTIFICATION) }}
</td> </td>
<td> <td>
<a class="btn btn-sm btn-show" :href="getEntityUrl(n)"> <a class="btn btn-sm btn-show" :href="getEntityUrl(n)">
{{ $t("show_entity", { entity: getEntityName(n) }) }} {{ trans(SHOW_ENTITY, { entity: getEntityName(n) }) }}
</a> </a>
</td> </td>
</tr> </tr>
@@ -43,65 +43,82 @@
</tab-table> </tab-table>
</template> </template>
<script> <script lang="ts" setup>
import { mapState, mapGetters } from "vuex"; import { computed, ComputedRef } from "vue";
import TabTable from "./TabTable"; import { useStore } from "vuex";
import { appMessages } from "ChillMainAssets/vuejs/HomepageWidget/js/i18n"; import TabTable from "./TabTable.vue";
import { Notification } from "ChillPersonAssets/types";
export default { import {
name: "MyNotifications", MY_NOTIFICATIONS_DESCRIPTION,
components: { DATE,
TabTable, FROM,
}, SUBJECT,
computed: { SHOW_ENTITY,
...mapState(["notifications"]), THE_ACTIVITY,
...mapGetters(["isNotificationsLoaded"]), THE_COURSE,
noResults() { THE_ACTION,
if (!this.isNotificationsLoaded) { THE_EVALUATION_DOCUMENT,
return false; THE_WORKFLOW,
} else { NO_DATA,
return this.notifications.count === 0; AUTOMATIC_NOTIFICATION,
} trans,
}, } from "translator";
}, import { PaginationResponse } from "ChillMainAssets/lib/api/apiMethods";
methods: { const store = useStore();
getNotificationUrl(n) {
return `/fr/notification/${n.id}/show`; const notifications: ComputedRef<PaginationResponse<Notification>> = computed(
}, () => store.state.homepage.notifications,
getEntityName(n) { );
switch (n.relatedEntityClass) { const isNotificationsLoaded = computed(
case "Chill\\ActivityBundle\\Entity\\Activity": () => store.getters.isNotificationsLoaded,
return appMessages.fr.the_activity; );
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod":
return appMessages.fr.the_course; const noResults = computed(() => {
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork": if (!isNotificationsLoaded.value) {
return appMessages.fr.the_action; return false;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument": } else {
return appMessages.fr.the_evaluation_document; return notifications.value.count === 0;
case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow": }
return appMessages.fr.the_workflow; });
default:
throw "notification type unknown"; function getNotificationUrl(n: Notification): string {
} return `/fr/notification/${n.id}/show`;
}, }
getEntityUrl(n) {
switch (n.relatedEntityClass) { function getEntityName(n: Notification): string {
case "Chill\\ActivityBundle\\Entity\\Activity": switch (n.relatedEntityClass) {
return `/fr/activity/${n.relatedEntityId}/show`; case "Chill\\ActivityBundle\\Entity\\Activity":
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod": return trans(THE_ACTIVITY);
return `/fr/parcours/${n.relatedEntityId}`; case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod":
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork": return trans(THE_COURSE);
return `/fr/person/accompanying-period/work/${n.relatedEntityId}/show`; case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork":
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument": return trans(THE_ACTION);
return `/fr/person/accompanying-period/work/evaluation/document/${n.relatedEntityId}/show`; case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument":
case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow": return trans(THE_EVALUATION_DOCUMENT);
return `/fr/main/workflow/${n.relatedEntityId}/show`; case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow":
default: return trans(THE_WORKFLOW);
throw "notification type unknown"; default:
} throw "notification type unknown";
}, }
}, }
};
function getEntityUrl(n: Notification): string {
switch (n.relatedEntityClass) {
case "Chill\\ActivityBundle\\Entity\\Activity":
return `/fr/activity/${n.relatedEntityId}/show`;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod":
return `/fr/parcours/${n.relatedEntityId}`;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork":
return `/fr/person/accompanying-period/work/${n.relatedEntityId}/show`;
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument":
return `/fr/person/accompanying-period/work/evaluation/document/${n.relatedEntityId}/show`;
case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow":
return `/fr/main/workflow/${n.relatedEntityId}/show`;
default:
throw "notification type unknown";
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -1,36 +1,38 @@
<template> <template>
<div class="alert alert-light"> <div class="alert alert-light">
{{ $t("my_tasks.description_warning") }} {{ trans(MY_TASKS_DESCRIPTION_WARNING) }}
</div> </div>
<span v-if="noResultsAlert" class="chill-no-data-statement">{{ <span v-if="noResultsAlert" class="chill-no-data-statement">
$t("no_data") {{ trans(NO_DATA) }}
}}</span> </span>
<tab-table v-else> <tab-table v-else>
<template #thead> <template #thead>
<th scope="col"> <th scope="col">
{{ $t("warning_date") }} {{ trans(WARNING_DATE) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("max_date") }} {{ trans(MAX_DATE) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("task") }} {{ trans(TASK) }}
</th> </th>
<th scope="col" /> <th scope="col" />
</template> </template>
<template #tbody> <template #tbody>
<tr v-for="(t, i) in tasks.alert.results" :key="`task-alert-${i}`"> <tr v-for="(t, i) in tasks.alert.results" :key="`task-alert-${i}`">
<td v-if="null !== t.warningDate"> <td v-if="t.warningDate !== null">
{{ $d(t.warningDate.datetime, "short") }} {{ $d(new Date(t.warningDate.datetime), "short") }}
</td> </td>
<td v-else /> <td v-else />
<td> <td>
<span class="outdated">{{ $d(t.endDate.datetime, "short") }}</span> <span class="outdated">{{
$d(new Date(t.endDate.datetime), "short")
}}</span>
</td> </td>
<td>{{ t.title }}</td> <td>{{ t.title }}</td>
<td> <td>
<a class="btn btn-sm btn-show" :href="getUrl(t)"> <a class="btn btn-sm btn-show" :href="getUrl(t)">
{{ $t("show_entity", { entity: $t("the_task") }) }} {{ trans(SHOW_ENTITY, { entity: trans(THE_TASK) }) }}
</a> </a>
</td> </td>
</tr> </tr>
@@ -38,21 +40,21 @@
</tab-table> </tab-table>
<div class="alert alert-light"> <div class="alert alert-light">
{{ $t("my_tasks.description_alert") }} {{ trans(MY_TASKS_DESCRIPTION_ALERT) }}
</div> </div>
<span v-if="noResultsWarning" class="chill-no-data-statement">{{ <span v-if="noResultsWarning" class="chill-no-data-statement">
$t("no_data") {{ trans(NO_DATA) }}
}}</span> </span>
<tab-table v-else> <tab-table v-else>
<template #thead> <template #thead>
<th scope="col"> <th scope="col">
{{ $t("warning_date") }} {{ trans(WARNING_DATE) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("max_date") }} {{ trans(MAX_DATE) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("task") }} {{ trans(TASK) }}
</th> </th>
<th scope="col" /> <th scope="col" />
</template> </template>
@@ -60,14 +62,14 @@
<tr v-for="(t, i) in tasks.warning.results" :key="`task-warning-${i}`"> <tr v-for="(t, i) in tasks.warning.results" :key="`task-warning-${i}`">
<td> <td>
<span class="outdated">{{ <span class="outdated">{{
$d(t.warningDate.datetime, "short") $d(new Date(t.warningDate.datetime), "short")
}}</span> }}</span>
</td> </td>
<td>{{ $d(t.endDate.datetime, "short") }}</td> <td>{{ $d(new Date(t.endDate.datetime), "short") }}</td>
<td>{{ t.title }}</td> <td>{{ t.title }}</td>
<td> <td>
<a class="btn btn-sm btn-show" :href="getUrl(t)"> <a class="btn btn-sm btn-show" :href="getUrl(t)">
{{ $t("show_entity", { entity: $t("the_task") }) }} {{ trans(SHOW_ENTITY, { entity: trans(THE_TASK) }) }}
</a> </a>
</td> </td>
</tr> </tr>
@@ -75,39 +77,51 @@
</tab-table> </tab-table>
</template> </template>
<script> <script lang="ts" setup>
import { mapState, mapGetters } from "vuex"; import { computed, ComputedRef } from "vue";
import TabTable from "./TabTable"; import { useStore } from "vuex";
import TabTable from "./TabTable.vue";
import {
MY_TASKS_DESCRIPTION_ALERT,
MY_TASKS_DESCRIPTION_WARNING,
MAX_DATE,
WARNING_DATE,
TASK,
SHOW_ENTITY,
THE_TASK,
NO_DATA,
trans,
} from "translator";
import { TasksState } from "./store/modules/homepage";
import { Alert, Warning } from "ChillPersonAssets/types";
export default { const store = useStore();
name: "MyTasks",
components: { const tasks: ComputedRef<TasksState> = computed(
TabTable, () => store.state.homepage.tasks,
}, );
computed: { const isTasksWarningLoaded = computed(() => store.getters.isTasksWarningLoaded);
...mapState(["tasks"]), const isTasksAlertLoaded = computed(() => store.getters.isTasksAlertLoaded);
...mapGetters(["isTasksWarningLoaded", "isTasksAlertLoaded"]),
noResultsAlert() { const noResultsAlert = computed(() => {
if (!this.isTasksAlertLoaded) { if (!isTasksAlertLoaded.value) {
return false; return false;
} else { } else {
return this.tasks.alert.count === 0; return tasks.value.alert.count === 0;
} }
}, });
noResultsWarning() {
if (!this.isTasksWarningLoaded) { const noResultsWarning = computed(() => {
return false; if (!isTasksWarningLoaded.value) {
} else { return false;
return this.tasks.warning.count === 0; } else {
} return tasks.value.warning.count === 0;
}, }
}, });
methods: {
getUrl(t) { function getUrl(t: Warning | Alert): string {
return `/fr/task/single-task/${t.id}/show`; return `/fr/task/single-task/${t.id}/show`;
}, }
},
};
</script> </script>
<style scoped> <style scoped>

View File

@@ -0,0 +1,58 @@
<template>
<div class="col-12">
<!-- Loading state -->
<div
v-if="isTicketLoading"
class="d-flex justify-content-center align-items-center"
style="height: 200px"
>
<div class="text-center">
<div class="spinner-border mb-3" role="status">
<span class="visually-hidden">{{
trans(CHILL_TICKET_LIST_LOADING_TICKET)
}}</span>
</div>
<div class="text-muted">
{{ trans(CHILL_TICKET_LIST_LOADING_TICKET) }}
</div>
</div>
</div>
<!-- Ticket list -->
<ticket-list-component
v-else
:tickets="ticketList"
title=""
:hasMoreTickets="pagination.next !== null"
@fetchNextPage="fetchNextPage"
/>
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import { useStore } from "vuex";
// Components
import TicketListComponent from "../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketListComponent.vue";
// Types
import { Pagination } from "ChillMainAssets/lib/api/apiMethods";
import { TicketSimple } from "src/Bundle/ChillTicketBundle/src/Resources/public/types";
// Translation
import { CHILL_TICKET_LIST_LOADING_TICKET, trans } from "translator";
const store = useStore();
const pagination = computed(() => store.getters.getPagination as Pagination);
const ticketList = computed(
() => store.getters.getTicketList as TicketSimple[],
);
const isTicketLoading = computed(() => store.getters.isTicketLoading);
const fetchNextPage = async () => {
if (pagination.value.next) {
await store.dispatch("fetchTicketListByUrl", pagination.value.next);
}
};
</script>

View File

@@ -1,26 +1,27 @@
<template> <template>
<div class="alert alert-light"> <div class="alert alert-light">
{{ $t("my_workflows.description") }} {{ trans(MY_WORKFLOWS_DESCRIPTION) }}
</div> </div>
<my-workflows-table :workflows="workflows" /> <my-workflows-table :workflows="workflows" />
<div class="alert alert-light"> <div class="alert alert-light">
{{ $t("my_workflows.description_cc") }} {{ trans(MY_WORKFLOWS_DESCRIPTION_CC) }}
</div> </div>
<my-workflows-table :workflows="workflowsCc" /> <my-workflows-table :workflows="workflowsCc" />
</template> </template>
<script> <script lang="ts" setup>
import { mapState } from "vuex"; import { computed } from "vue";
import { useStore } from "vuex";
import MyWorkflowsTable from "./MyWorkflowsTable.vue"; import MyWorkflowsTable from "./MyWorkflowsTable.vue";
import {
MY_WORKFLOWS_DESCRIPTION,
MY_WORKFLOWS_DESCRIPTION_CC,
trans,
} from "translator";
export default { const store = useStore();
name: "MyWorkflows",
components: { const workflows = computed(() => store.state.homepage.workflows);
MyWorkflowsTable, const workflowsCc = computed(() => store.state.homepage.workflowsCc);
},
computed: {
...mapState(["workflows", "workflowsCc"]),
},
};
</script> </script>

View File

@@ -1,17 +1,17 @@
<template> <template>
<span v-if="hasNoResults(workflows)" class="chill-no-data-statement">{{ <span v-if="hasNoResults" class="chill-no-data-statement">
$t("no_data") {{ trans(NO_DATA) }}
}}</span> </span>
<tab-table v-else> <tab-table v-else>
<template #thead> <template #thead>
<th scope="col"> <th scope="col">
{{ $t("Object_workflow") }} {{ trans(OBJECT_WORKFLOW) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("Step") }} {{ trans(STEP) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("concerned_users") }} {{ trans(CONCERNED_USERS) }}
</th> </th>
<th scope="col" /> <th scope="col" />
</template> </template>
@@ -29,7 +29,7 @@
<span <span
v-if="w.isOnHoldAtCurrentStep" v-if="w.isOnHoldAtCurrentStep"
class="badge bg-success rounded-pill" class="badge bg-success rounded-pill"
>{{ $t("on_hold") }}</span >{{ trans(ON_HOLD) }}</span
> >
</div> </div>
</td> </td>
@@ -46,7 +46,7 @@
</td> </td>
<td> <td>
<a class="btn btn-sm btn-show" :href="getUrl(w)"> <a class="btn btn-sm btn-show" :href="getUrl(w)">
{{ $t("show_entity", { entity: $t("the_workflow") }) }} {{ trans(SHOW_ENTITY, { entity: trans(THE_WORKFLOW) }) }}
</a> </a>
</td> </td>
</tr> </tr>
@@ -54,38 +54,48 @@
</tab-table> </tab-table>
</template> </template>
<script> <script lang="ts" setup>
import { mapGetters } from "vuex"; import { computed } from "vue";
import TabTable from "./TabTable"; import { useStore } from "vuex";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly"; import TabTable from "./TabTable.vue";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
import { Workflow } from "ChillPersonAssets/types";
import {
CONCERNED_USERS,
OBJECT_WORKFLOW,
ON_HOLD,
SHOW_ENTITY,
THE_WORKFLOW,
NO_DATA,
STEP,
trans,
} from "translator";
import { PaginationResponse } from "ChillMainAssets/lib/api/apiMethods";
export default { const props = defineProps<{
name: "MyWorkflows", workflows: PaginationResponse<Workflow>;
components: { }>();
TabTable,
OnTheFly, const store = useStore();
},
props: ["workflows"], const isWorkflowsLoaded = computed(() => store.getters.isWorkflowsLoaded);
computed: {
...mapGetters(["isWorkflowsLoaded"]), const hasNoResults = computed(() => {
}, if (!isWorkflowsLoaded.value) {
methods: { return false;
hasNoResults(workflows) { } else {
if (!this.isWorkflowsLoaded) { return props.workflows.count === 0;
return false; }
} else { });
return workflows.count === 0;
} function getUrl(w: Workflow): string {
}, return `/fr/main/workflow/${w.id}/show`;
getUrl(w) { }
return `/fr/main/workflow/${w.id}/show`;
}, function getStep(w: Workflow): string {
getStep(w) { const lastStep = w.steps.length - 1;
const lastStep = w.steps.length - 1; return w.steps[lastStep].currentStep.text;
return w.steps[lastStep].currentStep.text; }
},
},
};
</script> </script>
<style scoped> <style scoped>

View File

@@ -1,27 +1,26 @@
// CURRENTLY NOT IN USE
<template> <template>
<div class="accompanying-course-work"> <div class="accompanying-course-work">
<div class="alert alert-light"> <div class="alert alert-light">
{{ $t("my_works.description") }} {{ trans(MY_WORKS_DESCRIPTION) }}
</div> </div>
<span v-if="noResults" class="chill-no-data-statement">{{ <span v-if="noResults" class="chill-no-data-statement">
$t("no_data") {{ trans(NO_DATA) }}
}}</span> </span>
<tab-table v-else> <tab-table v-else>
<template #thead> <template #thead>
<th scope="col"> <th scope="col">
{{ $t("StartDate") }} {{ trans(START_DATE) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("SocialAction") }} {{ trans(SOCIAL_ACTION) }}
</th> </th>
<th scope="col"> <th scope="col">
{{ $t("concerned_persons") }} {{ trans(CONCERNED_PERSONS) }}
</th> </th>
<th scope="col" /> <th scope="col" />
</template> </template>
<template #tbody> <template #tbody>
<tr v-for="(w, i) in works.results" :key="`works-${i}`"> <tr v-for="(w, i) in works.value.results" :key="`works-${i}`">
<td>{{ $d(w.startDate.datetime, "short") }}</td> <td>{{ $d(w.startDate.datetime, "short") }}</td>
<td> <td>
<span class="chill-entity entity-social-issue"> <span class="chill-entity entity-social-issue">
@@ -51,8 +50,8 @@
<div class="btn-group-vertical" role="group" aria-label="Actions"> <div class="btn-group-vertical" role="group" aria-label="Actions">
<a class="btn btn-sm btn-update" :href="getUrl(w)"> <a class="btn btn-sm btn-update" :href="getUrl(w)">
{{ {{
$t("show_entity", { trans(SHOW_ENTITY, {
entity: $t("the_action"), entity: trans(THE_ACTION),
}) })
}} }}
</a> </a>
@@ -61,8 +60,8 @@
:href="getUrl(w.accompanyingPeriod)" :href="getUrl(w.accompanyingPeriod)"
> >
{{ {{
$t("show_entity", { trans(SHOW_ENTITY, {
entity: $t("the_course"), entity: trans(THE_COURSE),
}) })
}} }}
</a> </a>
@@ -74,41 +73,47 @@
</div> </div>
</template> </template>
<script> <script lang="ts" setup>
import { mapState, mapGetters } from "vuex"; import { computed } from "vue";
import TabTable from "./TabTable"; import { useStore } from "vuex";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly"; import TabTable from "./TabTable.vue";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
import {
MY_WORKS_DESCRIPTION,
CONCERNED_PERSONS,
SHOW_ENTITY,
THE_COURSE,
THE_ACTION,
SOCIAL_ACTION,
START_DATE,
NO_DATA,
trans,
} from "translator";
import { Workflow } from "ChillPersonAssets/types";
export default { const store = useStore();
name: "MyWorks",
components: { const works = computed(() => store.state.homepage.works);
TabTable, const isWorksLoaded = computed(() => store.getters.isWorksLoaded);
OnTheFly,
}, const noResults = computed(() => {
computed: { if (!isWorksLoaded.value) {
...mapState(["works"]), return false;
...mapGetters(["isWorksLoaded"]), } else {
noResults() { return works.value.count === 0;
if (!this.isWorksLoaded) { }
return false; });
} else {
return this.works.count === 0; function getUrl(e: Workflow): string {
} switch (e.type) {
}, case "accompanying_period_work":
}, return `/fr/person/accompanying-period/work/${e.id}/edit`;
methods: { case "accompanying_period":
getUrl(e) { return `/fr/parcours/${e.id}`;
switch (e.type) { default:
case "accompanying_period_work": throw "entity type unknown";
return `/fr/person/accompanying-period/work/${e.id}/edit`; }
case "accompanying_period": }
return `/fr/parcours/${e.id}`;
default:
throw "entity type unknown";
}
},
},
};
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -4,14 +4,14 @@
</span> </span>
</template> </template>
<script> <script lang="ts" setup>
export default { import { computed } from "vue";
name: "TabCounter",
props: ["count"], const props = defineProps<{
computed: { count: number;
isCounterAvailable() { }>();
return typeof this.count !== "undefined" && this.count > 0;
}, const isCounterAvailable = computed(
}, () => typeof props.count !== "undefined" && props.count > 0,
}; );
</script> </script>

View File

@@ -11,11 +11,8 @@
</table> </table>
</template> </template>
<script> <script lang="ts" setup>
export default { // Pas de props à définir, composant slot simple
name: "TabTable",
props: [],
};
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -1,89 +0,0 @@
const appMessages = {
fr: {
main_title: "Vue d'ensemble",
my_works: {
tab: "Mes actions",
description:
"Liste des actions d'accompagnement dont je suis référent et qui arrivent à échéance.",
},
my_evaluations: {
tab: "Mes évaluations",
description:
"Liste des évaluations dont je suis référent et qui arrivent à échéance.",
},
my_tasks: {
tab: "Mes tâches",
description_alert:
"Liste des tâches auxquelles je suis assigné et dont la date de rappel est dépassée.",
description_warning:
"Liste des tâches auxquelles je suis assigné et dont la date d'échéance est dépassée.",
},
my_accompanying_courses: {
tab: "Mes nouveaux parcours",
description:
"Liste des parcours d'accompagnement que l'on vient de m'attribuer depuis moins de 15 jours.",
},
my_notifications: {
tab: "Mes nouvelles notifications",
description: "Liste des notifications reçues et non lues.",
},
my_workflows: {
tab: "Mes workflows",
description: "Liste des workflows en attente d'une action.",
description_cc: "Liste des workflows dont je suis en copie.",
},
opening_date: "Date d'ouverture",
social_issues: "Problématiques sociales",
concerned_persons: "Usagers concernés",
max_date: "Date d'échéance",
warning_date: "Date de rappel",
evaluation: "Évaluation",
task: "Tâche",
Date: "Date",
From: "Expéditeur",
Subject: "Objet",
Entity: "Associé à",
Step: "Étape",
concerned_users: "Usagers concernés",
Object_workflow: "Objet du workflow",
on_hold: "En attente",
show_entity: "Voir {entity}",
the_activity: "l'échange",
the_course: "le parcours",
the_action: "l'action",
the_evaluation: "l'évaluation",
the_evaluation_document: "le document",
the_task: "la tâche",
the_workflow: "le workflow",
StartDate: "Date d'ouverture",
SocialAction: "Action d'accompagnement",
no_data: "Aucun résultats",
no_dashboard: "Pas de tableaux de bord",
counter: {
unread_notifications:
"{n} notification non lue | {n} notifications non lues",
assignated_courses:
"{n} parcours récent assigné | {n} parcours récents assignés",
assignated_actions: "{n} action assignée | {n} actions assignées",
assignated_evaluations:
"{n} évaluation assignée | {n} évaluations assignées",
alert_tasks: "{n} tâche en rappel | {n} tâches en rappel",
warning_tasks: "{n} tâche à échéance | {n} tâches à échéance",
},
emergency: "Urgent",
confidential: "Confidentiel",
automatic_notification: "Notification automatique",
widget: {
news: {
title: "Actualités",
readMore: "Lire la suite",
date: "Date",
none: "Aucune actualité",
},
},
},
};
Object.assign(appMessages.fr);
export { appMessages };

View File

@@ -0,0 +1,55 @@
import { createStore } from "vuex";
import { State as HomepageStates, moduleHomepage } from "./modules/homepage";
import {
State as TicketListStates,
moduleTicketList,
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/ticket_list";
import {
State as TicketStates,
moduleTicket,
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/ticket";
import {
State as CommentStates,
moduleComment,
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/comment";
import {
moduleMotive,
State as MotiveStates,
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/motive";
import {
State as AddresseeStates,
moduleAddressee,
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/addressee";
import {
modulePersons,
State as PersonsState,
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/persons";
import {
moduleUser,
State as UserState,
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/user";
export interface RootState {
homepage: HomepageStates;
motive: MotiveStates;
ticket: TicketStates;
comment: CommentStates;
addressee: AddresseeStates;
persons: PersonsState;
ticketList: TicketListStates;
user: UserState;
}
export const store = createStore<RootState>({
modules: {
homepage: moduleHomepage,
motive: moduleMotive,
ticket: moduleTicket,
comment: moduleComment,
addressee: moduleAddressee,
person: modulePersons,
ticketList: moduleTicketList,
user: moduleUser,
},
});

View File

@@ -1,90 +1,113 @@
import "es6-promise/auto"; import "es6-promise/auto";
import { createStore } from "vuex"; import { Module } from "vuex";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; import {
makeFetch,
PaginationResponse,
} from "ChillMainAssets/lib/api/apiMethods";
import {
AccompanyingCourse,
Alert,
Evaluation,
Warning,
Workflow,
WorflowCc,
} from "ChillPersonAssets/types";
import { RootState } from "..";
const debug = process.env.NODE_ENV !== "production"; export interface TasksState {
warning: PaginationResponse<Warning>;
alert: PaginationResponse<Alert>;
}
const isEmpty = (obj) => { export interface State {
return ( evaluations: PaginationResponse<Evaluation>;
obj && tasks: TasksState;
Object.keys(obj).length <= 1 && accompanyingCourses: PaginationResponse<AccompanyingCourse>;
Object.getPrototypeOf(obj) === Object.prototype notifications: PaginationResponse<Notification>;
); workflows: PaginationResponse<Workflow>;
}; workflowsCc: PaginationResponse<WorflowCc>;
errorMsg: unknown[];
loading: boolean;
ticketsLoading: boolean;
}
const store = createStore({ export const moduleHomepage: Module<State, RootState> = {
strict: debug,
state: { state: {
// works: {}, evaluations: {
evaluations: {}, count: 0,
} as PaginationResponse<Evaluation>,
tasks: { tasks: {
warning: {}, warning: {
alert: {}, count: 0,
} as PaginationResponse<Warning>,
alert: {
count: 0,
} as PaginationResponse<Alert>,
}, },
accompanyingCourses: {}, accompanyingCourses: {
notifications: {}, count: 0,
workflows: {}, } as PaginationResponse<AccompanyingCourse>,
workflowsCc: {}, notifications: {
count: 0,
} as PaginationResponse<Notification>,
workflows: {
count: 0,
} as PaginationResponse<Workflow>,
workflowsCc: {
count: 0,
} as PaginationResponse<WorflowCc>,
ticketsLoading: false,
errorMsg: [], errorMsg: [],
loading: false, loading: false,
}, },
getters: { getters: {
// isWorksLoaded(state) { isTicketLoading: (state) => {
// return !isEmpty(state.works); return state.ticketsLoading;
// }, },
isEvaluationsLoaded(state) { isEvaluationsLoaded(state) {
return !isEmpty(state.evaluations); return Array.isArray(state.evaluations.results);
}, },
isTasksWarningLoaded(state) { isTasksWarningLoaded(state) {
return !isEmpty(state.tasks.warning); return Array.isArray(state.tasks.warning.results);
}, },
isTasksAlertLoaded(state) { isTasksAlertLoaded(state) {
return !isEmpty(state.tasks.alert); return Array.isArray(state.tasks.alert.results);
}, },
isAccompanyingCoursesLoaded(state) { isAccompanyingCoursesLoaded(state) {
return !isEmpty(state.accompanyingCourses); return Array.isArray(state.accompanyingCourses.results);
}, },
isNotificationsLoaded(state) { isNotificationsLoaded(state) {
return !isEmpty(state.notifications); return Array.isArray(state.notifications.results);
}, },
isWorkflowsLoaded(state) { isWorkflowsLoaded(state) {
return !isEmpty(state.workflows); return Array.isArray(state.workflows.results);
}, },
counter(state) { counter(state, getters, rootState, rootGetters) {
return { return {
// works: state.works.count, tickets: rootGetters["ticketList/getCount"] || 0,
evaluations: state.evaluations.count, evaluations: state.evaluations.count || 0,
tasksWarning: state.tasks.warning.count, tasksWarning: state.tasks.warning.count || 0,
tasksAlert: state.tasks.alert.count, tasksAlert: state.tasks.alert.count || 0,
accompanyingCourses: state.accompanyingCourses.count, accompanyingCourses: state.accompanyingCourses.count || 0,
notifications: state.notifications.count, notifications: state.notifications.count || 0,
workflows: state.workflows.count, workflows: state.workflows.count || 0,
}; };
}, },
}, },
mutations: { mutations: {
// addWorks(state, works) {
// //console.log('addWorks', works);
// state.works = works;
// },
addEvaluations(state, evaluations) { addEvaluations(state, evaluations) {
//console.log('addEvaluations', evaluations);
state.evaluations = evaluations; state.evaluations = evaluations;
}, },
addTasksWarning(state, tasks) { addTasksWarning(state, tasks) {
//console.log('addTasksWarning', tasks);
state.tasks.warning = tasks; state.tasks.warning = tasks;
}, },
addTasksAlert(state, tasks) { addTasksAlert(state, tasks) {
//console.log('addTasksAlert', tasks);
state.tasks.alert = tasks; state.tasks.alert = tasks;
}, },
addCourses(state, courses) { addCourses(state, courses) {
//console.log('addCourses', courses);
state.accompanyingCourses = courses; state.accompanyingCourses = courses;
}, },
addNotifications(state, notifications) { addNotifications(state, notifications) {
//console.log('addNotifications', notifications);
state.notifications = notifications; state.notifications = notifications;
}, },
addWorkflows(state, workflows) { addWorkflows(state, workflows) {
@@ -96,29 +119,29 @@ const store = createStore({
setLoading(state, bool) { setLoading(state, bool) {
state.loading = bool; state.loading = bool;
}, },
setTicketsLoading(state, bool) {
state.ticketsLoading = bool;
},
catchError(state, error) { catchError(state, error) {
state.errorMsg.push(error); state.errorMsg.push(error);
}, },
}, },
actions: { actions: {
getByTab({ commit, getters }, { tab, param }) { async getByTab({ commit, getters, dispatch }, { tab, param }) {
switch (tab) { switch (tab) {
// case 'MyWorks': case "MyTickets":
// if (!getters.isWorksLoaded) { if (!getters.isTicketsLoaded) {
// commit('setLoading', true); commit("setTicketsLoading", true);
// const url = `/api/1.0/person/accompanying-period/work/my-near-end${'?'+ param}`; // Utilise l'action du module ticket_list
// makeFetch('GET', url) await dispatch(
// .then((response) => { "fetchTicketList",
// commit('addWorks', response); { byAddresseeToMe: true },
// commit('setLoading', false); { root: true },
// }) );
// .catch((error) => {
// commit('catchError', error); commit("setTicketsLoading", false);
// throw error; }
// }) break;
// ;
// }
// break;
case "MyEvaluations": case "MyEvaluations":
if (!getters.isEvaluationsLoaded) { if (!getters.isEvaluationsLoaded) {
commit("setLoading", true); commit("setLoading", true);
@@ -180,7 +203,6 @@ const store = createStore({
const url = `/api/1.0/main/notification/my/unread${"?" + param}`; const url = `/api/1.0/main/notification/my/unread${"?" + param}`;
makeFetch("GET", url) makeFetch("GET", url)
.then((response) => { .then((response) => {
console.log("notifications", response);
commit("addNotifications", response); commit("addNotifications", response);
commit("setLoading", false); commit("setLoading", false);
}) })
@@ -217,6 +239,4 @@ const store = createStore({
} }
}, },
}, },
}); };
export { store };

View File

@@ -46,10 +46,7 @@
</li> </li>
</ul> </ul>
<ul <ul class="badge-suggest add-items inline text-end">
class="badge-suggest add-items inline"
style="justify-content: flex-end; display: flex"
>
<li <li
v-for="s in suggested" v-for="s in suggested"
:key="s.type + s.id" :key="s.type + s.id"

View File

@@ -44,16 +44,16 @@ class SearchUserGroupApiProvider implements SearchApiInterface, LocaleAwareInter
public function provideQuery(string $pattern, array $parameters): SearchApiQuery public function provideQuery(string $pattern, array $parameters): SearchApiQuery
{ {
return $this->userGroupRepository->provideSearchApiQuery($pattern, $this->getLocale(), 'user-group'); return $this->userGroupRepository->provideSearchApiQuery($pattern, $this->getLocale(), 'user_group');
} }
public function supportsResult(string $key, array $metadatas): bool public function supportsResult(string $key, array $metadatas): bool
{ {
return 'user-group' === $key; return 'user_group' === $key;
} }
public function supportsTypes(string $pattern, array $types, array $parameters): bool public function supportsTypes(string $pattern, array $types, array $parameters): bool
{ {
return in_array('user-group', $types, true); return in_array('user_group', $types, true);
} }
} }

View File

@@ -305,7 +305,7 @@ paths:
- thirdparty - thirdparty
- user - user
- household - household
- user-group - user_group
responses: responses:
200: 200:
description: "OK" description: "OK"

View File

@@ -26,7 +26,7 @@ module.exports = function (encore, entries) {
); );
encore.addEntry( encore.addEntry(
"page_homepage_widget", "page_homepage_widget",
__dirname + "/Resources/public/page/homepage_widget/index.js", __dirname + "/Resources/public/page/homepage_widget/index.ts",
); );
encore.addEntry( encore.addEntry(
"page_export", "page_export",

View File

@@ -191,3 +191,61 @@ pick_entity:
one {Tiers} one {Tiers}
other {Tiers} other {Tiers}
} }
loading: "Chargement..."
main_title: "Vue d'ensemble"
my_tickets.tab: "Mes Tickets"
my_works.tab: "Mes actions"
my_works.description: "Liste des actions d'accompagnement dont je suis référent et qui arrivent à échéance."
my_evaluations.tab: "Mes évaluations"
my_evaluations.description: "Liste des évaluations dont je suis référent et qui arrivent à échéance."
my_tasks.tab: "Mes tâches"
my_tasks.description_alert: "Liste des tâches auxquelles je suis assigné et dont la date de rappel est dépassée."
my_tasks.description_warning: "Liste des tâches auxquelles je suis assigné et dont la date d'échéance est dépassée."
my_accompanying_courses.tab: "Mes nouveaux parcours"
my_accompanying_courses.description: "Liste des parcours d'accompagnement que l'on vient de m'attribuer depuis moins de 15 jours."
my_notifications.tab: "Mes nouvelles notifications"
my_notifications.description: "Liste des notifications reçues et non lues."
my_workflows.tab: "Mes workflows"
my_workflows.description: "Liste des workflows en attente d'une action."
my_workflows.description_cc: "Liste des workflows dont je suis en copie."
opening_date: "Date d'ouverture"
social_issues: "Problématiques sociales"
concerned_persons: "Usagers concernés"
max_date: "Date d'échéance"
warning_date: "Date de rappel"
evaluation: "Évaluation"
task: "Tâche"
Date: "Date"
From: "Expéditeur"
Subject: "Objet"
Entity: "Associé à"
Step: "Étape"
concerned_users: "Usagers concernés"
Object_workflow: "Objet du workflow"
on_hold: "En attente"
show_entity: "Voir {entity}"
the_activity: "l'échange"
the_course: "le parcours"
the_action: "l'action"
the_evaluation: "l'évaluation"
the_evaluation_document: "le document"
the_task: "la tâche"
the_workflow: "le workflow"
StartDate: "Date d'ouverture"
SocialAction: "Action d'accompagnement"
no_data: "Aucun résultats"
no_dashboard: "Pas de tableaux de bord"
counter.unread_notifications: "{n, plural, one {# notification non lue} other {# notifications non lues}}"
counter.assignated_courses: "{n, plural, one {# parcours récent assigné} other {# parcours récents assignés}}"
counter.assignated_actions: "{n, plural, one {# action assignée} other {# actions assignées}}"
counter.assignated_evaluations: "{n, plural, one {# évaluation assignée} other {# évaluations assignées}}"
counter.alert_tasks: "{n, plural, one {# tâche en rappel} other {# tâches en rappel}}"
counter.warning_tasks: "{n, plural, one {# tâche à échéance} other {# tâches à échéance}}"
emergency: "Urgent"
confidential: "Confidentiel"
automatic_notification: "Notification automatique"
widget.news.title: "Actualités"
widget.news.readMore: "Lire la suite"
widget.news.date: "Date"
widget.news.none: "Aucune actualité"

View File

@@ -14,12 +14,12 @@ import {
import { StoredObject } from "ChillDocStoreAssets/types"; import { StoredObject } from "ChillDocStoreAssets/types";
import { Thirdparty } from "../../../ChillThirdPartyBundle/Resources/public/types"; import { Thirdparty } from "../../../ChillThirdPartyBundle/Resources/public/types";
import { Calendar } from "../../../ChillCalendarBundle/Resources/public/types"; import { Calendar } from "../../../ChillCalendarBundle/Resources/public/types";
import Person from "./vuejs/_components/OnTheFly/Person.vue";
export interface AltName { export interface AltName {
label: string; label: string;
key: string; key: string;
} }
export interface Person { export interface Person {
id: number; id: number;
type: "person"; type: "person";
@@ -45,6 +45,7 @@ export interface Person {
export interface AccompanyingPeriod { export interface AccompanyingPeriod {
id: number; id: number;
type: "accompanying_period";
addressLocation?: Address | null; addressLocation?: Address | null;
administrativeLocation?: Location | null; administrativeLocation?: Location | null;
calendars: Calendar[]; calendars: Calendar[];
@@ -81,6 +82,7 @@ export interface AccompanyingPeriod {
export interface AccompanyingPeriodWork { export interface AccompanyingPeriodWork {
id: number; id: number;
type: "accompanying_period_work";
accompanyingPeriod?: AccompanyingPeriod; accompanyingPeriod?: AccompanyingPeriod;
accompanyingPeriodWorkEvaluations: AccompanyingPeriodWorkEvaluation[]; accompanyingPeriodWorkEvaluations: AccompanyingPeriodWorkEvaluation[];
createdAt?: string; createdAt?: string;
@@ -105,6 +107,7 @@ export interface AccompanyingPeriodWork {
export interface SocialAction { export interface SocialAction {
id: number; id: number;
text: string;
parent?: SocialAction | null; parent?: SocialAction | null;
children: SocialAction[]; children: SocialAction[];
issue?: SocialIssue | null; issue?: SocialIssue | null;
@@ -166,6 +169,7 @@ export interface AccompanyingPeriodLocationHistory {
export interface SocialIssue { export interface SocialIssue {
id: number; id: number;
text: string;
parent?: SocialIssue | null; parent?: SocialIssue | null;
children: SocialIssue[]; children: SocialIssue[];
socialActions?: SocialAction[] | null; socialActions?: SocialAction[] | null;
@@ -206,6 +210,7 @@ export interface AccompanyingPeriodWorkGoal {
} }
export interface AccompanyingPeriodWorkEvaluation { export interface AccompanyingPeriodWorkEvaluation {
type: "accompanying_period_work_evaluation";
accompanyingPeriodWork: AccompanyingPeriodWork | null; accompanyingPeriodWork: AccompanyingPeriodWork | null;
comment: string; comment: string;
createdAt: DateTime | null; createdAt: DateTime | null;
@@ -236,6 +241,68 @@ export interface Evaluation {
notificationDelay: string; notificationDelay: string;
} }
export interface Step {
currentStep: {
text: string;
};
}
export interface Workflow {
id: number;
title: string;
type: "accompanying_period_work" | "accompanying_period";
isOnHoldAtCurrentStep: boolean;
datas: {
persons: Person[];
};
steps: Step[];
}
export interface WorflowCc {
id: number;
title: string;
isOnHoldAtCurrentStep: boolean;
datas: {
persons: Person[];
};
steps: Step[];
}
export interface Warning {
id: number;
warningDate: DateTime;
endDate: DateTime;
title: string;
}
export interface Alert {
id: number;
warningDate: DateTime;
endDate: DateTime;
title: string;
}
export interface Notification {
id: number;
date: DateTime;
title: string;
sender: {
text: string;
};
relatedEntityClass: string;
relatedEntityId: number;
}
export interface Participation {
person: Person;
}
export interface AccompanyingCourse {
id: number;
openingDate: DateTime;
socialIssues: SocialIssue[];
participations: Participation[];
emergency: boolean;
confidential: boolean;
}
export interface AccompanyingPeriodWorkReferrerHistory { export interface AccompanyingPeriodWorkReferrerHistory {
id: number; id: number;
accompanyingPeriodWork: AccompanyingPeriodWork; accompanyingPeriodWork: AccompanyingPeriodWork;

View File

@@ -165,6 +165,7 @@ export interface TicketFilters {
byCreatedBefore: string; byCreatedBefore: string;
byResponseTimeExceeded: boolean; byResponseTimeExceeded: boolean;
byAddresseeToMe: boolean; byAddresseeToMe: boolean;
byTicketId: number | null;
} }
export interface TicketFilterParams { export interface TicketFilterParams {
@@ -179,6 +180,7 @@ export interface TicketFilterParams {
byCreatedBefore?: string; byCreatedBefore?: string;
byResponseTimeExceeded?: string; byResponseTimeExceeded?: string;
byAddresseeToMe?: boolean; byAddresseeToMe?: boolean;
byTicketId?: number;
} }
export interface TicketInitForm { export interface TicketInitForm {

View File

@@ -1,20 +1,11 @@
<template> <template>
<div class="col-12"> <div class="col-12" v-if="!commentHistory.deleted">
<blockquote class="chill-user-quote"> <blockquote class="chill-user-quote">
<button
class="btn btn-sm btn-primary float-end"
title="Visible"
@click="visibleComment"
v-if="commentHistory.deleted"
>
<i class="bi bi-eye"></i>
</button>
<button <button
class="btn btn-sm bg-chill-red float-end text-white" class="btn btn-sm bg-chill-red float-end text-white"
@click="maskComment" @click="deleteComment"
v-else
> >
<i class="bi bi-eye-slash"></i> <i class="bi bi-trash"></i>
</button> </button>
<button <button
class="btn btn-sm btn-edit mx-2 float-end text-white" class="btn btn-sm btn-edit mx-2 float-end text-white"
@@ -24,14 +15,16 @@
/> />
<p v-html="convertMarkdownToHtml(commentHistory.content)"></p> <p v-html="convertMarkdownToHtml(commentHistory.content)"></p>
<span
v-if="commentHistory.deleted"
class="ms-2 d-block text-center fst-italic text-muted"
>
{{ trans(CHILL_TICKET_TICKET_MASK_COMMENT_HINT) }}
</span>
</blockquote> </blockquote>
</div> </div>
<div class="col-12" v-else>
<span class="ms-2 d-block text-center">
{{ trans(CHILL_TICKET_TICKET_MASK_COMMENT_HINT) }}
<button class="btn btn-primary btn-sm ms-2" @click="restoreComment">
{{ trans(CANCEL) }}
</button>
</span>
</div>
<Modal <Modal
v-if="editCommentModal" v-if="editCommentModal"
:show="editCommentModal" :show="editCommentModal"
@@ -75,6 +68,7 @@ import {
CHILL_TICKET_TICKET_MASK_COMMENT_SUCCESS, CHILL_TICKET_TICKET_MASK_COMMENT_SUCCESS,
CHILL_TICKET_TICKET_MASK_COMMENT_HINT, CHILL_TICKET_TICKET_MASK_COMMENT_HINT,
CHILL_TICKET_TICKET_VISIBLE_COMMENT_SUCCESS, CHILL_TICKET_TICKET_VISIBLE_COMMENT_SUCCESS,
CANCEL,
} from "translator"; } from "translator";
import { useToast } from "vue-toast-notification"; import { useToast } from "vue-toast-notification";
@@ -97,18 +91,17 @@ const saveComment = () => {
toast.success(trans(CHILL_TICKET_TICKET_EDIT_COMMENT_SUCCESS)); toast.success(trans(CHILL_TICKET_TICKET_EDIT_COMMENT_SUCCESS));
}; };
const maskComment = () => { const deleteComment = () => {
store.dispatch("maskComment", props.commentHistory.id); store.dispatch("deleteComment", props.commentHistory.id);
store.commit("addRemovedCommentIds", props.commentHistory.id);
editCommentModal.value = false; editCommentModal.value = false;
toast.success(trans(CHILL_TICKET_TICKET_MASK_COMMENT_SUCCESS)); toast.success(trans(CHILL_TICKET_TICKET_MASK_COMMENT_SUCCESS));
}; };
const restoreComment = () => {
const visibleComment = () => { store.dispatch("restoreComment", props.commentHistory.id);
store.dispatch("visibleComment", props.commentHistory.id);
editCommentModal.value = false; editCommentModal.value = false;
toast.success(trans(CHILL_TICKET_TICKET_VISIBLE_COMMENT_SUCCESS)); toast.success(trans(CHILL_TICKET_TICKET_VISIBLE_COMMENT_SUCCESS));
}; };
const preprocess = (markdown: string): string => { const preprocess = (markdown: string): string => {
return markdown; return markdown;
}; };

View File

@@ -0,0 +1,49 @@
<template>
<div class="input-group mb-3">
<input
v-model="ticketId"
type="number"
class="form-control"
:placeholder="trans(CHILL_TICKET_LIST_FILTER_TICKET_ID)"
@input="
ticketId = isNaN(Number(($event.target as HTMLInputElement).value))
? null
: Number(($event.target as HTMLInputElement).value)
"
/>
<span class="input-group-text" v-if="ticketId !== null">
<i
class="fa fa-times chill-red"
style="cursor: pointer"
@click="ticketId = null"
title="clear"
></i>
</span>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
// Translation
import { trans, CHILL_TICKET_LIST_FILTER_TICKET_ID } from "translator";
const props = defineProps<{
modelValue: number | null;
}>();
const ticketId = ref<number | null>(props.modelValue);
watch(
() => props.modelValue,
(newVal) => {
if (newVal === null) {
ticketId.value = null;
}
},
);
const emit = defineEmits(["update:modelValue"]);
watch(ticketId, (ticketId) => {
emit("update:modelValue", ticketId ? ticketId : null);
});
</script>

View File

@@ -8,23 +8,15 @@ import { ApiException } from "../../../../../../../../ChillMainBundle/Resources/
export interface State { export interface State {
comments: Comment[]; comments: Comment[];
removedCommentIds: Comment["id"][];
} }
export const moduleComment: Module<State, RootState> = { export const moduleComment: Module<State, RootState> = {
state: () => ({ state: () => ({
comments: [] as Comment[], comments: [] as Comment[],
removedCommentIds: [] as Comment["id"][],
}), }),
getters: { getters: {
canBeDisplayed:
(state: State, getters: unknown, rootState: RootState) =>
(comment: Comment) => {
return (
(comment.deleted &&
comment.createdBy?.username ===
rootState.user.currentUser?.username) ||
!comment.deleted
);
},
canBeEdited: canBeEdited:
(state: State, getters: unknown, rootState: RootState) => (state: State, getters: unknown, rootState: RootState) =>
(comment: Comment) => { (comment: Comment) => {
@@ -32,8 +24,15 @@ export const moduleComment: Module<State, RootState> = {
comment.createdBy?.username === rootState.user.currentUser?.username comment.createdBy?.username === rootState.user.currentUser?.username
); );
}, },
getRemovedCommentIds(state) {
return state.removedCommentIds;
},
},
mutations: {
addRemovedCommentIds(state, commentId: number) {
state.removedCommentIds.push(commentId);
},
}, },
mutations: {},
actions: { actions: {
async createComment({ commit, rootState }, content: Comment["content"]) { async createComment({ commit, rootState }, content: Comment["content"]) {
try { try {
@@ -64,7 +63,7 @@ export const moduleComment: Module<State, RootState> = {
throw error.name; throw error.name;
} }
}, },
async maskComment({ commit }, id: Comment["id"]) { async deleteComment({ commit }, id: Comment["id"]) {
try { try {
const result: Comment = await makeFetch( const result: Comment = await makeFetch(
"POST", "POST",
@@ -77,7 +76,7 @@ export const moduleComment: Module<State, RootState> = {
} }
}, },
async visibleComment({ commit }, id: Comment["id"]) { async restoreComment({ commit }, id: Comment["id"]) {
try { try {
const result: Comment = await makeFetch( const result: Comment = await makeFetch(
"POST", "POST",

View File

@@ -73,7 +73,10 @@ export const moduleTicketList: Module<State, RootState> = {
([, value]) => ([, value]) =>
value !== undefined && value !== undefined &&
value !== null && value !== null &&
(value === true || (value !== "" && value.length > 0)), (value === true ||
(typeof value === "number" && !isNaN(value)) ||
(typeof value === "string" && value !== "") ||
(Array.isArray(value) && value.length > 0)),
), ),
); );
params = new URLSearchParams( params = new URLSearchParams(

View File

@@ -101,7 +101,6 @@ onMounted(async () => {
await store.dispatch("getCurrentUser"); await store.dispatch("getCurrentUser");
await store.dispatch("fetchTicketList", filters); await store.dispatch("fetchTicketList", filters);
await store.dispatch("fetchMotives"); await store.dispatch("fetchMotives");
await store.dispatch("fetchUserGroups");
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }

View File

@@ -0,0 +1,65 @@
<template>
<label :for="dateId" class="form-label col-12">
{{ label }}
</label>
<div class="d-flex gap-2">
<div class="input-group">
<input
type="date"
:id="dateId"
v-model="modelDate"
class="form-control"
:disabled="disabled"
/>
<input
type="time"
v-model="modelTime"
class="form-control"
:disabled="disabled"
style="max-width: 120px"
placeholder="hh:mm"
/>
<span class="input-group-text" v-if="modelDate">
<i
class="fa fa-times chill-red"
style="cursor: pointer"
@click="clear"
title="clear"
></i>
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps<{
label: string;
dateId: string;
modelValueDate: string;
modelValueTime: string;
defaultValueTime: string;
disabled?: boolean;
}>();
const emit = defineEmits<{
(e: "update:modelValueDate", value: string): void;
(e: "update:modelValueTime", value: string): void;
}>();
const modelDate = computed({
get: () => props.modelValueDate,
set: (val: string) => emit("update:modelValueDate", val),
});
const modelTime = computed({
get: () => props.modelValueTime,
set: (val: string) => emit("update:modelValueTime", val),
});
function clear() {
emit("update:modelValueDate", "");
emit("update:modelValueTime", props.defaultValueTime);
}
</script>

View File

@@ -43,12 +43,12 @@
}}</label> }}</label>
<addressee-selector-component <addressee-selector-component
v-model="selectedAddressees" v-model="selectedAddressees"
:suggested="userGroups" :suggested="[]"
:label="trans(CHILL_TICKET_LIST_FILTER_BY_ADDRESSEES)" :label="trans(CHILL_TICKET_LIST_FILTER_BY_ADDRESSEES)"
id="addresseeSelector" id="addresseeSelector"
/> />
</div> </div>
<div class="col-md-6"> <div class="col-md-6 mb-3">
<!-- Filtre par motifs --> <!-- Filtre par motifs -->
<div class="row"> <div class="row">
<label class="form-label" for="motiveSelector">{{ <label class="form-label" for="motiveSelector">{{
@@ -60,7 +60,7 @@
id="motiveSelector" id="motiveSelector"
/> />
<div class="mt-1" style="min-height: 2.2em"> <div class="mb-2" style="min-height: 2em">
<div class="d-flex flex-wrap gap-2"> <div class="d-flex flex-wrap gap-2">
<span <span
v-for="motive in selectedMotives" v-for="motive in selectedMotives"
@@ -79,43 +79,40 @@
</div> </div>
</div> </div>
<!-- Filtre par état actuel --> <!-- Filtre par état actuel -->
<div class="row"> <div class="d-flex gap-3">
<div class="col-6"> <div>
<div class="mb-2"> <label class="form-label pe-2" for="currentState">
<label class="form-label pe-2" for="currentState"> {{ trans(CHILL_TICKET_LIST_FILTER_CURRENT_STATE) }}
{{ trans(CHILL_TICKET_LIST_FILTER_CURRENT_STATE) }} </label>
</label> <toggle-component
<toggle-component v-model="isClosedToggled"
v-model="isClosedToggled" :on-label="trans(CHILL_TICKET_LIST_FILTER_CLOSED)"
:on-label="trans(CHILL_TICKET_LIST_FILTER_CLOSED)" :off-label="trans(CHILL_TICKET_LIST_FILTER_OPEN)"
:off-label="trans(CHILL_TICKET_LIST_FILTER_OPEN)" :classColor="{
:classColor="{ on: 'bg-chill-red',
on: 'bg-chill-red', off: 'bg-chill-green',
off: 'bg-chill-green', }"
}" @update:model-value="handleStateToggle"
@update:model-value="handleStateToggle" id="currentState"
id="currentState" />
/>
</div>
</div> </div>
<!-- Filtre par état d'urgence --> <!-- Filtre par état d'urgence -->
<div class="col-6"> <div>
<div class="mb-2"> <label class="form-label pe-2" for="emergency">
<label class="form-label pe-2" for="emergency"> {{ trans(CHILL_TICKET_LIST_FILTER_EMERGENCY) }}
{{ trans(CHILL_TICKET_LIST_FILTER_EMERGENCY) }} </label>
</label> <toggle-component
<toggle-component v-model="isEmergencyToggled"
v-model="isEmergencyToggled" :on-label="trans(CHILL_TICKET_LIST_FILTER_EMERGENCY)"
:on-label="trans(CHILL_TICKET_LIST_FILTER_EMERGENCY)" :off-label="trans(CHILL_TICKET_LIST_FILTER_EMERGENCY)"
:off-label="trans(CHILL_TICKET_LIST_FILTER_EMERGENCY)" :classColor="{
:classColor="{ on: 'bg-warning',
on: 'bg-warning', off: 'bg-secondary',
off: 'bg-secondary', }"
}" @update:model-value="handleEmergencyToggle"
@update:model-value="handleEmergencyToggle" id="emergency"
id="emergency" />
/>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -124,6 +121,18 @@
<div class="row"> <div class="row">
<!-- Filtre pour temps de réponse dépassé --> <!-- Filtre pour temps de réponse dépassé -->
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<div class="form-check">
<input
v-model="filters.byAddresseeToMe"
class="form-check-input"
type="checkbox"
id="stateMe"
/>
<label class="form-check-label" for="stateMe">
{{ trans(CHILL_TICKET_LIST_FILTER_TO_ME) }}
</label>
</div>
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input" class="form-check-input"
@@ -141,48 +150,40 @@
{{ trans(CHILL_TICKET_LIST_FILTER_RESPONSE_TIME_WARNING) }} {{ trans(CHILL_TICKET_LIST_FILTER_RESPONSE_TIME_WARNING) }}
</small> </small>
</div> </div>
<!-- Filtre par mes tickets --> <!-- Filtre par numéro de ticket -->
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<div class="d-flex gap-3"> <label class="form-label pe-2" for="ticketSelector">
<div class="form-check"> {{ trans(CHILL_TICKET_LIST_FILTER_BY_TICKET_ID) }}
<input </label>
v-model="filters.byAddresseeToMe" <ticket-selector v-model="filters.byTicketId" id="ticketSelector" />
class="form-check-input"
type="checkbox"
id="stateMe"
/>
<label class="form-check-label" for="stateMe">
{{ trans(CHILL_TICKET_LIST_FILTER_TO_ME) }}
</label>
</div>
</div>
</div> </div>
</div> </div>
<!-- Filtre par date de création --> <!-- Filtre par date de création -->
<div class="row"> <div class="row">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="byCreatedAfter" class="form-label">{{ <date-and-time-selector-component
trans(CHILL_TICKET_LIST_FILTER_CREATED_AFTER) :label="trans(CHILL_TICKET_LIST_FILTER_CREATED_AFTER)"
}}</label> date-id="byCreatedAfter"
<input default-value-time="00:00"
type="datetime-local" :model-value-date="filters.byCreatedAfter"
id="byCreatedAfter" :model-value-time="byCreatedAfterTime"
v-model="filters.byCreatedAfter"
class="form-control"
:disabled="filters.byResponseTimeExceeded" :disabled="filters.byResponseTimeExceeded"
@update:modelValueDate="filters.byCreatedAfter = $event"
@update:modelValueTime="byCreatedAfterTime = $event"
/> />
</div> </div>
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="byCreatedBefore" class="form-label">{{ <date-and-time-selector-component
trans(CHILL_TICKET_LIST_FILTER_CREATED_BEFORE) :label="trans(CHILL_TICKET_LIST_FILTER_CREATED_BEFORE)"
}}</label> date-id="byCreatedBefore"
<input default-value-time="23:59"
type="datetime-local" :model-value-date="filters.byCreatedBefore"
id="byCreatedBefore" :model-value-time="byCreatedBeforeTime"
v-model="filters.byCreatedBefore"
class="form-control"
:disabled="filters.byResponseTimeExceeded" :disabled="filters.byResponseTimeExceeded"
@update:modelValueDate="filters.byCreatedBefore = $event"
@update:modelValueTime="byCreatedBeforeTime = $event"
/> />
</div> </div>
</div> </div>
@@ -218,10 +219,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from "vue"; import { ref, computed, watch } from "vue";
import { useStore } from "vuex";
import type { Person } from "ChillPersonAssets/types"; import type { Person } from "ChillPersonAssets/types";
import type { Motive, TicketFilterParams, TicketFilters } from "../../../types"; import {
import { User, UserGroup, UserGroupOrUser } from "ChillMainAssets/types"; type Motive,
type TicketFilterParams,
type TicketFilters,
} from "../../../types";
import { User, UserGroupOrUser } from "ChillMainAssets/types";
// Translation // Translation
import { import {
@@ -234,6 +238,7 @@ import {
CHILL_TICKET_LIST_FILTER_ADDRESSEES, CHILL_TICKET_LIST_FILTER_ADDRESSEES,
CHILL_TICKET_LIST_FILTER_BY_ADDRESSEES, CHILL_TICKET_LIST_FILTER_BY_ADDRESSEES,
CHILL_TICKET_LIST_FILTER_BY_MOTIVES, CHILL_TICKET_LIST_FILTER_BY_MOTIVES,
CHILL_TICKET_LIST_FILTER_BY_TICKET_ID,
CHILL_TICKET_LIST_FILTER_REMOVE, CHILL_TICKET_LIST_FILTER_REMOVE,
CHILL_TICKET_LIST_FILTER_OPEN, CHILL_TICKET_LIST_FILTER_OPEN,
CHILL_TICKET_LIST_FILTER_CLOSED, CHILL_TICKET_LIST_FILTER_CLOSED,
@@ -254,6 +259,8 @@ import PersonsSelector from "../../TicketApp/components/Person/PersonsSelectorCo
import MotiveSelector from "../../TicketApp/components/Motive/MotiveSelectorComponent.vue"; import MotiveSelector from "../../TicketApp/components/Motive/MotiveSelectorComponent.vue";
import AddresseeSelectorComponent from "../../TicketApp/components/Addressee/AddresseeSelectorComponent.vue"; import AddresseeSelectorComponent from "../../TicketApp/components/Addressee/AddresseeSelectorComponent.vue";
import ToggleComponent from "./ToggleComponent.vue"; import ToggleComponent from "./ToggleComponent.vue";
import TicketSelector from "../../TicketApp/components/Ticket/TicketSelector.vue";
import DateAndTimeSelectorComponent from "./DateAndTimeSelectorComponent.vue";
// Props // Props
const props = defineProps<{ const props = defineProps<{
@@ -267,8 +274,6 @@ const emit = defineEmits<{
"filters-changed": [filters: TicketFilterParams]; "filters-changed": [filters: TicketFilterParams];
}>(); }>();
const store = useStore();
// État réactif // État réactif
const filters = ref<TicketFilters>({ const filters = ref<TicketFilters>({
byCurrentState: ["open"], byCurrentState: ["open"],
@@ -277,15 +282,18 @@ const filters = ref<TicketFilters>({
byCreatedBefore: "", byCreatedBefore: "",
byResponseTimeExceeded: false, byResponseTimeExceeded: false,
byAddresseeToMe: false, byAddresseeToMe: false,
byTicketId: null,
}); });
const byCreatedAfterTime = ref("00:00");
const byCreatedBeforeTime = ref("23:59");
// Sélection des personnes // Sélection des personnes
const selectedPersons = ref<Person[]>([]); const selectedPersons = ref<Person[]>([]);
const availablePersons = ref<Person[]>(props.availablePersons || []); const availablePersons = ref<Person[]>(props.availablePersons || []);
// Sélection des utilisateur assigné // Sélection des utilisateur assigné
const selectedAddressees = ref<UserGroupOrUser[]>([]); const selectedAddressees = ref<UserGroupOrUser[]>([]);
const userGroups = computed(() => store.getters.getUserGroups as UserGroup[]);
// Séléction des créateurs // Séléction des créateurs
const selectedCreator = ref<User[]>([]); const selectedCreator = ref<User[]>([]);
@@ -351,10 +359,10 @@ const handleEmergencyToggle = (value: boolean) => {
} }
}; };
// Méthodes const formatDateToISO = (dateString: string, timeString: string): string => {
const formatDateToISO = (dateString: string): string => { const [hours, minutes] = timeString.split(":").map(Number);
if (!dateString) return dateString;
const date = new Date(dateString); const date = new Date(dateString);
date.setHours(hours, minutes, 0, 0);
return date.toISOString(); return date.toISOString();
}; };
@@ -406,11 +414,17 @@ const applyFilters = (): void => {
} }
if (filters.value.byCreatedAfter) { if (filters.value.byCreatedAfter) {
apiFilters.byCreatedAfter = formatDateToISO(filters.value.byCreatedAfter); apiFilters.byCreatedAfter = formatDateToISO(
filters.value.byCreatedAfter,
byCreatedAfterTime.value,
);
} }
if (filters.value.byCreatedBefore) { if (filters.value.byCreatedBefore) {
apiFilters.byCreatedBefore = formatDateToISO(filters.value.byCreatedBefore); apiFilters.byCreatedBefore = formatDateToISO(
filters.value.byCreatedBefore,
byCreatedBeforeTime.value,
);
} }
if (filters.value.byResponseTimeExceeded) { if (filters.value.byResponseTimeExceeded) {
@@ -419,7 +433,12 @@ const applyFilters = (): void => {
if (filters.value.byAddresseeToMe) { if (filters.value.byAddresseeToMe) {
apiFilters.byAddresseeToMe = true; apiFilters.byAddresseeToMe = true;
} }
if (filters.value.byAddresseeToMe) {
apiFilters.byAddresseeToMe = true;
}
if (filters.value.byTicketId) {
apiFilters.byTicketId = filters.value.byTicketId;
}
emit("filters-changed", apiFilters); emit("filters-changed", apiFilters);
}; };
@@ -431,13 +450,14 @@ const resetFilters = (): void => {
byCreatedBefore: "", byCreatedBefore: "",
byResponseTimeExceeded: false, byResponseTimeExceeded: false,
byAddresseeToMe: false, byAddresseeToMe: false,
byTicketId: null,
}; };
selectedPersons.value = []; selectedPersons.value = [];
selectedCreator.value = []; selectedCreator.value = [];
selectedAddressees.value = []; selectedAddressees.value = [];
selectedMotives.value = []; selectedMotives.value = [];
selectedMotive.value = undefined; selectedMotive.value = undefined;
isClosedToggled.value = true; isClosedToggled.value = false;
isEmergencyToggled.value = false; isEmergencyToggled.value = false;
applyFilters(); applyFilters();
}; };
@@ -446,7 +466,7 @@ const handleResponseTimeExceededChange = (): void => {
if (filters.value.byResponseTimeExceeded) { if (filters.value.byResponseTimeExceeded) {
filters.value.byCreatedBefore = ""; filters.value.byCreatedBefore = "";
filters.value.byCreatedAfter = ""; filters.value.byCreatedAfter = "";
isClosedToggled.value = true; isClosedToggled.value = false;
} }
}; };
</script> </script>

View File

@@ -11,16 +11,6 @@
<div class="d-flex align-items-center fw-bold"> <div class="d-flex align-items-center fw-bold">
<i :class="`${actionIcons[history_line.event_type]} me-1`"></i> <i :class="`${actionIcons[history_line.event_type]} me-1`"></i>
<span>{{ explainSentence(history_line) }}</span> <span>{{ explainSentence(history_line) }}</span>
<span
v-if="
history_line.event_type === 'add_comment' &&
history_line.data.deleted
"
class="badge bg-danger ms-2"
>
{{ trans(CHILL_TICKET_TICKET_HISTORY_MASK_COMMENT) }}
</span>
<state-component <state-component
:new_state="history_line.data.new_state" :new_state="history_line.data.new_state"
v-if="history_line.event_type == 'state_change'" v-if="history_line.event_type == 'state_change'"
@@ -59,10 +49,7 @@
/> />
<comment-component <comment-component
:commentHistory="history_line.data" :commentHistory="history_line.data"
v-else-if=" v-else-if="history_line.event_type == 'add_comment'"
history_line.event_type == 'add_comment' &&
canBeDisplayed(history_line.data)
"
/> />
<addressee-component <addressee-component
:addressees="history_line.data.addressees" :addressees="history_line.data.addressees"
@@ -105,19 +92,24 @@ import {
CHILL_TICKET_TICKET_HISTORY_STATE_CHANGE, CHILL_TICKET_TICKET_HISTORY_STATE_CHANGE,
CHILL_TICKET_TICKET_HISTORY_EMERGENCY_CHANGE, CHILL_TICKET_TICKET_HISTORY_EMERGENCY_CHANGE,
CHILL_TICKET_TICKET_HISTORY_SET_CALLER, CHILL_TICKET_TICKET_HISTORY_SET_CALLER,
CHILL_TICKET_TICKET_HISTORY_MASK_COMMENT,
} from "translator"; } from "translator";
const props = defineProps<{ history?: TicketHistoryLine[] }>(); const props = defineProps<{ history?: TicketHistoryLine[] }>();
const history = props.history ?? []; const history = props.history ?? [];
const store = useStore(); const store = useStore();
const actionIcons = ref(store.getters.getActionIcons); const actionIcons = ref<Record<string, string>>(store.getters.getActionIcons);
const canBeDisplayed = ref(store.getters.canBeDisplayed); const removedCommentIds = ref<number[]>(store.getters.getRemovedCommentIds);
const filteredHistoryLines = computed(() => const filteredHistoryLines = computed(() =>
history.filter( history.filter(
(line: TicketHistoryLine) => !(line.event_type === "add_person"), (line: TicketHistoryLine) =>
line.event_type !== "add_person" &&
!(
line.event_type == "add_comment" &&
line.data.deleted &&
!removedCommentIds.value.includes(line.data.id)
),
), ),
); );

View File

@@ -24,6 +24,8 @@ chill_ticket:
addressees: "Par destinataire" addressees: "Par destinataire"
by_addressees: "Par destinataire" by_addressees: "Par destinataire"
by_motives: "Par motifs" by_motives: "Par motifs"
by_ticket_id: "Par numéro de ticket"
ticket_id: "Numéro de ticket"
current_state: "État actuel" current_state: "État actuel"
open: "Ouvert" open: "Ouvert"
closed: "Clôturé" closed: "Clôturé"
@@ -55,7 +57,7 @@ chill_ticket:
create_ticket: "Ticket créé" create_ticket: "Ticket créé"
state_change: "" state_change: ""
emergency_change: "" emergency_change: ""
mask_comment: "Masqué" mask_comment: "Supprimer"
previous_tickets: "Précédents tickets" previous_tickets: "Précédents tickets"
actions_toolbar: actions_toolbar:
cancel: "Annuler" cancel: "Annuler"
@@ -67,10 +69,10 @@ chill_ticket:
reopen_success: "Rouverture du ticket réussie" reopen_success: "Rouverture du ticket réussie"
reopen_error: "Erreur lors de la rouverture du ticket" reopen_error: "Erreur lors de la rouverture du ticket"
visible_comment: visible_comment:
success: "Commentaire visible" success: "Commentaire restauré"
mask_comment: mask_comment:
success: "Commentaire masqué" success: "Commentaire supprimé"
hint: "Ce commentaire est masqué; il n'est visible que par vous." hint: "Ce commentaire a été supprimé."
edit_comment: edit_comment:
title: "Éditer le commentaire" title: "Éditer le commentaire"
success: "Commentaire modifié" success: "Commentaire modifié"