@rkosafo/cai.components 0.0.39 → 0.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/layout/Chat/CategorySelector.svelte +52 -0
- package/dist/layout/Chat/CategorySelector.svelte.d.ts +4 -0
- package/dist/layout/Chat/ChatEntry.svelte +246 -0
- package/dist/layout/Chat/ChatEntry.svelte.d.ts +4 -0
- package/dist/layout/Chat/ChatEntrySkeleton.svelte +81 -0
- package/dist/layout/Chat/ChatEntrySkeleton.svelte.d.ts +4 -0
- package/dist/layout/Chat/ChatHeader.svelte +172 -0
- package/dist/layout/Chat/ChatHeader.svelte.d.ts +4 -0
- package/dist/layout/Chat/ChatInput.svelte +205 -0
- package/dist/layout/Chat/ChatInput.svelte.d.ts +4 -0
- package/dist/layout/Chat/DraggableWindow.svelte +230 -0
- package/dist/layout/Chat/DraggableWindow.svelte.d.ts +28 -0
- package/dist/layout/Chat/PreviewPage.svelte +182 -0
- package/dist/layout/Chat/PreviewPage.svelte.d.ts +4 -0
- package/dist/layout/Chat/RichText.svelte +215 -0
- package/dist/layout/Chat/RichText.svelte.d.ts +4 -0
- package/dist/layout/Chat/index.d.ts +9 -0
- package/dist/layout/Chat/index.js +9 -0
- package/dist/layout/Chat/types.d.ts +139 -0
- package/dist/layout/Chat/types.js +1 -0
- package/dist/ui/datatable/Datatable.svelte +10 -8
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.js +79 -0
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export * from './ui/box/index.js';
|
|
|
27
27
|
export * from './ui/breadcrumb/index.js';
|
|
28
28
|
export * from './layout/ComponentCanvas/index.js';
|
|
29
29
|
export * from './ui/toast/index.js';
|
|
30
|
+
export * from './layout/Chat/index.js';
|
|
30
31
|
export * from './forms/input/index.js';
|
|
31
32
|
export * from './forms/label/index.js';
|
|
32
33
|
export * from './forms/datepicker/index.js';
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,7 @@ export * from './ui/box/index.js';
|
|
|
28
28
|
export * from './ui/breadcrumb/index.js';
|
|
29
29
|
export * from './layout/ComponentCanvas/index.js';
|
|
30
30
|
export * from './ui/toast/index.js';
|
|
31
|
+
export * from './layout/Chat/index.js';
|
|
31
32
|
export * from './forms/input/index.js';
|
|
32
33
|
export * from './forms/label/index.js';
|
|
33
34
|
export * from './forms/datepicker/index.js';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { findTicketCategoryParents, IconifyIcon } from '../../index.js';
|
|
3
|
+
import { slide } from 'svelte/transition';
|
|
4
|
+
import type { CategorySelectorProps, ICaseCategory } from './types.js';
|
|
5
|
+
|
|
6
|
+
let { items = [], options = [], onClick }: CategorySelectorProps = $props();
|
|
7
|
+
|
|
8
|
+
let openSubmenu = $state(-1);
|
|
9
|
+
|
|
10
|
+
function toggleSubmenu(index: any) {
|
|
11
|
+
openSubmenu = openSubmenu === index ? -1 : index;
|
|
12
|
+
}
|
|
13
|
+
function handleItemClick(option: ICaseCategory) {
|
|
14
|
+
if (!option.items) {
|
|
15
|
+
const data = findTicketCategoryParents(option, options);
|
|
16
|
+
onClick?.({ selected: option, list: data });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<ul transition:slide class="divide-y">
|
|
22
|
+
{#each items as item, index (item.name)}
|
|
23
|
+
<li class="w-full text-sm">
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
class="w-full px-2 py-1 text-left hover:bg-gray-100"
|
|
27
|
+
onclick={() => {
|
|
28
|
+
if (item.items) {
|
|
29
|
+
toggleSubmenu(index);
|
|
30
|
+
} else {
|
|
31
|
+
handleItemClick(item);
|
|
32
|
+
}
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
<div class="flex items-center justify-between">
|
|
36
|
+
{item.name}
|
|
37
|
+
{#if item.items}
|
|
38
|
+
<IconifyIcon
|
|
39
|
+
icon={openSubmenu === index ? 'ri:arrow-up-s-fill' : 'ri:arrow-down-s-fill'}
|
|
40
|
+
class="text-lg text-gray-500"
|
|
41
|
+
/>
|
|
42
|
+
{/if}
|
|
43
|
+
</div>
|
|
44
|
+
</button>
|
|
45
|
+
{#if item.items && openSubmenu === index}
|
|
46
|
+
<div class="pl-4">
|
|
47
|
+
<svelte:self items={item.items} onclick {options} />
|
|
48
|
+
</div>
|
|
49
|
+
{/if}
|
|
50
|
+
</li>
|
|
51
|
+
{/each}
|
|
52
|
+
</ul>
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { IconifyIcon, isExcel, isImage, isPdf, isPPT, isWord } from '../../index.js';
|
|
3
|
+
import { type ChatEntryPageProps } from './index.js';
|
|
4
|
+
import PreviewPage from './PreviewPage.svelte';
|
|
5
|
+
|
|
6
|
+
let { message, time = '', textBorder = true, userId }: ChatEntryPageProps = $props();
|
|
7
|
+
|
|
8
|
+
let showImage = $state(false);
|
|
9
|
+
let selectedFile = $state('');
|
|
10
|
+
|
|
11
|
+
let frame = $state<HTMLIFrameElement | undefined>();
|
|
12
|
+
function onItemClick(e: any) {
|
|
13
|
+
e.target.requestFullscreen();
|
|
14
|
+
e.stopPropagation();
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<div class=" mb-2 {message.mine ? 'flex justify-end' : 'flex justify-start'} ">
|
|
19
|
+
<div class="mx-2 flex flex-col">
|
|
20
|
+
<div class=" {message.mine ? ' text-right ' : ' text-left'} ">
|
|
21
|
+
<span class=" text-sm text-gray-800/70">
|
|
22
|
+
{time}
|
|
23
|
+
</span>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="order-1 flex max-w-sm flex-col space-y-2 text-sm" class:items-end={message.mine}>
|
|
26
|
+
<div>
|
|
27
|
+
<div class="space-y-2">
|
|
28
|
+
<div class="flex" class:justify-end={message.mine} class:justify-start={!message.mine}>
|
|
29
|
+
<div
|
|
30
|
+
class="inline-block bg-gray-200 px-4 py-2 break-normal {message.mine
|
|
31
|
+
? 'rounded-l-md rounded-r-sm border-r-4 border-blue-800 '
|
|
32
|
+
: 'rounded-l-sm rounded-r-md border-l-4 border-yellow-400'} "
|
|
33
|
+
>
|
|
34
|
+
{#if message.message.includes('http' || 'www.')}
|
|
35
|
+
<a
|
|
36
|
+
class={textBorder ? '' : 'bg-yellow-500 px-px '}
|
|
37
|
+
href={message.message}
|
|
38
|
+
target="_blank"
|
|
39
|
+
>
|
|
40
|
+
<p
|
|
41
|
+
class="text-blue-500 underline decoration-blue-500 underline-offset-2 hover:text-blue-700"
|
|
42
|
+
>
|
|
43
|
+
{message.message}
|
|
44
|
+
</p>
|
|
45
|
+
</a>
|
|
46
|
+
{:else}
|
|
47
|
+
<p class={`break-normal ${textBorder ? '' : 'bg-yellow-500 px-px'}`}>
|
|
48
|
+
{message.message}
|
|
49
|
+
</p>
|
|
50
|
+
{/if}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
{#if message.files && message.files.length}
|
|
54
|
+
{#each message.files as image}
|
|
55
|
+
<div
|
|
56
|
+
class:justify-end={message.mine}
|
|
57
|
+
class:justify-start={!message.mine}
|
|
58
|
+
class:hidden={!isImage(image)}
|
|
59
|
+
>
|
|
60
|
+
<div
|
|
61
|
+
class=" flex rounded-md p-1"
|
|
62
|
+
class:justify-end={message.mine}
|
|
63
|
+
class:justify-start={!message.mine}
|
|
64
|
+
>
|
|
65
|
+
<button class="group relative rounded-md bg-gray-200 p-0.5" onclick={onItemClick}>
|
|
66
|
+
<img
|
|
67
|
+
src={image}
|
|
68
|
+
alt="pic"
|
|
69
|
+
class="max-h-60 w-40 transform rounded-md transition duration-300 ease-in-out group-hover:scale-95"
|
|
70
|
+
/>
|
|
71
|
+
<!-- <div
|
|
72
|
+
class="absolute inset-0 bg-black opacity-0 group-hover:opacity-40 transition-opacity rounded-md"
|
|
73
|
+
/> -->
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
<div
|
|
78
|
+
class:justify-end={message.mine}
|
|
79
|
+
class:justify-start={!message.mine}
|
|
80
|
+
class:hidden={!isPdf(image)}
|
|
81
|
+
>
|
|
82
|
+
<div
|
|
83
|
+
class=" flex rounded-md p-1"
|
|
84
|
+
class:justify-end={message.mine}
|
|
85
|
+
class:justify-start={!message.mine}
|
|
86
|
+
>
|
|
87
|
+
<button
|
|
88
|
+
class="group relative rounded-md bg-gray-200 px-0.5 py-2"
|
|
89
|
+
onclick={(_) => {
|
|
90
|
+
selectedFile = image;
|
|
91
|
+
showImage = true;
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
<IconifyIcon
|
|
95
|
+
icon="vscode-icons:file-type-pdf2"
|
|
96
|
+
style="font-size: 150px;"
|
|
97
|
+
class="transform transition duration-300 ease-in-out group-hover:scale-95"
|
|
98
|
+
/>
|
|
99
|
+
<!-- <div
|
|
100
|
+
class="absolute inset-0 bg-black opacity-0 group-hover:opacity-40 transition-opacity rounded-md"
|
|
101
|
+
/> -->
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
<div
|
|
106
|
+
class:justify-end={message.mine}
|
|
107
|
+
class:justify-start={!message.mine}
|
|
108
|
+
class:hidden={!isWord(image)}
|
|
109
|
+
>
|
|
110
|
+
<div
|
|
111
|
+
class=" flex rounded-md p-1"
|
|
112
|
+
class:justify-end={message.mine}
|
|
113
|
+
class:justify-start={!message.mine}
|
|
114
|
+
>
|
|
115
|
+
<button
|
|
116
|
+
class="group relative rounded-md bg-gray-200 p-0.5"
|
|
117
|
+
onclick={(_) => {
|
|
118
|
+
selectedFile = image;
|
|
119
|
+
showImage = true;
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
<IconifyIcon
|
|
123
|
+
icon="vscode-icons:file-type-word"
|
|
124
|
+
style="font-size: 150px;"
|
|
125
|
+
class="transform transition duration-300 ease-in-out group-hover:scale-95"
|
|
126
|
+
/>
|
|
127
|
+
<!-- <div
|
|
128
|
+
class="absolute inset-0 bg-black opacity-0 group-hover:opacity-40 transition-opacity rounded-md"
|
|
129
|
+
/> -->
|
|
130
|
+
</button>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div
|
|
134
|
+
class:justify-end={message.mine}
|
|
135
|
+
class:justify-start={!message.mine}
|
|
136
|
+
class:hidden={!isPPT(image)}
|
|
137
|
+
>
|
|
138
|
+
<div
|
|
139
|
+
class=" flex rounded-md p-1"
|
|
140
|
+
class:justify-end={message.mine}
|
|
141
|
+
class:justify-start={!message.mine}
|
|
142
|
+
>
|
|
143
|
+
<button
|
|
144
|
+
class="group relative rounded-md bg-gray-200 p-0.5"
|
|
145
|
+
onclick={(_) => {
|
|
146
|
+
selectedFile = image;
|
|
147
|
+
showImage = true;
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
<IconifyIcon
|
|
151
|
+
icon="vscode-icons:file-type-powerpoint2"
|
|
152
|
+
style="font-size: 150px;"
|
|
153
|
+
class="transform transition duration-300 ease-in-out group-hover:scale-95"
|
|
154
|
+
/>
|
|
155
|
+
<!-- <div
|
|
156
|
+
class="absolute inset-0 bg-black opacity-0 group-hover:opacity-40 transition-opacity rounded-md"
|
|
157
|
+
/> -->
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
<div
|
|
162
|
+
class:justify-end={message.mine}
|
|
163
|
+
class:justify-start={!message.mine}
|
|
164
|
+
class:hidden={!isExcel(image)}
|
|
165
|
+
>
|
|
166
|
+
<div
|
|
167
|
+
class=" flex rounded-md p-1"
|
|
168
|
+
class:justify-end={message.mine}
|
|
169
|
+
class:justify-start={!message.mine}
|
|
170
|
+
>
|
|
171
|
+
<button
|
|
172
|
+
class="group relative rounded-md bg-gray-200 p-0.5"
|
|
173
|
+
onclick={() => {
|
|
174
|
+
selectedFile = image;
|
|
175
|
+
showImage = true;
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
<IconifyIcon
|
|
179
|
+
icon="vscode-icons:file-type-excel"
|
|
180
|
+
style="font-size: 150px;"
|
|
181
|
+
class="transform transition duration-300 ease-in-out group-hover:scale-95"
|
|
182
|
+
/>
|
|
183
|
+
<!-- <div
|
|
184
|
+
class="absolute inset-0 bg-black opacity-0 group-hover:opacity-40 transition-opacity rounded-md"
|
|
185
|
+
/> -->
|
|
186
|
+
</button>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
<div
|
|
190
|
+
class:justify-end={message.mine}
|
|
191
|
+
class:justify-start={!message.mine}
|
|
192
|
+
class:hidden={isImage(image) ||
|
|
193
|
+
isPPT(image) ||
|
|
194
|
+
isPdf(image) ||
|
|
195
|
+
isWord(image) ||
|
|
196
|
+
isExcel(image)}
|
|
197
|
+
>
|
|
198
|
+
<div
|
|
199
|
+
class=" flex rounded-md p-1"
|
|
200
|
+
class:justify-end={message.mine}
|
|
201
|
+
class:justify-start={!message.mine}
|
|
202
|
+
>
|
|
203
|
+
<button
|
|
204
|
+
class="group relative rounded-md bg-gray-200 p-0.5"
|
|
205
|
+
onclick={(_) => {
|
|
206
|
+
selectedFile = image;
|
|
207
|
+
showImage = true;
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
<IconifyIcon
|
|
211
|
+
icon="bxs:file"
|
|
212
|
+
style="font-size: 150px;"
|
|
213
|
+
class="transform transition duration-300 ease-in-out group-hover:scale-95"
|
|
214
|
+
/>
|
|
215
|
+
<!-- <div
|
|
216
|
+
class="absolute inset-0 bg-black opacity-0 group-hover:opacity-40 transition-opacity rounded-md"
|
|
217
|
+
/> -->
|
|
218
|
+
</button>
|
|
219
|
+
</div>
|
|
220
|
+
<iframe src={image} title={image} class="flex-grow" bind:this={frame}></iframe>
|
|
221
|
+
</div>
|
|
222
|
+
<!-- {/if} -->
|
|
223
|
+
{/each}
|
|
224
|
+
{/if}
|
|
225
|
+
</div>
|
|
226
|
+
<!-- {/if} -->
|
|
227
|
+
</div>
|
|
228
|
+
<div class:hidden={!message.mine}>
|
|
229
|
+
<p class:hidden={userId === message.user?.id} class="text-sm text-gray-500 antialiased">
|
|
230
|
+
{message.user?.firstName}
|
|
231
|
+
{message?.user?.otherNames ?? ''}
|
|
232
|
+
{message.user?.surname}
|
|
233
|
+
{message.user?.staffNumber ?? ''}
|
|
234
|
+
</p>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<PreviewPage
|
|
241
|
+
title="Attachment Preview"
|
|
242
|
+
bind:open={showImage}
|
|
243
|
+
showActions={false}
|
|
244
|
+
fullImage
|
|
245
|
+
src={{ content: selectedFile, name: '', file: '' }}
|
|
246
|
+
/>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fade } from 'svelte/transition';
|
|
3
|
+
import type { ChatEntrySkeletonProps } from './index.js';
|
|
4
|
+
|
|
5
|
+
let { isMobile = false, messageCount = 5 }: ChatEntrySkeletonProps = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div class="flex h-full w-full flex-col overflow-x-hidden overflow-y-auto">
|
|
9
|
+
<div
|
|
10
|
+
class="chat-list-pane relative isolate flex h-full w-full flex-col overflow-x-hidden overflow-y-auto bg-green-50/40 !bg-contain !bg-center !bg-no-repeat"
|
|
11
|
+
>
|
|
12
|
+
{#if messageCount === 0}
|
|
13
|
+
<div
|
|
14
|
+
class="mx-auto flex min-h-full max-w-[12rem] flex-col items-center justify-center gap-1 text-center text-base font-semibold text-gray-400"
|
|
15
|
+
>
|
|
16
|
+
<div class="h-20 w-20 rounded-full bg-gray-400 opacity-20"></div>
|
|
17
|
+
<span class="text-sm"> Select a query to view the conversation </span>
|
|
18
|
+
</div>
|
|
19
|
+
{:else}
|
|
20
|
+
<!-- Received message skeletons -->
|
|
21
|
+
{#each Array(Math.ceil(messageCount / 2)) as _, i}
|
|
22
|
+
<div transition:fade={{ delay: i * 100 }} class="flex flex-col items-start gap-2 p-4">
|
|
23
|
+
<div class="flex items-center gap-2">
|
|
24
|
+
<div class="h-8 w-8 animate-pulse rounded-full bg-gray-200"></div>
|
|
25
|
+
<div class="h-4 w-20 animate-pulse rounded-full bg-gray-200"></div>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="ml-10 flex flex-col gap-1">
|
|
28
|
+
<div class="h-4 w-64 animate-pulse rounded-md bg-gray-200"></div>
|
|
29
|
+
<div class="h-4 w-48 animate-pulse rounded-md bg-gray-200"></div>
|
|
30
|
+
<div class="h-4 w-32 animate-pulse rounded-md bg-gray-200"></div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="ml-10 h-3 w-24 animate-pulse rounded-full bg-gray-200"></div>
|
|
33
|
+
</div>
|
|
34
|
+
{/each}
|
|
35
|
+
|
|
36
|
+
<!-- Sent message skeletons -->
|
|
37
|
+
{#each Array(Math.floor(messageCount / 2)) as _, i}
|
|
38
|
+
<div
|
|
39
|
+
transition:fade={{ delay: (i + Math.ceil(messageCount / 2)) * 100 }}
|
|
40
|
+
class="flex flex-col items-end gap-2 p-4"
|
|
41
|
+
>
|
|
42
|
+
<div class="flex gap-1">
|
|
43
|
+
<div class="flex flex-col items-end gap-1">
|
|
44
|
+
<div class="h-4 w-20 animate-pulse rounded-full bg-gray-200"></div>
|
|
45
|
+
<div class="h-4 w-56 animate-pulse rounded-md bg-gray-200"></div>
|
|
46
|
+
<div class="h-4 w-40 animate-pulse rounded-md bg-gray-200"></div>
|
|
47
|
+
<div class="h-3 w-20 animate-pulse rounded-full bg-gray-200"></div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="flex items-center gap-2">
|
|
50
|
+
<div class="h-8 w-8 animate-pulse rounded-full bg-gray-200"></div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
{/each}
|
|
55
|
+
{/if}
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<!-- Input area skeleton -->
|
|
59
|
+
<div class={`border-t bg-white p-2 ${isMobile ? 'pb-20' : 'pb-2'}`}>
|
|
60
|
+
<div class="flex items-center gap-2">
|
|
61
|
+
<div class="h-10 flex-1 animate-pulse rounded-full bg-gray-200"></div>
|
|
62
|
+
<div class="h-10 w-10 animate-pulse rounded-full bg-gray-200"></div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<style>
|
|
68
|
+
.animate-pulse {
|
|
69
|
+
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@keyframes pulse {
|
|
73
|
+
0%,
|
|
74
|
+
100% {
|
|
75
|
+
opacity: 1;
|
|
76
|
+
}
|
|
77
|
+
50% {
|
|
78
|
+
opacity: 0.5;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { clickOutsideAction, DropdownItem } from '../../index.js';
|
|
3
|
+
import { Avatar } from '../../ui/avatar/index.js';
|
|
4
|
+
import { IconifyIcon } from '../../ui/icons/index.js';
|
|
5
|
+
import { slide } from 'svelte/transition';
|
|
6
|
+
import type { ChatHeaderProps } from './index.js';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
caseTitle = '',
|
|
10
|
+
user = { name: '', profileImageUrl: '', initials: '' },
|
|
11
|
+
caseTypes = '',
|
|
12
|
+
assignedTo = '',
|
|
13
|
+
showAssignedTo = false,
|
|
14
|
+
showFollowCaseButton = false,
|
|
15
|
+
showBackArrow = false,
|
|
16
|
+
isFollowingCase = false,
|
|
17
|
+
overflowActionsList = [],
|
|
18
|
+
showOverflowActionButton = true,
|
|
19
|
+
caseStatus = '',
|
|
20
|
+
logoUrl = '',
|
|
21
|
+
showDefaultCaseOveflowActions = true,
|
|
22
|
+
showCloseOverflowAction = true,
|
|
23
|
+
onOverflowActionClicked,
|
|
24
|
+
onToggleFollowCase,
|
|
25
|
+
leadingWidget,
|
|
26
|
+
trailingWidget,
|
|
27
|
+
caseDetailsWidget,
|
|
28
|
+
onBackArrowClicked,
|
|
29
|
+
onCloseCase
|
|
30
|
+
}: ChatHeaderProps = $props();
|
|
31
|
+
|
|
32
|
+
let showMenu = $state(false);
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<div class="flex items-center justify-between">
|
|
36
|
+
{#if caseDetailsWidget}
|
|
37
|
+
{@render caseDetailsWidget?.()}
|
|
38
|
+
{:else}
|
|
39
|
+
<div class="flex w-full items-center gap-1.5">
|
|
40
|
+
{@render leadingWidget?.()}
|
|
41
|
+
{#if showBackArrow}
|
|
42
|
+
<button aria-label="back" onclick={onBackArrowClicked}>
|
|
43
|
+
<IconifyIcon
|
|
44
|
+
icon="ep:back"
|
|
45
|
+
class="block rounded-full p-2 hover:bg-gray-200 lg:hidden"
|
|
46
|
+
style="font-size: 20px;"
|
|
47
|
+
/>
|
|
48
|
+
</button>
|
|
49
|
+
{/if}
|
|
50
|
+
<div class="flex items-center gap-3">
|
|
51
|
+
{#if user?.profileImageUrl}
|
|
52
|
+
<Avatar
|
|
53
|
+
src={user?.profileImageUrl}
|
|
54
|
+
alt="pic"
|
|
55
|
+
class="h-14 w-14 border text-lg font-medium"
|
|
56
|
+
/>
|
|
57
|
+
{:else if user?.initials}
|
|
58
|
+
<Avatar class="h-14 w-14 border bg-primary-500 text-lg font-medium text-white"
|
|
59
|
+
>{user.initials}</Avatar
|
|
60
|
+
>
|
|
61
|
+
{:else}
|
|
62
|
+
<Avatar src={logoUrl} alt="logo" class="h-14 w-14 border text-lg font-medium" />
|
|
63
|
+
{/if}
|
|
64
|
+
<div class="flex flex-col">
|
|
65
|
+
<div class="font-medium capitalize">{user.name}</div>
|
|
66
|
+
<div class="flex gap-2">
|
|
67
|
+
<div class="max-w-xs truncate text-sm text-gray-500 capitalize">
|
|
68
|
+
<span class=" font-medium text-black">Title:</span>
|
|
69
|
+
<span title={caseTitle}>
|
|
70
|
+
{caseTitle}
|
|
71
|
+
</span>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="text-sm text-gray-500 capitalize">
|
|
74
|
+
<span class="font-medium text-black">status: </span>
|
|
75
|
+
{caseStatus}
|
|
76
|
+
</div>
|
|
77
|
+
<div class="text-sm" class:hidden={!showAssignedTo}>
|
|
78
|
+
<span>Assigned To :</span> <span class="text-gray-500">{assignedTo}</span>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="flex gap-2">
|
|
82
|
+
<div class="max-w-xl truncate text-sm text-gray-500 capitalize">
|
|
83
|
+
{caseTypes}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
{#if showFollowCaseButton}
|
|
87
|
+
<div class="">
|
|
88
|
+
<button
|
|
89
|
+
onclick={() => onToggleFollowCase?.(isFollowingCase ? 'unfollow' : 'follow')}
|
|
90
|
+
class="flex w-fit items-center gap-1 rounded bg-gray-200 px-2 py-0.5 text-xs hover:bg-gray-300"
|
|
91
|
+
>
|
|
92
|
+
<span>{isFollowingCase ? 'Unfollow' : 'Follow'}</span>
|
|
93
|
+
<IconifyIcon icon={isFollowingCase ? 'mdi:eye-off' : 'mdi:eye'} />
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
{/if}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
{/if}
|
|
101
|
+
{@render trailingWidget?.()}
|
|
102
|
+
{#if showOverflowActionButton}
|
|
103
|
+
<div class="w-10"></div>
|
|
104
|
+
{/if}
|
|
105
|
+
|
|
106
|
+
{#if showOverflowActionButton}
|
|
107
|
+
<div
|
|
108
|
+
class="relative space-y-2"
|
|
109
|
+
use:clickOutsideAction
|
|
110
|
+
onclickoutside={() => (showMenu = false)}
|
|
111
|
+
>
|
|
112
|
+
<button
|
|
113
|
+
aria-label="toggle menu"
|
|
114
|
+
class="grid place-content-center rounded-full p-1 text-gray-600 hover:bg-gray-200"
|
|
115
|
+
onclick={() => (showMenu = !showMenu)}
|
|
116
|
+
>
|
|
117
|
+
<IconifyIcon icon="mdi:dots-vertical" style="font-size: 25px;" />
|
|
118
|
+
</button>
|
|
119
|
+
{#if showMenu}
|
|
120
|
+
<div
|
|
121
|
+
class="absolute right-3 z-10 flex w-44 flex-col divide-y rounded-md bg-white py-1 shadow-md shadow-gray-300"
|
|
122
|
+
transition:slide
|
|
123
|
+
use:clickOutsideAction
|
|
124
|
+
>
|
|
125
|
+
{#if showDefaultCaseOveflowActions}
|
|
126
|
+
{#if !isFollowingCase}
|
|
127
|
+
<DropdownItem
|
|
128
|
+
class="flex items-center gap-1 whitespace-nowrap "
|
|
129
|
+
onclick={() => {
|
|
130
|
+
onToggleFollowCase?.('follow');
|
|
131
|
+
showMenu = false;
|
|
132
|
+
}}><IconifyIcon icon="mdi:eye" style="font-size: 20px;" /> Follow Case</DropdownItem
|
|
133
|
+
>
|
|
134
|
+
{:else}
|
|
135
|
+
<DropdownItem
|
|
136
|
+
class="flex items-center gap-1 whitespace-nowrap "
|
|
137
|
+
onclick={() => {
|
|
138
|
+
onToggleFollowCase?.('unfollow');
|
|
139
|
+
showMenu = false;
|
|
140
|
+
}}
|
|
141
|
+
><IconifyIcon icon="mdi:eye-off" style="font-size: 20px;" /> Unfollow Case</DropdownItem
|
|
142
|
+
>
|
|
143
|
+
{/if}
|
|
144
|
+
{/if}
|
|
145
|
+
{#if overflowActionsList.length > 0}
|
|
146
|
+
{#each overflowActionsList as item}
|
|
147
|
+
<DropdownItem
|
|
148
|
+
class="flex items-center gap-1 whitespace-nowrap "
|
|
149
|
+
onclick={() => {
|
|
150
|
+
onOverflowActionClicked?.(item);
|
|
151
|
+
showMenu = false;
|
|
152
|
+
}}
|
|
153
|
+
><IconifyIcon icon={item.icon} style="font-size: 20px;" />
|
|
154
|
+
{item.label}</DropdownItem
|
|
155
|
+
>
|
|
156
|
+
{/each}
|
|
157
|
+
{/if}
|
|
158
|
+
{#if showDefaultCaseOveflowActions && showCloseOverflowAction}
|
|
159
|
+
<DropdownItem
|
|
160
|
+
class="flex items-center gap-1 whitespace-nowrap text-red-600"
|
|
161
|
+
onclick={() => {
|
|
162
|
+
onCloseCase?.();
|
|
163
|
+
showMenu = false;
|
|
164
|
+
}}
|
|
165
|
+
><IconifyIcon icon="tabler:message-2-x" style="font-size: 20px;" /> Close Case</DropdownItem
|
|
166
|
+
>
|
|
167
|
+
{/if}
|
|
168
|
+
</div>
|
|
169
|
+
{/if}
|
|
170
|
+
</div>
|
|
171
|
+
{/if}
|
|
172
|
+
</div>
|