Generalize ticket actions

This commit is contained in:
Boris Waaub 2024-05-22 00:38:47 +02:00
parent c73c1eb8d5
commit a20f9b4f86
15 changed files with 213 additions and 138 deletions

View File

@ -36,6 +36,7 @@ export default defineComponent({
const motives = computed(() => store.getters.getMotives as Motive[]);
const ticket = computed(() => store.getters.getTicket as Ticket);
const ticketHistory = computed(
() => store.getters.getDistinctAddressesHistory
);

View File

@ -11,20 +11,20 @@
<form @submit.prevent="submitAction">
<add-comment-component
v-model="content"
v-if="activeTab === 'comment'"
v-if="activeTab === 'add_comment'"
/>
<addressee-selector-component
v-model="addressees"
:user-groups="userGroups"
:users="users"
v-if="activeTab === 'transfert'"
v-if="activeTab === 'add_addressee'"
/>
<motive-selector-component
v-model="motive"
:motives="motives"
v-if="activeTab === 'motive'"
v-if="activeTab === 'set_motive'"
/>
<ul class="record_actions sticky-form-buttons">
@ -52,13 +52,13 @@
type="button"
class="m-2 btn btn-light"
@click="
activeTab === 'comment'
activeTab === 'add_comment'
? (activeTab = '')
: (activeTab = 'comment')
: (activeTab = 'add_comment')
"
>
<i class="fa fa-comment"></i>
{{ $t("comment.title") }}
<i :class="actionIcons['add_comment']"></i>
{{ $t("add_comment.title") }}
</button>
</li>
<li class="nav-item">
@ -66,13 +66,13 @@
type="button"
class="m-2 btn btn-light"
@click="
activeTab === 'transfert'
activeTab === 'add_addressee'
? (activeTab = '')
: (activeTab = 'transfert')
: (activeTab = 'add_addressee')
"
>
<i class="fa fa-paper-plane"></i>
{{ $t("transfert.title") }}
<i :class="actionIcons['add_addressee']"></i>
{{ $t("add_addressee.title") }}
</button>
</li>
<li class="nav-item">
@ -80,13 +80,13 @@
type="button"
class="m-2 btn btn-light"
@click="
activeTab === 'motive'
activeTab === 'set_motive'
? (activeTab = '')
: (activeTab = 'motive')
: (activeTab = 'set_motive')
"
>
<i class="fa fa-paint-brush"></i>
{{ $t("motive.title") }}
<i :class="actionIcons['set_motive']"></i>
{{ $t("set_motive.title") }}
</button>
</li>
<li class="nav-item">
@ -133,7 +133,9 @@ export default defineComponent({
const store = useStore();
const { t } = useI18n();
const toast = inject("toast") as any;
const activeTab = ref("" as "" | "comment" | "motive" | "transfert");
const activeTab = ref(
"" as "" | "add_comment" | "set_motive" | "add_addressee"
);
const ticket = computed(() => store.getters.getTicket as Ticket);
const motives = computed(() => store.getters.getMotives as Motive[]);
@ -155,9 +157,9 @@ export default defineComponent({
async function submitAction() {
try {
switch (activeTab.value) {
case "comment":
case "add_comment":
if (!content.value) {
toast.error(t("comment.error"));
toast.error(t("add_comment.error"));
} else {
await store.dispatch("createComment", {
ticketId: ticket.value.id,
@ -165,31 +167,31 @@ export default defineComponent({
});
content.value = "";
activeTab.value = "";
toast.success(t("comment.success"));
toast.success(t("add_comment.success"));
}
break;
case "motive":
case "set_motive":
if (!motive.value.id) {
toast.error(t("motive.error"));
toast.error(t("set_motive.error"));
} else {
await store.dispatch("createMotive", {
ticketId: ticket.value.id,
motive: motive.value,
});
activeTab.value = "";
toast.success(t("motive.success"));
toast.success(t("set_motive.success"));
}
break;
case "transfert":
case "add_addressee":
if (!addressees.value.length) {
toast.error(t("transfert.error"));
toast.error(t("add_addressee.error"));
} else {
await store.dispatch("setAdressees", {
ticketId: ticket.value.id,
addressees: addressees.value,
});
activeTab.value = "";
toast.success(t("transfert.success"));
toast.success(t("add_addressee.success"));
}
break;
}
@ -203,6 +205,7 @@ export default defineComponent({
}
return {
actionIcons: ref(store.getters.getActionIcons),
activeTab,
ticket,
motives,

View File

@ -3,7 +3,7 @@
<div class="col-12">
<ckeditor
name="content"
:placeholder="$t('comment.content')"
:placeholder="$t('add_comment.content')"
:editor="editor"
v-model="content"
tag-name="textarea"

View File

@ -29,7 +29,7 @@
</template>
<script lang="ts">
import { PropType, defineComponent, ref } from "vue";
import { PropType, computed, defineComponent, ref } from "vue";
// Types
import {
@ -37,7 +37,6 @@ import {
UserGroup,
UserGroupOrUser,
} from "../../../../../../../ChillMainBundle/Resources/public/types";
import { Ticket } from "../../../types";
export default defineComponent({
name: "AddresseeComponent",
@ -48,23 +47,27 @@ export default defineComponent({
},
},
setup(props, ctx) {
const userGroups = ref(
props.addressees.filter(
(addressee) =>
addressee.type == "user_group" && addressee.excludeKey == ""
) as UserGroup[]
const userGroups = computed(
() =>
props.addressees.filter(
(addressee) =>
addressee.type == "user_group" &&
addressee.excludeKey == ""
) as UserGroup[]
);
const userGroupLevels = ref(
props.addressees.filter(
(addressee) =>
addressee.type == "user_group" &&
addressee.excludeKey == "level"
) as UserGroup[]
const userGroupLevels = computed(
() =>
props.addressees.filter(
(addressee) =>
addressee.type == "user_group" &&
addressee.excludeKey == "level"
) as UserGroup[]
);
const users = ref(
props.addressees.filter(
(addressee) => addressee.type == "user"
) as User[]
const users = computed(
() =>
props.addressees.filter(
(addressee) => addressee.type == "user"
) as User[]
);
return { userGroups, users, userGroupLevels };
},

View File

@ -66,8 +66,8 @@
<add-persons
:options="addPersonsOptions"
key="add-person-ticket"
buttonTitle="transfert.user_label"
modalTitle="transfert.user_label"
buttonTitle="add_addressee.user_label"
modalTitle="add_addressee.user_label"
ref="addPersons"
@addNewPersons="addNewEntity"
/>
@ -121,10 +121,12 @@ export default defineComponent({
emits: ["update:modelValue"],
setup(props, ctx) {
const addressees = ref(props.modelValue as UserGroupOrUser[]);
const userGroups = props.modelValue.filter(
(addressee) => addressee.type == "user_group"
) as UserGroup[];
const addressees = ref([...props.modelValue] as UserGroupOrUser[]);
const userGroups = [
...props.modelValue.filter(
(addressee) => addressee.type == "user_group"
),
] as UserGroup[];
const userGroupLevel = ref(
userGroups.filter(
@ -135,10 +137,9 @@ export default defineComponent({
userGroups.filter((userGroup) => userGroup.excludeKey == "") as
| UserGroup[]
);
const users = ref(
props.modelValue.filter((addressee) => addressee.type == "user") as
| User[]
);
const users = ref([
...props.modelValue.filter((addressee) => addressee.type == "user"),
] as User[]);
const addPersons = ref();
const { t } = useI18n();
@ -184,24 +185,26 @@ export default defineComponent({
}
watch(userGroupLevel, (userGroupLevelAdd, userGroupLevelRem) => {
if (userGroupLevelRem) {
addressees.value.splice(
addressees.value.indexOf(userGroupLevelRem as UserGroup),
1
);
const index = addressees.value.indexOf(
userGroupLevelRem as UserGroup
);
if (index !== -1) {
addressees.value.splice(index, 1);
}
addressees.value.push(userGroupLevelAdd as UserGroup);
ctx.emit("update:modelValue", addressees.value);
});
watch(userGroup, (userGroupAdd) => {
const userGroups = addressees.value.filter(
(addressee) => addressee.type === "user_group"
const userGroupLevel = addressees.value.filter(
(addressee) =>
addressee.type == "user_group" &&
addressee.excludeKey == "level"
) as UserGroup[];
addressees.value = userGroups.filter(
(addressee) => addressee.excludeKey !== ""
);
addressees.value = [...addressees.value, ...userGroupAdd];
const users = addressees.value.filter(
(addressee) => addressee.type == "user"
) as UserGroup[];
addressees.value = [...users, ...userGroupLevel, ...userGroupAdd];
ctx.emit("update:modelValue", addressees.value);
});
@ -218,7 +221,7 @@ export default defineComponent({
customUserGroupLabel(selectedUserGroup: UserGroup) {
return selectedUserGroup.label
? selectedUserGroup.label.fr
: t("transfert.user_group_label");
: t("add_addresseeuser_group_label");
},
};
},

View File

@ -13,16 +13,18 @@
</div>
<div class="col-md-6 col-sm-12">
<div class="float-end">
<div class="d-flex justify-content-end">
<h1>
<span class="badge text-bg-chill-green text-white">
{{ $t("banner.open") }}
</span>
</h1>
</div>
<div class="d-flex justify-content-end">
<h3 class="fst-italic" v-if="ticket.createdAt">
{{
$t("banner.since", {
count: getSince(ticket.createdAt),
time: since,
})
}}
</h3>
@ -68,17 +70,14 @@
</template>
<script lang="ts">
import { PropType, defineComponent, ref, watch } from "vue";
import { PropType, computed, defineComponent, ref } from "vue";
import { useI18n } from "vue-i18n";
// Components
import PersonRenderBox from "../../../../../../../ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue";
import AddresseeComponent from "./AddresseeComponent.vue";
// Types
import {
User,
UserGroup,
} from "../../../../../../../ChillMainBundle/Resources/public/types";
import { Ticket } from "../../../types";
export default defineComponent({
@ -93,16 +92,46 @@ export default defineComponent({
PersonRenderBox,
AddresseeComponent,
},
setup() {
function getSince(createdAt: any) {
const today = new Date();
const date = new Date(createdAt.date);
setup(props) {
const { t } = useI18n();
const today = ref(new Date());
const createdAt = ref(props.ticket.createdAt as any);
const timeDiff = Math.abs(today.getTime() - date.getTime());
const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24));
return daysDiff;
}
return { getSince };
setInterval(function () {
today.value = new Date();
}, 5000);
const since = computed(() => {
const date = new Date(createdAt.value.date);
const timeDiff = Math.abs(today.value.getTime() - date.getTime());
const daysDiff = Math.floor(timeDiff / (1000 * 3600 * 24));
const hoursDiff = Math.floor(
(timeDiff % (1000 * 3600 * 24)) / (1000 * 3600)
);
const minutesDiff = Math.floor(
(timeDiff % (1000 * 3600)) / (1000 * 60)
);
if (daysDiff < 1) {
return `${t("banner.hours", { count: hoursDiff })} ${t(
"banner.and"
)} ${t("banner.minutes", {
count: minutesDiff,
})}`;
} else {
return `${t("banner.days", { count: daysDiff })}, ${t(
"banner.hours",
{
count: hoursDiff,
}
)} ${t("banner.and")} ${t("banner.minutes", {
count: minutesDiff,
})}`;
}
});
return { since };
},
});
</script>

View File

@ -1,11 +1,22 @@
<template>
<div class="row">
<div class="col-12 col-lg-4 col-md-6">
<vue-multiselect name="selectMotive" id="selectMotive" label="label" :custom-label="customLabel"
track-by="id" open-direction="top" :multiple="false" :searchable="true"
:placeholder="$t('motive.label')" :select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')" :selected-label="$t('multiselect.selected_label')"
:options="motives" v-model="motive" />
<vue-multiselect
name="selectMotive"
id="selectMotive"
label="label"
:custom-label="customLabel"
track-by="id"
open-direction="top"
:multiple="false"
:searchable="true"
:placeholder="$t('set_motive.label')"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
:options="motives"
v-model="motive"
/>
</div>
</div>
</template>
@ -46,7 +57,7 @@ export default defineComponent({
return {
motive,
customLabel(motive: Motive) {
return motive.label ? motive.label.fr : t('motive.label');
return motive.label ? motive.label.fr : t("set_motive.label");
},
};
},

View File

@ -1,12 +1,6 @@
<template>
<div class="col-12">
<i class="fa fa-comment"></i>
<span class="mx-1">
{{ $t("history.comment") }}
</span>
<div class="mt-2">
<div v-html="convertMarkdownToHtml(commentHistory.content)"></div>
</div>
<div v-html="convertMarkdownToHtml(commentHistory.content)"></div>
</div>
</template>
@ -27,7 +21,6 @@ export default defineComponent({
},
},
setup() {
const preprocess = (markdown: string): string => {
return markdown;
};
@ -62,6 +55,4 @@ export default defineComponent({
});
</script>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>

View File

@ -6,12 +6,13 @@
>
<template v-if="!Array.isArray(history_line)">
<div class="card-header">
<span class="fw-bold fst-italic">
<i :class="`${actionIcons[history_line.event_type]} me-1`"></i>
<span class="fw-bold fst-italic mx-1">
{{ formatDate(history_line.at) }}
</span>
<span class="badge bg-white text-black mx-1">{{
history_line.by.username
}}</span>
<span class="badge bg-white text-black mx-1">
{{ history_line.by.username }}
</span>
</div>
<div class="card-body row">
<ticket-history-person-component
@ -52,10 +53,11 @@
</template>
<script lang="ts">
import { PropType, defineComponent } from "vue";
import { DateTime } from "../../../../../../../ChillMainBundle/Resources/public/types";
import { PropType, defineComponent, ref } from "vue";
import { useStore } from "vuex";
// Types
import { DateTime } from "../../../../../../../ChillMainBundle/Resources/public/types";
import { Ticket } from "../../../types";
// Components
@ -80,13 +82,15 @@ export default defineComponent({
},
setup() {
const store = useStore();
function formatDate(d: DateTime) {
const date = new Date(d.datetime);
const month = date.toLocaleString("default", { month: "long" });
return `${date.getDate()} ${month} ${date.getFullYear()}, ${date.toLocaleTimeString()}`;
}
return { formatDate };
return { actionIcons: ref(store.getters.getActionIcons), formatDate };
},
});
</script>

View File

@ -1,21 +1,17 @@
<template>
<div class="col-12">
<i class="fa fa-paint-brush"></i>
<span class="mx-1">
{{ $t('history.motive',{ motive: motiveHistory.motive.label.fr }) }}
</span>
{{ motiveHistory.motive.label.fr }}
</div>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue';
import { PropType, defineComponent } from "vue";
// Types
import { MotiveHistory } from '../../../types';
import { MotiveHistory } from "../../../types";
export default defineComponent({
name: 'TicketHistoryMotiveComponent',
name: "TicketHistoryMotiveComponent",
props: {
motiveHistory: {
type: Object as PropType<MotiveHistory>,
@ -23,7 +19,7 @@ export default defineComponent({
},
},
setup() {}
setup() {},
});
</script>

View File

@ -1,21 +1,40 @@
<template>
<div class="col-12" v-if="personHistory.createdBy">
<i class="fa fa-eyedropper"></i>
<span class="mx-1">
{{ $t("history.user", { username: personHistory.createdBy.username }) }}
{{ $t("history.user") }}
<span class="badge bg-primary m-1">
{{ personHistory.createdBy.username }}
</span>
</span>
</div>
<div class="col-12">
<i class="fa fa-bolt" style="min-width: 16px"></i>
<span class="mx-1">
{{ $t("history.person", { person: personHistory.person.text }) }}
{{ $t("history.person") }}
</span>
<person-render-box
render="badge"
:key="personHistory.person.id"
:person="personHistory.person"
:options="{
addLink: true,
addId: false,
addAltNames: false,
addEntity: true,
addInfo: true,
hLevel: 3,
isMultiline: true,
isConfidential: false,
}"
/>
</div>
</template>
<script lang="ts">
import { PropType, defineComponent } from "vue";
// Components
import PersonRenderBox from "../../../../../../../ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue";
// Type
import { PersonHistory, Ticket } from "../../../types";
@ -27,6 +46,9 @@ export default defineComponent({
required: true,
},
},
components: {
PersonRenderBox,
},
setup() {},
});

View File

@ -10,29 +10,23 @@ const messages = {
close: "Fermer",
},
history: {
person: "Ouverture par appel téléphonique de {person}",
user: "Prise en charge par {username}",
motive: "Motif indiqué: {motive}",
comment: "Commentaire",
add_addressee_user_group: "Groupe {user_group} transferé",
remove_addressee_user_group: "Groupe {user_group} retiré",
add_addressee_user: " Utilisateur {user} Transferé",
remove_addressee_user: "Utilisateur {user} retiré",
person: "Ouverture par appel téléphonique de ",
user: "Prise en charge par ",
},
comment: {
add_comment: {
title: "Commentaire",
label: "Ajouter un commentaire",
success: "Commentaire enregistré",
content: "Ajouter un commentaire",
error: "Aucun commentaire ajouté",
},
motive: {
set_motive: {
title: "Motif",
label: "Choisir un motif",
success: "Motif enregistré",
error: "Aucun motif sélectionné",
},
transfert: {
add_addressee: {
title: "Transfert",
user_group_label: "Transferer vers un groupe",
user_label: "Transferer vers un ou plusieurs utilisateurs",
@ -43,7 +37,11 @@ const messages = {
concerned_patient: "Patient concerné",
speaker: "Destinataire(s)",
open: "Ouvert",
since: "Aucun jour | Depuis 1 jour | Depuis {count} jours",
since: "Depuis {time}",
and: "et",
days: "1 jour | {count} jours",
hours: "1 heure | {count} heures",
minutes: "1 minute | {count} minutes",
no_motive: "Pas de motif",
},
},

View File

@ -2,21 +2,20 @@ import { createStore } from "vuex";
import { State as MotiveStates, moduleMotive } from "./modules/motive";
import { State as TicketStates, moduleTicket } from "./modules/ticket";
import { State as CommentStates, moduleComment } from "./modules/comment";
import { State as UserStates, moduleUser } from "./modules/user";
import { State as UserStates, moduleUser } from "./modules/addressee";
export type RootState = {
motive: MotiveStates;
ticket: TicketStates;
comment: CommentStates;
user: UserStates;
};
};
export const store = createStore({
modules: {
motive:moduleMotive,
ticket:moduleTicket,
comment:moduleComment,
user:moduleUser,
}
motive: moduleMotive,
ticket: moduleTicket,
comment: moduleComment,
user: moduleUser,
},
});

View File

@ -5,16 +5,31 @@ import { Ticket } from "../../../../types";
export interface State {
ticket: Ticket;
action_icons: Object;
}
export const moduleTicket: Module<State, RootState> = {
state: () => ({
ticket: {} as Ticket,
action_icons: {
add_person: "fa fa-eyedropper",
add_comment: "fa fa-comment",
set_motive: "fa fa-paint-brush",
add_addressee: "fa fa-paper-plane",
},
toto: "toto",
}),
getters: {
getTicket(state) {
state.ticket.history = state.ticket.history.sort((a, b) =>
b.at.datetime.localeCompare(a.at.datetime)
);
return state.ticket;
},
getActionIcons(state) {
return state.action_icons;
},
getDistinctAddressesHistory(state) {
const addresseeHistory = state.ticket.history.reduce(
(result, item) => {