Skip to content

Commit caeaaba

Browse files
separation threads
1 parent 737a4ee commit caeaaba

File tree

7 files changed

+311
-74
lines changed

7 files changed

+311
-74
lines changed

app/src/components/threads/Editor.vue

Lines changed: 69 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//- tk-tiptap-editor(v-model="message" ref="editorDialog")
1111
q-editor(
1212
v-model="message" placeholder="Votre message ..."
13-
:definitions="editorDefinitions"
13+
:definitions="editorDefinitions" :disable="isDisabledTicket"
1414
:toolbar="editorToolbar" dense style="height: 100%"
1515
).col
1616
template(v-slot:threadTypes)
@@ -25,7 +25,7 @@
2525
q-item-label {{ type.label }}
2626
q-btn(
2727
icon="mdi-send" size="md" color="primary" flat
28-
@click="sendMessage"
28+
@click="isFullscreen = true"
2929
).col-1
3030
q-tooltip.text-body2 Envoyer
3131

@@ -35,30 +35,40 @@
3535

3636
q-dialog(v-model="isFullscreen")
3737
q-card
38+
q-card-section.bg-grey-2
39+
q-input(dense label="From" v-model="mailInfo.from")
40+
q-input(dense label="To" v-model="mailInfo.to")
41+
q-input(dense label="Copy" v-model="mailInfo.cc")
42+
q-input(dense label="Subject" v-model="mailInfo.subject")
3843
q-card-section
3944
q-editor(
4045
min-height="50vh" min-width="50vw"
4146
v-model="message" placeholder="Votre message ..."
4247
:definitions="editorDefinitions"
4348
:toolbar="editorToolbar" class="q-pa-none"
44-
:disable="disabled" ref="dropZoneRef"
49+
:readonly="isDisabledTicket" ref="dropZoneRef"
4550
)
4651
q-card-section.q-pa-sm
4752
div(ref="dropZoneDialogRef").row.center.bg-grey-3
4853
.col.text-center
4954
q-icon(name="mdi-paperclip" size="md" :class="isOverDropZoneDialog ? 'text-primary' : 'text-grey-5'")
5055
span.q-ml-md(:class="isOverDropZoneDialog ? 'text-primary' : 'text-grey-5'") Déposer un fichier
5156
q-card-section
52-
q-chip(v-for="attachement in attachements" :key="attachement.id" icon="mdi-paperclip" dense size='md' :label="attachement.name" removable @remove="removeAttachment(attachement.id)")
57+
q-scroll-area(style="width: 100%; height: 100%")
58+
q-virtual-scroll(:items="attachements" virtual-scroll-horizontal v-slot="{item}")
59+
q-chip(v-for="attachement in attachements" :key="attachement.id" icon="mdi-paperclip" dense size='md' :label="attachement.name" removable @remove="removeAttachment(attachement.id)")
5360

54-
//- .col-1(ref="dropZoneRef").bg-grey-3.items-center.justify-center.q-pa-md
55-
//- q-icon(name="mdi-paperclip" size="md" :class="isOverDropZone ? 'text-primary' : 'text-grey-5'")
56-
//- span.q-ml-md(:class="isOverDropZone ? 'text-primary' : 'text-grey-5'") Déposer un fichier
61+
.row
62+
q-btn(label="Envoyer en note interne" color="primary" icon="mdi-note" @click="sendMessage(ThreadType.INTERNAL)").col-6
63+
q-btn(label="Envoyer par mail" color="primary" icon="mdi-email" @click="sendMessage(ThreadType.OUTGOING)").col-6
64+
//- .col-1(ref="dropZoneRef").bg-grey-3.items-center.justify-center.q-pa-md
65+
//- q-icon(name="mdi-paperclip" size="md" :class="isOverDropZone ? 'text-primary' : 'text-grey-5'")
66+
//- span.q-ml-md(:class="isOverDropZone ? 'text-primary' : 'text-grey-5'") Déposer un fichier
5767
5868
</template>
5969

6070
<script lang="ts" setup>
61-
import { ref, onMounted, computed } from 'vue'
71+
import { ref, onMounted, computed, inject } from 'vue'
6272
import { useRoute, useRouter } from 'nuxt/app';
6373
import { useHttpApi } from '~/composables';
6474
import { useDayjs, usePinia } from "#imports";
@@ -72,6 +82,9 @@ import ObjectID from 'bson-objectid';
7282
7383
type ThreadCreateDto = components['schemas']['ThreadCreateDto']
7484
type FsPart = components["schemas"]["IdfsPartDto"]
85+
type MailinfoPartDto = components["schemas"]["MailinfoPartDto"]
86+
87+
const emit = defineEmits(['refreshThreadsList'])
7588
7689
const dayjs = useDayjs()
7790
const store = usePinia()
@@ -91,6 +104,12 @@ const dropZoneDialogRef = ref<HTMLDivElement>()
91104
const editorDialog = ref()
92105
const currentThreadId = ref<ObjectID | null>(null)
93106
const attachements = ref<FsPart[]>([])
107+
const mailInfo = ref({
108+
from: '',
109+
to: '',
110+
cc: '',
111+
subject: ''
112+
})
94113
95114
const onDrop = (files: File[] | null) => {
96115
if (!files) return
@@ -131,6 +150,14 @@ const uploadFile = async (file: File) => {
131150
$q.notify('Fichier envoyé')
132151
}
133152
153+
const emailReponse = (data: MailinfoPartDto) => {
154+
mailInfo.value.to = data.from.address
155+
mailInfo.value.from = data.to[0].address
156+
mailInfo.value.subject = data.subject.startsWith('Re:') ? data.subject : `Re:${data.subject}`
157+
isFullscreen.value = true
158+
console.log(data)
159+
}
160+
134161
const removeAttachment = (id: string) => {
135162
const { data, error } = useHttpApi(`core/filestorage/${id}`, {
136163
method: 'delete'
@@ -146,53 +173,52 @@ const removeAttachment = (id: string) => {
146173
$q.notify('Fichier supprimé')
147174
}
148175
149-
const sendMessage = async () => {
150-
const payload: ThreadCreateDto = {
151-
_id: currentThreadId,
152-
attachments: attachements.value,
153-
ticketId: generateMongoId(route.params.id.toString()),
154-
fragments: [
155-
{
176+
const sendMessage = (type: ThreadType = ThreadType.OUTGOING) => {
177+
const { data: thread, error } = useHttpApi(`tickets/thread`, {
178+
method: 'post',
179+
body: {
180+
_id: currentThreadId.value,
181+
attachments: attachements.value,
182+
ticketId: generateMongoId(route.params.id.toString()),
183+
fragments: [{
156184
id: generateMongoId(),
157185
disposition: 'raw',
158186
message: message.value
159-
}
160-
],
161-
metadata: {
162-
createdBy: user.username,
163-
createdAt: dayjs().toISOString(),
164-
lastUpdatedAt: dayjs().toISOString(),
165-
lastUpdatedBy: user.username
166-
},
167-
type: ThreadType.OUTGOING
168-
}
169-
const { data: thread, error } = await useHttpApi(`tickets/thread`, {
170-
method: 'post',
171-
body: payload
187+
}],
188+
metadata: {
189+
createdBy: user.username,
190+
createdAt: dayjs().toISOString(),
191+
lastUpdatedAt: dayjs().toISOString(),
192+
lastUpdatedBy: user.username
193+
},
194+
type
195+
}
172196
})
197+
173198
if (error.value) {
174199
$q.notify({
175200
message: 'Impossible d\'envoyer le message',
176201
type: 'negative'
177202
})
178203
return
179204
}
180-
threads.value?.data.push(thread.value?.data)
205+
181206
message.value = ''
182-
scroll()
183207
attachements.value = []
208+
currentThreadId.value = generateMongoId()
209+
isFullscreen.value = false
184210
$q.notify('Message envoyé')
185-
211+
emit('refreshThreadsList')
186212
}
187213
188214
const editorDefinitions = computed(() => (
189215
{
190-
send: {
191-
tip: 'Envoyer',
192-
icon: 'mdi-send',
193-
label: 'Envoyer',
194-
handler: sendMessage
195-
},
216+
// send: {
217+
// tip: 'Envoyer',
218+
// icon: 'mdi-send',
219+
// label: 'Envoyer',
220+
// handler: sendMessage
221+
// },
196222
attach: {
197223
tip: 'Joindre un fichier',
198224
icon: 'mdi-paperclip',
@@ -215,4 +241,10 @@ const editorToolbar = computed(() => {
215241
return [['left', 'center', 'right', 'justify'], ['bold', 'italic', 'underline', 'strike'], ['undo', 'redo'], ['fullscreen']]
216242
})
217243
244+
const isDisabledTicket = inject('isDisabledTicket')
245+
246+
defineExpose({
247+
emailReponse
248+
})
249+
218250
</script>

app/src/components/threads/List.vue

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,22 @@ q-scroll-area(ref="chatScroll")
99
q-separator(inset)
1010

1111
div(v-for="(message, index) in value" :id="message._id").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"
12+
component(
13+
:is="getThreadHookName(message.type)"
14+
:data="message"
15+
@email:response="emailReponse($event)"
1616
)
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='attachment in message.attachments' :key='attachment._id' icon="mdi-paperclip" text-color="white" color="primary" dense size='md' :label="attachment.name")
23-
q-separator.q-my-xs(v-if="message.fragments.file")
24-
div(v-for='raw in message.fragments.raw' v-html="raw.message")
2517
</template>
2618

2719
<script lang="ts" setup>
2820
import { ref, onMounted, computed } from 'vue'
2921
import { useRoute, useRouter } from 'nuxt/app';
3022
import { useHttpApi } from '~/composables';
3123
import { useDayjs, usePinia } from "#imports";
32-
import { generateMongoId } from '~/utils';
3324
import type { Fragments, Threads } from '~/types';
3425
import type { components } from '#build/types/service-api'
35-
import { FsType } from '~/utils'
3626
import { useQuasar } from 'quasar';
27+
import { ThreadType } from '../../utils';
3728
3829
type ThreadDto = components['schemas']['ThreadDto']
3930
@@ -46,13 +37,20 @@ const user = store.state.value.auth.user
4637
onMounted(() => {
4738
scroll()
4839
})
40+
41+
const emit = defineEmits(['email:response'])
42+
const emailReponse = (data: ThreadDto) => {
43+
emit('email:response', data)
44+
}
45+
4946
const chatScroll = ref(null)
5047
const baseQuery = ref({
5148
"filters[ticketId]": `${route.params.id}`,
5249
"sort[metadata.createdAt]": 'asc',
5350
"limit": 999,
5451
})
55-
const { data: threads } = await useHttpApi(`tickets/thread`, {
52+
53+
const { data: threads, refresh, error } = await useHttpApi(`tickets/thread`, {
5654
method: 'get',
5755
query: computed(() => {
5856
return {
@@ -62,6 +60,35 @@ const { data: threads } = await useHttpApi(`tickets/thread`, {
6260
})
6361
})
6462
63+
if (error.value) {
64+
$q.notify({
65+
message: 'Impossible de charger les messages',
66+
type: 'negative'
67+
})
68+
}
69+
70+
71+
const threadsRefresh = async () => {
72+
await refresh()
73+
scroll()
74+
}
75+
76+
const getThreadHookName = (type: ThreadType) => {
77+
const baseUrl = 'tk-threadsTypes'
78+
switch (type) {
79+
case ThreadType.EXTERNAL:
80+
case ThreadType.INTERNAL:
81+
case ThreadType.OUTGOING:
82+
return `${baseUrl}Base`
83+
case ThreadType.INCOMING:
84+
return `${baseUrl}Mail`
85+
case ThreadType.SYSTEM:
86+
return `${baseUrl}System`
87+
default:
88+
return `${baseUrl}Base`
89+
}
90+
}
91+
6592
const getMessageByDay = computed((): Threads => {
6693
return threads.value?.data.reduce((acc: Threads, thread: ThreadDto) => {
6794
const day = dayjs(thread.metadata.createdAt).format('DD-MM-YYYY')
@@ -90,14 +117,9 @@ const scroll = () => {
90117
chatScroll.value?.setScrollPosition('vertical', target.scrollHeight, 0)
91118
}
92119
93-
94-
const getTimeFrom = (time: string) => {
95-
return dayjs().to(dayjs(time))
96-
}
97-
98-
const getHour = (time: string) => {
99-
return dayjs(time).format('DD-MM-YYYY HH:mm')
100-
}
101-
120+
defineExpose({
121+
scroll,
122+
threadsRefresh,
123+
})
102124
103125
</script>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<template lang="pug">
2+
div(v-for="fragment in props.data.fragments")
3+
q-chat-message(
4+
:sent="props.data.metadata.createdBy === user.username"
5+
:name="props.data.metadata.createdBy" size="10"
6+
)
7+
template(v-slot:stamp)
8+
.row.items-center
9+
q-icon(name="mdi-email").q-mx-sm
10+
span {{ getTimeFrom(props.data.metadata.createdAt) }}
11+
q-tooltip.text-body2 {{ getHour(props.data.metadata.createdAt) }}
12+
template(v-slot:default)
13+
div
14+
q-chip(v-for='attachment in props.data.attachments' :key='attachment._id' icon="mdi-paperclip" text-color="white" color="primary" dense size='md' :label="attachment.name")
15+
q-separator.q-my-xs(v-if="props.data.fragments.file")
16+
div(v-for='raw in props.data.fragments.raw' v-html="raw.message")
17+
</template>
18+
19+
<script lang="ts" setup>
20+
import type { components } from '#build/types/service-api'
21+
import { useRoute } from 'nuxt/app';
22+
import { useDayjs, usePinia } from "#imports";
23+
import { useQuasar } from 'quasar';
24+
type ThreadDto = components['schemas']['ThreadDto']
25+
26+
const props = defineProps<{
27+
data: ThreadDto
28+
}>()
29+
30+
const dayjs = useDayjs()
31+
const store = usePinia()
32+
const route = useRoute()
33+
const $q = useQuasar()
34+
const user = store.state.value.auth.user
35+
36+
37+
const getTimeFrom = (time: string) => {
38+
return dayjs().to(dayjs(time))
39+
}
40+
41+
const getHour = (time: string) => {
42+
return dayjs(time).format('DD-MM-YYYY HH:mm')
43+
}
44+
45+
</script>

0 commit comments

Comments
 (0)