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
}
interface PersonHistory {
export interface PersonHistory {
type: "ticket_person_history",
id: number,
startDate: DateTime,
@ -32,7 +32,7 @@ interface PersonHistory {
createdAt: DateTime|null
}
interface MotiveHistory {
export interface MotiveHistory {
type: "ticket_motive_history",
id: number,
startDate: null,
@ -42,7 +42,7 @@ interface MotiveHistory {
createdAt: DateTime|null,
}
interface Comment {
export interface Comment {
type: "ticket_comment",
id: number,
content: string,

View File

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

View File

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

View File

@ -1,38 +1,55 @@
<template>
<div class="btn-group">
<button type="button" class="btn btn-light dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
{{ $t('motive.label') }}
<span class="visually-hidden">{{ $t('motive.label') }}</span>
</button>
<ul class="dropdown-menu">
<li v-for="motive in motives" :key="motive.label.fr"><button class="dropdown-item" type="button">
{{ motive.label.fr }}
</button>
</li>
</ul>
<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" />
</div>
</div>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue';
import { PropType, defineComponent, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import VueMultiselect from "vue-multiselect";
// Types
import { Motive } from '../../../types';
import { Motive } from "../../../types";
export default defineComponent({
name: 'MotiveSelectorComponent',
name: "MotiveSelectorComponent",
props: {
motives: {
type: Object as PropType<Motive[]>,
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>

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 class="badge bg-white text-black mx-1">{{ history_line.by.username }}</span>
</div>
<div class="card-body row fst-italic" v-if="history_line.event_type == 'add_person'">
<div class="col-12">
<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="card-body row fst-italic">
<div class="col-12">
<i class="fa fa-eyedropper"></i>
<span class="mx-1">
{{ $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 }) }}
{{ $t('history.user', { username: history_line.by.username }) }}
</span>
</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>
</template>
@ -34,10 +28,22 @@
import { PropType, defineComponent } from 'vue';
import { DateTime } from '../../../../../../../ChillMainBundle/Resources/public/types';
// Types
import { Ticket } from '../../../types';
// Components
import TicketHistoryPersonComponent from './TicketHistoryPersonComponent.vue';
import TicketHistoryMotiveComponent from './TicketHistoryMotiveComponent.vue';
import TicketHistoryCommentComponent from './TicketHistoryCommentComponent.vue';
export default defineComponent({
name: 'TicketHistoryListComponent',
components: {
TicketHistoryPersonComponent,
TicketHistoryMotiveComponent,
TicketHistoryCommentComponent,
},
props: {
history: {
type: Array as PropType<Ticket["history"]>,
@ -46,11 +52,10 @@ export default defineComponent({
},
setup() {
function formatDate(d: DateTime) {
const date = new Date(d.datetime);
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 }

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>
<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">
{{ $t('ticket.previous_tickets') }}
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-chill-green">
@ -8,11 +8,6 @@
</span>
</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>
</template>
@ -32,7 +27,10 @@ export default defineComponent({
},
},
setup() {
return {}
function handleClick() {
alert('Sera disponible plus tard')
}
return { handleClick }
}
});
</script>

View File

@ -1,27 +1,36 @@
export const messages = {
import { multiSelectMessages } from"../../../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
const messages = {
fr: {
ticket: {
previous_tickets: "Précédents tickets",
},
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}",
motive: "Motif indiqué: {motive}",
comment: "Commentaire: {comment}",
},
comment: {
title: "Commentaire",
label: "Ajouter un commentaire",
save: "Enregistrer",
succcess: "Commentaire enregistré",
},
motive: {
title: "Motif",
label: "Ajouter un motif",
label: "Choisir un motif",
save: "Enregistrer",
success: "Motif enregistré",
},
transfert: {
title: "Transfert",
label: "Transferer vers",
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 { State as MotiveStates, moduleMotive } from "./modules/motive";
import { State as TicketStates, moduleTicket } from "./modules/ticket";
import { State as CommentStates, moduleComment } from "./modules/comment";
export type RootState = {
motive: MotiveStates;
ticket: TicketStates;
comment: CommentStates;
};
export const store = createStore({
modules: {
motive:moduleMotive,
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 { RootState } from "..";
@ -9,31 +12,55 @@ export interface State {
motives: Array<Motive>;
}
export const moduleMotive: Module<State, RootState> ={
export const moduleMotive: Module<State, RootState> = {
state: () => ({
motives: [] as Array<Motive>,
}),
getters: {
getMotives(state) {
return state.motives;
}
},
getMotiveOptions(state) {
return state.motives.map((motive) => ({
value: motive.id,
text: motive.label.fr,
}));
},
},
mutations: {
setMotives(state, motives) {
state.motives = motives;
}
},
},
actions: {
async fetchMotives({ commit }) {
const results = await fetchResults("/api/1.0/ticket/motive.json") as Motive[];
commit("setMotives", results);
return results;
try {
const results = (await fetchResults(
"/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() }}
</h2>
<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>
</div>

View File

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