Skip to content

Commit 5338d65

Browse files
thread page
1 parent 00511f2 commit 5338d65

File tree

4 files changed

+231
-106
lines changed

4 files changed

+231
-106
lines changed

app/src/components/ticket/leftPanel.vue

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@ div
55
q-breadcrumbs-el(to="/") Accueil
66
q-breadcrumbs-el(to="/tickets") Tickets
77
q-breadcrumbs-el(:label="sequence")
8-
q-card.bg-gray-4
9-
q-card-section
10-
.text-h6 Autres sections
11-
q-separator(inset)
12-
q-card-actions(align="center" vertical)
13-
q-btn(color="primary" flat) Mes tickets
14-
q-btn(color="primary" flat) Tout les tickets
15-
q-separator(inset)
16-
q-card-actions(align="center" vertical)
17-
q-btn(v-for="projet in projets.data" color="primary" flat :key="projet.id" :to="`/ticket/${projet.id}`") {{ projet.name }}
18-
8+
q-expansion-item(label="Autres sections").bg-gray-4
9+
q-card.bg-gray-4
10+
q-separator(inset)
11+
q-card-actions(align="center" vertical)
12+
q-btn(color="primary" flat) Mes tickets
13+
q-btn(color="primary" flat) Tout les tickets
14+
q-separator(inset)
15+
q-card-actions(align="center" vertical)
16+
q-btn(v-for="projet in projets.data" color="primary" flat :key="projet.id" :to="`/ticket/${projet.id}`") {{ projet.name }}
1917
</template>
2018

2119
<script lang="ts" setup>
Lines changed: 140 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,161 @@
11
<template lang="pug">
2-
q-card(style="height: 100%")
3-
q-toolbar
4-
q-toolbar-title {{ sequence }} | {{ subject }}
5-
q-card-section(style="height: auto")
6-
//- q-scroll-area(:style="{height: '100%'}")
7-
q-chat-message(v-for='(message, index) in thread.data' :key='message._id'
8-
:text="message.fragments.map(f => f.message)"
9-
:sent="index % 2 === 0"
2+
q-card(style="height: 100%").column
3+
q-toolbar.col-1
4+
q-toolbar-title {{ sequence }} | {{ subject }}
5+
q-scroll-area.col-8(ref="chatScroll")
6+
div(v-for='(value, key) in getMessageByDay' :key='key')
7+
q-chat-message
8+
template(v-slot:label)
9+
span {{ key }}
10+
q-separator(inset)
11+
div(v-for="(message, index) in value" :id="index").q-mx-md
12+
//- div(v-for="fragment in message.fragments")
13+
q-chat-message(
14+
:sent="message.metadata.createdBy === user.username"
15+
:name="message.metadata.createdBy" size="10"
1016
)
17+
template(v-slot:stamp)
18+
span {{ getTimeFrom(message.metadata.createdAt) }}
19+
q-tooltip.text-body2 {{ getHour(message.metadata.createdAt) }}
20+
template(v-slot:default)
21+
div
22+
q-chip(v-for='file in message.fragments.file' :key='file.id' icon="mdi-paperclip" dense size='md' :label="file.message")
23+
q-separator.q-my-xs(v-if="message.fragments.file")
24+
div(v-for='raw in message.fragments.raw' v-html="raw.message")
25+
26+
q-editor(
27+
v-model="message" placeholder="Votre message ..." v-model:fullscreen="isFullscreen"
28+
:definitions="{ send: { tip: 'Envoyer', icon: 'mdi-send', label: 'Envoyer', handler: sendMessage }, attach:{ tip: 'Joindre un fichier', icon: 'mdi-paperclip', label: 'Joindre un fichier', handler: () => {console.log('joindre')} }}"
29+
:toolbar="[['left','center','right','justify'],['bold','italic','underline','strike'],['undo','redo'],['attach','send']]"
30+
).col
31+
.col-1(ref="dropZoneRef").bg-grey-3.items-center.justify-center.q-pa-md
32+
q-icon(name="mdi-paperclip" size="md" :class="isOverDropZone ? 'text-primary' : 'text-grey-5'")
33+
span.q-ml-md(:class="isOverDropZone ? 'text-primary' : 'text-grey-5'") Déposer un fichier
1134

12-
q-card-section
13-
q-editor(v-model="message" dense)
1435
</template>
1536

1637
<script lang="ts" setup>
1738
import { ref, onMounted, computed } from 'vue'
1839
import { useRoute } from 'nuxt/app';
1940
import { useHttpApi } from '~/composables/useHttpApi';
41+
import { useDayjs, usePinia } from "#imports";
42+
import { generateMongoId } from '~/utils';
43+
import type { Fragments, Threads } from '~/types';
44+
import type { components } from '#build/types/service-api'
45+
import { ThreadType } from '~/utils';
46+
import { useDropZone } from '@vueuse/core'
47+
48+
type ThreadDto = components['schemas']['ThreadDto']
49+
type FragmentPartDto = components["schemas"]["FragmentPartDto"]
50+
type ThreadCreateDto = components['schemas']['ThreadCreateDto']
51+
52+
const dayjs = useDayjs()
2053
54+
const store = usePinia()
55+
const user = store.state.value.auth.user
2156
const props = defineProps({
22-
sequence: {
23-
type: String,
24-
required: true
25-
},
26-
subject: {
27-
type: String,
28-
required: true
29-
}
57+
sequence: {
58+
type: String,
59+
required: true
60+
},
61+
subject: {
62+
type: String,
63+
required: true
64+
}
65+
})
66+
67+
const chatScroll = ref(null)
68+
onMounted(() => {
69+
scroll()
3070
})
3171
3272
const route = useRoute()
3373
const query = ref({
34-
"filters[ticketId]": `${route.params.id}`
74+
"filters[ticketId]": `${route.params.id}`,
75+
"sort[metadata.createdAt]": 'asc',
76+
"limit": 100,
77+
})
78+
79+
const isFullscreen = ref(false)
80+
81+
const dropZoneRef = ref<HTMLDivElement>()
82+
const { data: threads } = await useHttpApi(`tickets/thread`, {
83+
method: 'get',
84+
query
3585
})
36-
const { data: thread } = await useHttpApi(`tickets/thread`, {
37-
method: 'get',
38-
query
86+
const onDrop = (files: File[] | null) => {
87+
console.log('drop')
88+
console.log(files)
89+
}
90+
const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
91+
92+
const getTimeFrom = (time: string) => {
93+
return dayjs().to(dayjs(time))
94+
}
95+
96+
const getHour = (time: string) => {
97+
return dayjs(time).format('DD-MM-YYYY HH:mm')
98+
}
99+
100+
const scroll = () => {
101+
const target = chatScroll.value?.getScrollTarget()
102+
chatScroll.value?.setScrollPosition('vertical', target.scrollHeight, 0)
103+
}
104+
105+
const getThreadFragments = (thread: ThreadDto): Fragments => {
106+
return thread.fragments.reduce((acc: Fragments, fragment) => {
107+
const disposition = fragment.disposition
108+
if (!acc[disposition]) acc[disposition] = []
109+
acc[disposition]?.push(fragment)
110+
return acc as Fragments
111+
}, {})
112+
}
113+
114+
const sendMessage = async () => {
115+
const payload: ThreadCreateDto = {
116+
ticketId: generateMongoId(route.params.id.toString()),
117+
fragments: [
118+
{
119+
id: generateMongoId(),
120+
disposition: 'raw',
121+
message: message.value
122+
}
123+
],
124+
metadata: {
125+
createdBy: user.username,
126+
createdAt: dayjs().toISOString(),
127+
lastUpdatedAt: dayjs().toISOString(),
128+
lastUpdatedBy: user.username
129+
},
130+
type: ThreadType.OUTGOING
131+
}
132+
const { data: thread } = await useHttpApi(`tickets/thread`, {
133+
method: 'post',
134+
body: payload
135+
})
136+
threads.value?.data.push(thread.value?.data)
137+
message.value = ''
138+
scroll()
139+
}
140+
141+
const getMessageByDay = computed((): Threads => {
142+
return threads.value?.data.reduce((acc: Threads, thread: ThreadDto) => {
143+
const day = dayjs(thread.metadata.createdAt).format('DD-MM-YYYY')
144+
const newTread = {
145+
...thread,
146+
fragments: getThreadFragments(thread)
147+
}
148+
if (!acc[day]) acc[day] = []
149+
acc[day].push(newTread)
150+
return acc
151+
}, {})
39152
})
40153
41154
const message = ref('')
42155
</script>
43156

157+
<style lang="css" scoped>
158+
.mainContent {
159+
height: calc(100% - 50px);
160+
}
161+
</style>

app/src/components/ticket/rightPanel.vue

Lines changed: 78 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,97 @@
11
<template lang="pug">
22
q-scroll-area(:style="{height: '100%'}")
33
q-card
4-
q-list
5-
q-expansion-item(label="Enveloppe")
6-
q-card
7-
q-card-section
8-
q-select.q-my-xs(
9-
label="Appelant(s)" filled v-model="ticket.envelope.senders"
10-
option-label="name"
11-
use-input use-chips multiple
12-
new-value-mode="add-unique"
13-
)
14-
q-select.q-my-xs(
15-
option-label="name"
16-
label="Appelant(s)" filled v-model="ticket.envelope.observers"
17-
use-input use-chips multiple
18-
new-value-mode="add-unique"
19-
)
20-
q-select.q-my-xs(
21-
option-label="name"
22-
label="Appelant(s)" filled v-model="ticket.envelope.assigned"
23-
use-input use-chips multiple
24-
new-value-mode="add-unique"
25-
)
26-
q-expansion-item(label="Informations")
27-
q-card
28-
q-card-section
29-
.row.items-center
30-
.col-6 Type de ticket :
31-
.col-6
32-
q-chip(:icon="typeOfTicket.icon" :color="typeOfTicket.color" outline).q-mx-auto {{ typeOfTicket.label }}
33-
q-select.q-my-xs(
34-
label="Projet(s)" filled v-model="ticket.project"
35-
option-label="name"
36-
use-input use-chips
37-
new-value-mode="add-unique"
38-
)
39-
q-select.q-my-xs(
40-
label="Priorité" filled
41-
option-label="name"
42-
use-input use-chips
43-
new-value-mode="add-unique"
44-
v-model="ticket.priority"
45-
)
46-
q-select.q-my-xs(
47-
label="Impact" filled
48-
option-label="name"
49-
use-input use-chips
50-
new-value-mode="add-unique"
51-
v-model="ticket.impact"
52-
)
53-
q-select.q-my-xs(
54-
label="SLA" filled
55-
option-label="name"
56-
use-input use-chips
57-
new-value-mode="add-unique"
58-
v-model="ticket.sla"
59-
)
60-
q-input.q-my-xs( label="Due date" type="date" filled v-model="dueDate")
61-
q-input.q-my-xs( label="Temps total" type="time" filled readonly v-model="totalTime")
62-
q-expansion-item(label="Cycle de vie")
63-
q-card
64-
q-card-section
65-
.row.items-center
66-
.col-6 Etape de vie :
67-
.col-6
68-
q-chip(:icon="lifestepOfTicket?.icon" :color="lifestepOfTicket?.color" outline).q-mx-auto {{ lifestepOfTicket?.label }}
69-
.row.items-center
70-
.col-6 State :
71-
.col-6
72-
q-chip(:icon="stateOfTicket?.icon" :color="stateOfTicket?.color" outline).q-mx-auto {{ stateOfTicket?.name }}
4+
q-toolbar.justify-end
5+
q-btn-group
6+
q-btn(color="primary" icon="mdi-printer" @click="console.log('Imprimer')" size="md")
7+
q-tooltip.text-body2 Imprimer
8+
q-btn(color="info" icon="mdi-content-save-all" @click="console.log('Save')" size="md")
9+
q-tooltip.text-body2 Sauvegarder
10+
q-btn(color="red" icon="mdi-arrow-left" @click="console.log(router.go(-1))" size="md")
11+
q-tooltip.text-body2 Retour
12+
q-expansion-item(label="Enveloppe").bg-gray-4
13+
q-card
14+
q-card-section
15+
q-select.q-my-xs(
16+
label="Appelant(s)" filled v-model="ticket.envelope.senders"
17+
option-label="name"
18+
use-input use-chips multiple
19+
new-value-mode="add-unique"
20+
)
21+
q-select.q-my-xs(
22+
option-label="name"
23+
label="Appelant(s)" filled v-model="ticket.envelope.observers"
24+
use-input use-chips multiple
25+
new-value-mode="add-unique"
26+
)
27+
q-select.q-my-xs(
28+
option-label="name"
29+
label="Appelant(s)" filled v-model="ticket.envelope.assigned"
30+
use-input use-chips multiple
31+
new-value-mode="add-unique"
32+
)
33+
q-expansion-item(label="Informations").bg-gray-4
34+
q-card
35+
q-card-section
36+
.row.items-center
37+
.col-6 Type de ticket :
38+
.col-6
39+
q-chip(:icon="typeOfTicket.icon" :color="typeOfTicket.color" outline).q-mx-auto {{ typeOfTicket.label }}
40+
q-select.q-my-xs(
41+
label="Projet(s)" filled v-model="ticket.project"
42+
option-label="name"
43+
use-input use-chips
44+
new-value-mode="add-unique"
45+
)
46+
q-select.q-my-xs(
47+
label="Priorité" filled
48+
option-label="name"
49+
use-input use-chips
50+
new-value-mode="add-unique"
51+
v-model="ticket.priority"
52+
)
53+
q-select.q-my-xs(
54+
label="Impact" filled
55+
option-label="name"
56+
use-input use-chips
57+
new-value-mode="add-unique"
58+
v-model="ticket.impact"
59+
)
60+
q-select.q-my-xs(
61+
label="SLA" filled
62+
option-label="name"
63+
use-input use-chips
64+
new-value-mode="add-unique"
65+
v-model="ticket.sla"
66+
)
67+
q-input.q-my-xs( label="Due date" type="date" filled v-model="dueDate")
68+
q-input.q-my-xs( label="Temps total" type="time" filled readonly v-model="totalTime")
69+
q-expansion-item(label="Cycle de vie").bg-gray-4
70+
q-card
71+
q-card-section
72+
.row.items-center
73+
.col-6 Etape de vie :
74+
.col-6
75+
q-chip(:icon="lifestepOfTicket?.icon" :color="lifestepOfTicket?.color" outline).q-mx-auto {{ lifestepOfTicket?.label }}
76+
.row.items-center
77+
.col-6 State :
78+
.col-6
79+
q-chip(:icon="stateOfTicket?.icon" :color="stateOfTicket?.color" outline).q-mx-auto {{ stateOfTicket?.name }}
7380
</template>
7481

7582
<script lang="ts" setup>
7683
import { ref, onMounted, computed, inject } from 'vue'
7784
import { ticketType, lifeSteps, useDayjs } from '#imports';
7885
import { useHttpApi } from '~/composables/useHttpApi';
86+
import { useRouter } from 'vue-router';
7987
const props = defineProps({
8088
ticketData: {
8189
type: Object,
8290
required: true
8391
}
8492
})
8593
const dayjs = useDayjs()
94+
const router = useRouter()
8695
const ticket = ref(props.ticketData)
8796
const { data: states, pending: statesPending, refresh: statesRefresh, error: statesError } = await useHttpApi('/tickets/state', {
8897
method: 'get'

0 commit comments

Comments
 (0)