1+ <template lang="pug">
2+ .column
3+ .col-10
4+ .row ( style ="height: 100%" )
5+ q-btn( icon ="mdi-paperclip" label ="Glissez vos fichiers ici"
6+ size ="md" : class = "isOverDropZone ? 'text-primary' : 'text-grey-5'" flat ref ="dropZoneRef"
7+ ) .col-1.bg-grey-2
8+ q-badge( floating ) {{ attachements.length }}
9+ //- client-only
10+ //- tk-tiptap-editor(v-model="message" ref="editorDialog")
11+ q-editor(
12+ v-model ="message" placeholder ="Votre message ..."
13+ :definitions ="editorDefinitions"
14+ :toolbar ="editorToolbar" dense style ="height: 100%"
15+ ) .col
16+ template( v-slot:threadTypes )
17+ q-btn-dropdown(
18+ v-model ="threadType" : options= "threadTypes"
19+ no-wrap unelevated no-caps dense flat
20+ label ="Type de thread" : icon= "threadType.icon" : color= "threadType.color"
21+ )
22+ q-list( dense )
23+ q-item( clickable v-for ="threadType in threadTypes" : key= "type.value" v-ripple tag ="label" )
24+ q-item-section
25+ q-item-label {{ type.label }}
26+ q-btn(
27+ icon ="mdi-send" size ="md" color ="primary" flat
28+ @click ="sendMessage"
29+ ) .col-1
30+ q-tooltip.text-body2 Envoyer
31+
32+ q-scroll-area( style ="width: 100%" ) .col
33+ q-virtual-scroll( :items ="attachements" virtual-scroll-horizontal v-slot ="{item}" )
34+ q-chip( :key ="item.id" icon ="mdi-paperclip" dense size ='md' : label= "item.name" removable @remove ="removeAttachment(item.id)" )
35+
36+ q-dialog( v-model ="isFullscreen" )
37+ q-card
38+ q-card-section
39+ q-editor(
40+ min-height ="50vh" min-width ="50vw"
41+ v-model ="message" placeholder ="Votre message ..."
42+ :definitions ="editorDefinitions"
43+ :toolbar ="editorToolbar" class ="q-pa-none"
44+ :disable ="disabled" ref ="dropZoneRef"
45+ )
46+ q-card-section.q-pa-sm
47+ div( ref ="dropZoneDialogRef" ) .row.center.bg-grey-3
48+ .col.text-center
49+ q-icon( name ="mdi-paperclip" size ="md" : class = "isOverDropZoneDialog ? 'text-primary' : 'text-grey-5'" )
50+ span.q-ml-md ( :class ="isOverDropZoneDialog ? 'text-primary' : 'text-grey-5'" ) Déposer un fichier
51+ 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)" )
53+
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
57+
58+ </template >
59+
60+ <script lang="ts" setup>
61+ import { ref , onMounted , computed } from ' vue'
62+ import { useRoute , useRouter } from ' nuxt/app' ;
63+ import { useHttpApi } from ' ~/composables' ;
64+ import { useDayjs , usePinia } from " #imports" ;
65+ import { generateMongoId } from ' ~/utils' ;
66+ import type { components } from ' #build/types/service-api'
67+ import { ThreadType , threadTypes } from ' ~/utils' ;
68+ import { useDropZone , useResizeObserver } from ' @vueuse/core'
69+ import { FsType } from ' ~/utils'
70+ import { useQuasar } from ' quasar' ;
71+ import ObjectID from ' bson-objectid' ;
72+
73+ type ThreadCreateDto = components [' schemas' ][' ThreadCreateDto' ]
74+ type FsPart = components [" schemas" ][" IdfsPartDto" ]
75+
76+ const dayjs = useDayjs ()
77+ const store = usePinia ()
78+ const route = useRoute ()
79+ const $q = useQuasar ()
80+ const user = store .state .value .auth .user
81+
82+ onMounted (() => {
83+ currentThreadId .value = generateMongoId ()
84+ })
85+
86+ const threadType = ref (threadTypes [0 ])
87+ const isFullscreen = ref (false )
88+ const message = ref (' ' )
89+ const dropZoneRef = ref <HTMLDivElement >()
90+ const dropZoneDialogRef = ref <HTMLDivElement >()
91+ const editorDialog = ref ()
92+ const currentThreadId = ref <ObjectID | null >(null )
93+ const attachements = ref <FsPart []>([])
94+
95+ const onDrop = (files : File [] | null ) => {
96+ if (! files ) return
97+ for (const file of files ) {
98+ uploadFile (file )
99+ }
100+ }
101+
102+ const { isOverDropZone } = useDropZone (dropZoneRef , onDrop )
103+ const { isOverDropZone : isOverDropZoneDialog } = useDropZone (dropZoneDialogRef , onDrop )
104+
105+ const uploadFile = async (file : File ) => {
106+ const formData = new FormData ()
107+ formData .append (' file' , file )
108+ formData .append (' namespace' , ' s3' )
109+ formData .append (' path' , ` /ticket/${route .params .id }/attachement/${currentThreadId }/${file .name } ` )
110+ formData .append (' type' , FsType .FILE )
111+ const { data, error } = await useHttpApi (` core/filestorage ` , {
112+ method: ' post' ,
113+ body: formData
114+ })
115+ console .log (error .value )
116+ if (error .value ) {
117+ $q .notify ({
118+ message: ' Impossible d\' envoyer le fichier' ,
119+ type: ' negative'
120+ })
121+ return
122+ }
123+ const fsPart: FsPart = {
124+ id: data .value ?.data ._id ,
125+ name: file .name ,
126+ namespace: data .value ?.data .namespace ,
127+ path: data .value ?.data .path ,
128+ mime: data .value ?.data .mime
129+ }
130+ attachements .value .push (fsPart )
131+ $q .notify (' Fichier envoyé' )
132+ }
133+
134+ const removeAttachment = (id : string ) => {
135+ const { data, error } = useHttpApi (` core/filestorage/${id } ` , {
136+ method: ' delete'
137+ })
138+ if (error .value ) {
139+ $q .notify ({
140+ message: ' Impossible de supprimer le fichier' ,
141+ type: ' negative'
142+ })
143+ return
144+ }
145+ attachements .value = attachements .value .filter (attachement => attachement .id !== id )
146+ $q .notify (' Fichier supprimé' )
147+ }
148+
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+ {
156+ id: generateMongoId (),
157+ disposition: ' raw' ,
158+ 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
172+ })
173+ if (error .value ) {
174+ $q .notify ({
175+ message: ' Impossible d\' envoyer le message' ,
176+ type: ' negative'
177+ })
178+ return
179+ }
180+ threads .value ?.data .push (thread .value ?.data )
181+ message .value = ' '
182+ scroll ()
183+ attachements .value = []
184+ $q .notify (' Message envoyé' )
185+
186+ }
187+
188+ const editorDefinitions = computed (() => (
189+ {
190+ send : {
191+ tip : ' Envoyer' ,
192+ icon : ' mdi-send' ,
193+ label : ' Envoyer' ,
194+ handler : sendMessage
195+ },
196+ attach : {
197+ tip : ' Joindre un fichier' ,
198+ icon : ' mdi-paperclip' ,
199+ label : ' Joindre un fichier' ,
200+ handler : () = > {
201+ console.log(' joindre' )
202+ }
203+ },
204+ fullscreen: {
205+ tip: ' Plein écran' ,
206+ icon: isFullscreen .value ? ' mdi-fullscreen-exit' : ' mdi-fullscreen' ,
207+ label: isFullscreen .value ? ' Quitter le plein écran' : ' Plein écran' ,
208+ handler : () => {
209+ isFullscreen .value = ! isFullscreen .value
210+ }
211+ }
212+ }))
213+
214+ const editorToolbar = computed (() => {
215+ return [[' left' , ' center' , ' right' , ' justify' ], [' bold' , ' italic' , ' underline' , ' strike' ], [' undo' , ' redo' ], [' fullscreen' ]]
216+ })
217+
218+ </script >
0 commit comments