Add comment and motive

This commit is contained in:
Boris Waaub 2024-05-02 00:50:33 +02:00
parent 85bdfb9e21
commit a27d92aba0
15 changed files with 359 additions and 118 deletions

View File

@ -21,7 +21,7 @@ interface TicketHistory<T extends string, D extends object> {
data: D data: D
} }
interface PersonHistory { export interface PersonHistory {
type: "ticket_person_history", type: "ticket_person_history",
id: number, id: number,
startDate: DateTime, startDate: DateTime,
@ -32,7 +32,7 @@ interface PersonHistory {
createdAt: DateTime|null createdAt: DateTime|null
} }
interface MotiveHistory { export interface MotiveHistory {
type: "ticket_motive_history", type: "ticket_motive_history",
id: number, id: number,
startDate: null, startDate: null,
@ -42,7 +42,7 @@ interface MotiveHistory {
createdAt: DateTime|null, createdAt: DateTime|null,
} }
interface Comment { export interface Comment {
type: "ticket_comment", type: "ticket_comment",
id: number, id: number,
content: string, content: string,

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="container-xxl p-3"> <div class="container-xxl p-3" style="padding-bottom: 55px;">
<ticket-selector-component :tickets="[]" /> <ticket-selector-component :tickets="[]" />
<ticket-history-list-component :history="ticket.history" /> <ticket-history-list-component :history="ticket.history" />
</div> </div>

View File

@ -1,65 +1,70 @@
<template> <template>
<div class="fixed-bottom"> <div class="fixed-bottom">
<div class="footer-ticket-details"> <div class="footer-ticket-details">
<div class="tab-content"> <div class="tab-content p-2">
<div v-if="activeTab === 'commentaire'" class="p-2">
<label class="col-form-label" for="content">{{ $t('comment.title') }}</label>
<textarea v-model="comment" class="form-control" id="content" rows="3"
:placeholder="$t('comment.label')">
</textarea>
<div class="d-flex justify-content-end p-2">
<button class="btn btn-chill-green text-white float-right" type="submit">
<i class="fa fa-pencil"></i>
{{ $t('comment.save') }}
</button>
</div>
<div v-if="activeTab">
<label class="col-form-label">
{{ $t(`${activeTab}.title`) }}
</label>
</div> </div>
<div v-if="activeTab === 'transfert'" class="p-2">
<label class="col-form-label" for="content">{{ $t('transfert.title') }}</label>
<div v-if="activeTab === 'comment'">
<form @submit.prevent="createComment">
<textarea v-model="content" class="form-control" id="content" rows="3"
:placeholder="$t('comment.label')"></textarea>
<div class="d-flex justify-content-end p-2">
<button class="btn btn-chill-green text-white float-right" type="submit">
<i class="fa fa-pencil"></i>
{{ $t("comment.save") }}
</button>
</div>
</form>
</div>
<div v-if="activeTab === 'transfert'">
<div class="d-flex justify-content-end p-2"> <div class="d-flex justify-content-end p-2">
<button class="btn btn-chill-green text-white float-right" type="submit"> <button class="btn btn-chill-green text-white float-right" type="submit">
<i class="fa fa-pencil"></i> <i class="fa fa-pencil"></i>
{{ $t('transfert.save') }} {{ $t("transfert.save") }}
</button> </button>
</div> </div>
</div> </div>
<div v-if="activeTab === 'motif'" class="p-2">
<label class="col-form-label" for="content">{{ $t('motive.title') }}</label> <div v-if="activeTab === 'motive'">
<div> <form @submit.prevent="createMotive">
<MotiveSelectorComponent :motives="motives" />
</div> <motive-selector-component v-model="motive" :motives="motives"
<div class="d-flex justify-content-end p-2"> :current-motive="ticket.currentMotive" />
<button class="btn btn-chill-green text-white float-right" type="submit"> <div class="d-flex justify-content-end p-2">
<i class="fa fa-pencil"></i> <button class="btn btn-chill-green text-white float-right" type="submit">
{{ $t('motive.save') }} <i class="fa fa-pencil"></i>
</button> {{ $t("motive.save") }}
</div> </button>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>
<div class="footer-ticket-main"> <div class="footer-ticket-main">
<ul class="nav nav-tabs justify-content-end"> <ul class="nav nav-tabs justify-content-end">
<li class="nav-item"> <li class="nav-item">
<button type="button" class="m-2 btn btn-light" @click="activeTab = 'commentaire'"> <button type="button" class="m-2 btn btn-light" @click="activeTab = 'comment'">
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
Commentaire {{ $t('comment.title') }}
</button> </button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<button type="button" class="m-2 btn btn-light" @click="activeTab = 'transfert'"> <button type="button" class="m-2 btn btn-light" @click="activeTab = 'transfert'">
<i class="fa fa-paper-plane"></i> <i class="fa fa-paper-plane"></i>
Transfert {{ $t('transfert.title') }}
</button> </button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<button type="button" class="m-2 btn btn-light" @click="activeTab = 'motif'"> <button type="button" class="m-2 btn btn-light" @click="activeTab = 'motive'">
<i class="fa fa-paint-brush"></i> <i class="fa fa-paint-brush"></i>
Motif {{ $t('motive.title') }}
</button> </button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
@ -73,42 +78,82 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, ref } from 'vue'; import { defineComponent, inject, ref } from "vue";
import { useStore } from 'vuex'; import { useI18n } from "vue-i18n";
import MotiveSelectorComponent from './MotiveSelectorComponent.vue'; import { useStore } from "vuex";
import { Motive } from '../../../types';
// Types
import { Comment, Motive, Ticket } from "../../../types";
// Component
import MotiveSelectorComponent from "./MotiveSelectorComponent.vue";
export default defineComponent({ export default defineComponent({
name: 'ActionToolbarComponent', name: "ActionToolbarComponent",
components: { components: {
MotiveSelectorComponent MotiveSelectorComponent,
}, },
setup(props, ctx) { setup() {
const store = useStore(); const store = useStore();
const editor = ref(); const { t } = useI18n();
const toast = inject('toast') as any;
const activeTab = ref(""); const activeTab = ref("");
const comment = ref("");
const motives = computed(() => store.getters.getMotives as Motive[])
return {
motives,
activeTab,
comment,
editor,
};
const ticket = ref(store.getters.getTicket as Ticket);
const motives = ref(store.getters.getMotives as Motive[]);
const motive = ref({} as Motive);
const content = ref('' as Comment["content"]);
async function createMotive() {
try {
await store.dispatch("createMotive", {
ticketId: ticket.value.id,
motive: motive.value,
});
toast.success(t("motive.success"))
} catch (error) {
toast.error(error)
}
}
async function createComment() {
try {
await store.dispatch("createComment", {
ticketId: ticket.value.id,
content: content.value,
});
content.value = "";
toast.success(t("comment.success"))
} catch (error) {
toast.error(error)
}
}
return {
activeTab,
ticket,
motives,
motive,
content,
createMotive,
createComment,
};
}, },
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
div.fixed-bottom { div.fixed-bottom {
div.footer-ticket-main { div.footer-ticket-main {
background: none repeat scroll 0 0 #CABB9F; background: none repeat scroll 0 0 #cabb9f;
} }
div.footer-ticket-details { div.footer-ticket-details {
background: none repeat scroll 0 0 #EFE2CA; background: none repeat scroll 0 0 #efe2ca;
} }
} }
</style> </style>

View File

@ -1,38 +1,55 @@
<template> <template>
<div class="btn-group"> <div class="row">
<button type="button" class="btn btn-light dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"> <div class="col-12 col-lg-4 col-md-6">
{{ $t('motive.label') }} <vue-multiselect name="selectMotive" id="selectMotive" label="label" :custom-label="customLabel"
<span class="visually-hidden">{{ $t('motive.label') }}</span> track-by="id" open-direction="top" :multiple="false" :searchable="true"
</button> :placeholder="$t('motive.label')" :select-label="$t('multiselect.select_label')"
<ul class="dropdown-menu"> :deselect-label="$t('multiselect.deselect_label')" :selected-label="$t('multiselect.selected_label')"
<li v-for="motive in motives" :key="motive.label.fr"><button class="dropdown-item" type="button"> :options="motives" v-model="motive" />
{{ motive.label.fr }} </div>
</button>
</li>
</ul>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { PropType, defineComponent, ref, watch } from "vue";
import { PropType, defineComponent } from 'vue'; import { useI18n } from "vue-i18n";
import VueMultiselect from "vue-multiselect";
// Types // Types
import { Motive } from '../../../types'; import { Motive } from "../../../types";
export default defineComponent({ export default defineComponent({
name: 'MotiveSelectorComponent', name: "MotiveSelectorComponent",
props: { props: {
motives: { motives: {
type: Object as PropType<Motive[]>, type: Object as PropType<Motive[]>,
required: true, required: true,
}, },
currentMotive: {
type: Object as PropType<null|Motive>,
required: false,
},
},
components: {
VueMultiselect,
},
emits: ["update:modelValue"],
setup(props, ctx) {
const motive = ref(props.currentMotive ? props.currentMotive : {} as Motive);
const { t } = useI18n();
watch(motive, (motive) => {
ctx.emit("update:modelValue", motive);
});
return {
motive,
customLabel(motive: Motive) {
return motive.label ? motive.label.fr : t('motive.label');
},
};
}, },
setup() {
return {}
}
}); });
</script> </script>

View File

@ -0,0 +1,29 @@
<template>
<div class="col-12">
<i class="fa fa-comment"></i>
<span class="mx-1">
{{ $t('history.comment',{ comment: commentHistory.content}) }}
</span>
</div>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue';
// Types
import { Comment } from '../../../types';
export default defineComponent({
name: 'TicketHistoryCommentComponent',
props: {
commentHistory: {
type: Object as PropType<Comment>,
required: true,
},
},
setup() {}
});
</script>
<style lang="scss" scoped></style>

View File

@ -6,26 +6,20 @@
</span> </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>
<div class="card-body row fst-italic" v-if="history_line.event_type == 'add_person'">
<div class="col-12"> <div class="card-body row fst-italic">
<i class="fa fa-bolt" style="min-width: 16px;"></i>
<span class="mx-1">
{{ $t('history.person',{ text: history_line.data.person.text }) }}
</span>
</div>
<div class="col-12"> <div class="col-12">
<i class="fa fa-eyedropper"></i> <i class="fa fa-eyedropper"></i>
<span class="mx-1"> <span class="mx-1">
{{ $t('history.user',{ username: history_line.by.username }) }} {{ $t('history.user', { username: history_line.by.username }) }}
</span>
</div>
<div class="col-12">
<i class="fa fa-paint-brush"></i>
<span class="mx-1">
{{ $t('history.motive',{ text: history_line.data.person.text }) }}
</span> </span>
</div> </div>
<ticket-history-person-component :personHistory="history_line.data"
v-if="history_line.event_type == 'add_person'" />
<ticket-history-motive-component :motiveHistory="history_line.data"
v-else-if="history_line.event_type == 'set_motive'" />
<ticket-history-comment-component :commentHistory="history_line.data"
v-else-if="history_line.event_type == 'add_comment'" />
</div> </div>
</div> </div>
</template> </template>
@ -34,10 +28,22 @@
import { PropType, defineComponent } from 'vue'; import { PropType, defineComponent } from 'vue';
import { DateTime } from '../../../../../../../ChillMainBundle/Resources/public/types'; import { DateTime } from '../../../../../../../ChillMainBundle/Resources/public/types';
// Types
import { Ticket } from '../../../types'; import { Ticket } from '../../../types';
// Components
import TicketHistoryPersonComponent from './TicketHistoryPersonComponent.vue';
import TicketHistoryMotiveComponent from './TicketHistoryMotiveComponent.vue';
import TicketHistoryCommentComponent from './TicketHistoryCommentComponent.vue';
export default defineComponent({ export default defineComponent({
name: 'TicketHistoryListComponent', name: 'TicketHistoryListComponent',
components: {
TicketHistoryPersonComponent,
TicketHistoryMotiveComponent,
TicketHistoryCommentComponent,
},
props: { props: {
history: { history: {
type: Array as PropType<Ticket["history"]>, type: Array as PropType<Ticket["history"]>,
@ -46,11 +52,10 @@ export default defineComponent({
}, },
setup() { setup() {
function formatDate(d: DateTime) { function formatDate(d: DateTime) {
const date = new Date(d.datetime); const date = new Date(d.datetime);
const month = date.toLocaleString('default', { month: 'long' }); const month = date.toLocaleString('default', { month: 'long' });
return `${date.getDate()} ${month} ${date.getFullYear()}, ${date.getHours()}:${date.getMinutes()}` return `${date.getDate()} ${month} ${date.getFullYear()}, ${date.toLocaleTimeString()}`
} }
return { formatDate } return { formatDate }

View File

@ -0,0 +1,30 @@
<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>
</div>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue';
// Types
import { MotiveHistory } from '../../../types';
export default defineComponent({
name: 'TicketHistoryMotiveComponent',
props: {
motiveHistory: {
type: Object as PropType<MotiveHistory>,
required: true,
},
},
setup() {}
});
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,29 @@
<template>
<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 }) }}
</span>
</div>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue';
// Type
import { PersonHistory } from '../../../types';
export default defineComponent({
name: 'TicketHistoryPersonComponent',
props: {
personHistory: {
type: Object as PropType<PersonHistory>,
required: true,
},
},
setup() {}
});
</script>
<style lang="scss" scoped></style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="btn-group"> <div class="btn-group" @click="handleClick">
<button type="button" class="btn btn-light dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"> <button type="button" class="btn btn-light dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
{{ $t('ticket.previous_tickets') }} {{ $t('ticket.previous_tickets') }}
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-chill-green"> <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-chill-green">
@ -8,11 +8,6 @@
</span> </span>
</button> </button>
<ul class="dropdown-menu">
<li><button class="dropdown-item" type="button">Action</button></li>
<li><button class="dropdown-item" type="button">Another action</button></li>
<li><button class="dropdown-item" type="button">Something else here</button></li>
</ul>
</div> </div>
</template> </template>
@ -32,7 +27,10 @@ export default defineComponent({
}, },
}, },
setup() { setup() {
return {} function handleClick() {
alert('Sera disponible plus tard')
}
return { handleClick }
} }
}); });
</script> </script>

View File

@ -1,27 +1,36 @@
export const messages = { import { multiSelectMessages } from"../../../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
const messages = {
fr: { fr: {
ticket: { ticket: {
previous_tickets: "Précédents tickets", previous_tickets: "Précédents tickets",
}, },
history: { history: {
person: "Ouverture par appel téléphonique de {text}", person: "Ouverture par appel téléphonique de {person}",
user: "Prise en charge par {username}", user: "Prise en charge par {username}",
motive: "Motif indiqué: {motive}", motive: "Motif indiqué: {motive}",
comment: "Commentaire: {comment}",
}, },
comment: { comment: {
title: "Commentaire", title: "Commentaire",
label: "Ajouter un commentaire", label: "Ajouter un commentaire",
save: "Enregistrer", save: "Enregistrer",
succcess: "Commentaire enregistré",
}, },
motive: { motive: {
title: "Motif", title: "Motif",
label: "Ajouter un motif", label: "Choisir un motif",
save: "Enregistrer", save: "Enregistrer",
success: "Motif enregistré",
}, },
transfert: { transfert: {
title: "Transfert", title: "Transfert",
label: "Transferer vers", label: "Transferer vers",
save: "Enregistrer", save: "Enregistrer",
success: "Transfert effectué",
}, },
close: "Fermer"
}, },
}; };
Object.assign(messages.fr, multiSelectMessages.fr);
export default messages;

View File

@ -1,15 +1,19 @@
import { createStore } from "vuex"; import { createStore } from "vuex";
import { State as MotiveStates, moduleMotive } from "./modules/motive"; import { State as MotiveStates, moduleMotive } from "./modules/motive";
import { State as TicketStates, moduleTicket } from "./modules/ticket"; import { State as TicketStates, moduleTicket } from "./modules/ticket";
import { State as CommentStates, moduleComment } from "./modules/comment";
export type RootState = { export type RootState = {
motive: MotiveStates; motive: MotiveStates;
ticket: TicketStates; ticket: TicketStates;
comment: CommentStates;
}; };
export const store = createStore({ export const store = createStore({
modules: { modules: {
motive:moduleMotive, motive:moduleMotive,
ticket:moduleTicket, ticket:moduleTicket,
comment:moduleComment,
} }
}); });

View File

@ -0,0 +1,40 @@
import {
fetchResults,
makeFetch,
} from "../../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import { Module } from "vuex";
import { RootState } from "..";
import { Comment } from "../../../../types";
export interface State {
comments: Array<Comment>;
}
export const moduleComment: Module<State, RootState> = {
state: () => ({
comments: [] as Array<Comment>,
}),
getters: {},
mutations: {},
actions: {
async createComment(
{ commit },
datas: { ticketId: number; content: Comment["content"] }
) {
const { ticketId, content } = datas;
try {
const result = await makeFetch(
"POST",
`/api/1.0/ticket/${ticketId}/comment/add`,
{ content }
);
commit("setTicket", result);
}
catch(e: any) {
throw e.name;
}
},
},
};

View File

@ -1,4 +1,7 @@
import { fetchResults, makeFetch } from "../../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; import {
fetchResults,
makeFetch,
} from "../../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import { Module } from "vuex"; import { Module } from "vuex";
import { RootState } from ".."; import { RootState } from "..";
@ -9,31 +12,55 @@ export interface State {
motives: Array<Motive>; motives: Array<Motive>;
} }
export const moduleMotive: Module<State, RootState> ={ export const moduleMotive: Module<State, RootState> = {
state: () => ({ state: () => ({
motives: [] as Array<Motive>, motives: [] as Array<Motive>,
}), }),
getters: { getters: {
getMotives(state) { getMotives(state) {
return state.motives; return state.motives;
} },
getMotiveOptions(state) {
return state.motives.map((motive) => ({
value: motive.id,
text: motive.label.fr,
}));
},
}, },
mutations: { mutations: {
setMotives(state, motives) { setMotives(state, motives) {
state.motives = motives; state.motives = motives;
} },
}, },
actions: { actions: {
async fetchMotives({ commit }) { async fetchMotives({ commit }) {
const results = await fetchResults("/api/1.0/ticket/motive.json") as Motive[]; try {
commit("setMotives", results); const results = (await fetchResults(
return results; "/api/1.0/ticket/motive.json"
)) as Motive[];
commit("setMotives", results);
}
catch(e: any) {
throw e.name;
}
},
async createMotive(
{ commit },
datas: { ticketId: number; motive: Motive }
) {
const { ticketId, motive } = datas;
try {
const result = await makeFetch(
"POST",
`/api/1.0/ticket/${ticketId}/motive/set`,
{ motive }
);
commit("setTicket", result);
}
catch(e: any) {
throw e.name;
}
}, },
async createMotive({ commit }, datas: {currentMotiveId: number, motive: Motive}) {
const { currentMotiveId, motive } = datas;
const result = await makeFetch("POST", `/api/1.0/ticket/${currentMotiveId}/motive/set`, motive);
commit("setMotives", result);
return result;
}
}, },
}; };

View File

@ -7,7 +7,14 @@
#{{ ticket.getExternalRef() }} #{{ ticket.getExternalRef() }}
</h2> </h2>
<h1> <h1>
{{ticket.getMotive()}} {% if ticket.getMotive() %}
{{ ticket.getMotive().label.fr }}
{% else %}
{{ ticket.getMotive() }}
<p class="chill-no-data-statement">
{{ "no_motive"|trans }}
</p>
{% endif %}
</h1> </h1>
</div> </div>

View File

@ -3,3 +3,4 @@ caller: Appelant
speaker: Intervenant speaker: Intervenant
open: Ouvert open: Ouvert
since: Depuis since: Depuis
no_motive: Pas de motif