11<template lang="pug">
2-
3- </template >
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"
16+ )
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
34+
35+ </template >
36+
37+ <script lang="ts" setup>
38+ import { ref , onMounted , computed } from ' vue'
39+ import { useRoute } from ' nuxt/app' ;
40+ 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 ()
53+
54+ const store = usePinia ()
55+ const user = store .state .value .auth .user
56+ const props = defineProps ({
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 ()
70+ })
71+
72+ const route = useRoute ()
73+ const query = ref ({
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
85+ })
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+ }, {})
152+ })
153+
154+ const message = ref (' ' )
155+ </script >
156+
157+ <style lang="css" scoped>
158+ .mainContent {
159+ height : calc (100% - 50px );
160+ }
161+ </style >
0 commit comments