@adatechnology/conversations-ui 0.1.0-rc.2 → 0.1.0-rc.21
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/{chunk-ZDURDZTM.js → chunk-2AYDBWNE.js} +14 -0
- package/dist/chunk-TV4OQRGH.js +2187 -0
- package/dist/flows/index.d.ts +14 -2
- package/dist/flows/index.js +117 -39
- package/dist/index.d.ts +862 -136
- package/dist/index.js +1746 -1124
- package/dist/preview/index.d.ts +470 -0
- package/dist/preview/index.js +1329 -0
- package/dist/styles.css +228 -0
- package/dist/types-C6A_9edv.d.ts +456 -0
- package/package.json +10 -3
- package/src/AudioRecorderButton.test.tsx +30 -0
- package/src/AudioRecorderButton.tsx +248 -0
- package/src/AudioTranscription.test.tsx +115 -0
- package/src/AudioTranscription.tsx +249 -0
- package/src/Avatar.tsx +30 -4
- package/src/ChannelIcon.tsx +87 -0
- package/src/ConversationContextPanel.tsx +280 -0
- package/src/ConversationDocumentsPanel.tsx +425 -0
- package/src/ConversationHeader.tsx +257 -0
- package/src/ConversationListItem.tsx +54 -7
- package/src/ConversationLocalesProvider.tsx +44 -0
- package/src/ConversationRow.tsx +137 -0
- package/src/DateDivider.tsx +16 -3
- package/src/DocumentsLibrary.tsx +322 -0
- package/src/EmojiPicker.tsx +69 -55
- package/src/FileIcon.test.ts +83 -0
- package/src/FileIcon.tsx +88 -11
- package/src/InteractiveMessage.test.tsx +41 -0
- package/src/InteractiveMessage.tsx +143 -0
- package/src/Lightbox.tsx +18 -3
- package/src/MediaRenderer.tsx +96 -22
- package/src/MessageBubble.tsx +77 -5
- package/src/MessageComposer.test.tsx +35 -0
- package/src/MessageComposer.tsx +165 -19
- package/src/RichMessageComposer.test.tsx +83 -0
- package/src/RichMessageComposer.tsx +380 -0
- package/src/Wallpaper.test.tsx +21 -0
- package/src/Wallpaper.tsx +69 -7
- package/src/WhatsAppMessageEditor.tsx +28 -4
- package/src/WindowExpiredNotice.tsx +57 -0
- package/src/audioRecorderFormat.test.ts +67 -0
- package/src/conversationChannel.test.ts +53 -0
- package/src/conversationChannel.ts +146 -0
- package/src/conversationTranscript.test.ts +122 -0
- package/src/conversationTranscript.ts +89 -0
- package/src/conversationWindow.test.ts +90 -0
- package/src/conversationWindow.ts +78 -0
- package/src/documentTypeLabel.test.ts +57 -0
- package/src/emojiCatalog.test.ts +35 -0
- package/src/emojiCatalog.ts +189 -0
- package/src/flows/FlowGroupHeader.tsx +12 -2
- package/src/flows/FlowMapCanvas.tsx +17 -14
- package/src/flows/FlowMapNode.tsx +3 -1
- package/src/flows/FlowNodeCard.tsx +22 -4
- package/src/flows/FlowNodePanel.tsx +132 -35
- package/src/flows/FlowPalette.tsx +6 -2
- package/src/flows/FlowWhatsAppPreview.tsx +14 -3
- package/src/flows/flowGraph.ts +5 -5
- package/src/flows/labels.ts +5 -0
- package/src/hooks/useConversationActions.ts +56 -0
- package/src/hooks/useConversationDocuments.ts +15 -9
- package/src/hooks/useConversationList.ts +15 -9
- package/src/hooks/useConversationMessages.ts +2 -2
- package/src/hooks/useScrollToLatestMessage.ts +127 -0
- package/src/index.ts +140 -16
- package/src/lib/cn.test.ts +29 -0
- package/src/lib/cn.ts +15 -0
- package/src/lib/createMediaUrlResolver.ts +33 -0
- package/src/lib/paginated.test.ts +33 -0
- package/src/lib/paginated.ts +26 -0
- package/src/lib/phone.ts +34 -0
- package/src/preview/ConversationPreview.tsx +334 -0
- package/src/preview/MediaTypesPreview.tsx +87 -0
- package/src/preview/conversationPreviewFailures.test.ts +64 -0
- package/src/preview/createMockConversationsApi.ts +271 -0
- package/src/preview/createMockSSEProvider.ts +40 -0
- package/src/preview/createPreviewBridgeClient.test.ts +92 -0
- package/src/preview/createPreviewBridgeClient.ts +124 -0
- package/src/preview/createPreviewMediaUploader.ts +82 -0
- package/src/preview/createPreviewWebhookClient.test.ts +194 -0
- package/src/preview/createPreviewWebhookClient.ts +222 -0
- package/src/preview/index.ts +67 -0
- package/src/preview/mediaTypeOf.test.ts +15 -0
- package/src/preview/mockDocumentsSearch.test.ts +57 -0
- package/src/preview/mockEventSource.ts +53 -0
- package/src/preview/preview.test.ts +177 -0
- package/src/preview/previewFileSamples.test.ts +151 -0
- package/src/preview/previewFileSamples.ts +74 -0
- package/src/preview/previewFixtures.ts +440 -0
- package/src/preview/previewMediaSource.test.ts +62 -0
- package/src/preview/previewMediaSource.ts +91 -0
- package/src/preview/previewMediaUploader.test.ts +61 -0
- package/src/preview/previewStore.ts +193 -0
- package/src/preview/startPreviewScript.ts +60 -0
- package/src/providers/types.ts +175 -12
- package/src/quickReply.test.ts +58 -0
- package/src/settings/TranscriptionSettingsForm.test.tsx +81 -0
- package/src/settings/TranscriptionSettingsForm.tsx +189 -0
- package/src/settings/WhatsAppCreateTemplateForm.tsx +3 -1
- package/src/settings/WhatsAppTemplatesSettings.tsx +106 -0
- package/src/styles.css +173 -0
- package/src/types.ts +72 -1
- package/src/useDarkMode.ts +26 -0
- package/src/useIsNarrow.ts +29 -0
- package/src/useWaitingNotifications.ts +74 -29
|
@@ -1,64 +1,109 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, useCallback } from 'react'
|
|
2
2
|
import { useConversations } from './providers/ConversationsProvider'
|
|
3
|
-
import
|
|
3
|
+
import { conversationsOf } from './lib/paginated'
|
|
4
|
+
import type { ConversationSummary, ListConversationsParams } from './providers/types'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
const DEFAULT_POLL_INTERVAL_MS = 10_000
|
|
7
|
+
const DEFAULT_LIMIT = 50
|
|
8
|
+
|
|
9
|
+
export interface UseWaitingNotificationsLabels {
|
|
10
|
+
/** Título da notificação do sistema. Recebe a conversa para o host escolher nome × número. */
|
|
11
|
+
title: (conversation: ConversationSummary) => string
|
|
12
|
+
body: (conversation: ConversationSummary) => string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseWaitingNotificationsParams {
|
|
16
|
+
/**
|
|
17
|
+
* Repassado cru ao `fetchConversations`. É o que permite filtrar não lidas **no servidor** em
|
|
18
|
+
* vez de baixar a lista inteira e contar no cliente: um painel com milhares de conversas não
|
|
19
|
+
* pode paginar 50 por vez atrás de quem tem `unread > 0`.
|
|
20
|
+
*/
|
|
21
|
+
readonly params?: ListConversationsParams
|
|
22
|
+
readonly intervalMs?: number
|
|
23
|
+
/** Desliga o polling sem desmontar quem chama — útil com a aba em segundo plano. */
|
|
24
|
+
readonly enabled?: boolean
|
|
25
|
+
readonly icon?: string
|
|
26
|
+
readonly labels?: Partial<UseWaitingNotificationsLabels>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface UseWaitingNotificationsResult {
|
|
6
30
|
unreadCount: number
|
|
7
31
|
conversations: ConversationSummary[]
|
|
32
|
+
/**
|
|
33
|
+
* Releitura sob demanda. Existe porque o polling é o piso, não o mecanismo: quem já recebe SSE
|
|
34
|
+
* ou acabou de marcar tudo como lido sabe da mudança antes do próximo tick, e esperar 10s para
|
|
35
|
+
* o contador acompanhar faz a interface parecer travada.
|
|
36
|
+
*/
|
|
37
|
+
refresh: () => Promise<void>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DEFAULT_LABELS: UseWaitingNotificationsLabels = {
|
|
41
|
+
title: (conversation) => conversation.clientName ?? conversation.whatsappNumber,
|
|
42
|
+
body: (conversation) => conversation.lastContent ?? 'Nova mensagem',
|
|
8
43
|
}
|
|
9
44
|
|
|
10
|
-
export function useWaitingNotifications(): UseWaitingNotificationsResult {
|
|
11
|
-
const
|
|
45
|
+
export function useWaitingNotifications(params?: UseWaitingNotificationsParams): UseWaitingNotificationsResult {
|
|
46
|
+
const conversationsContext = useConversations()
|
|
12
47
|
const [conversations, setConversations] = useState<ConversationSummary[]>([])
|
|
13
48
|
const previousUnreadMap = useRef<Map<string, number>>(new Map())
|
|
14
49
|
const notifiedIds = useRef<Set<string>>(new Set())
|
|
50
|
+
// Trava de reentrância: com a rede lenta, o tick de 10s dispara sobre a busca anterior ainda em
|
|
51
|
+
// voo, e duas respostas fora de ordem fazem o contador oscilar e a notificação repetir.
|
|
52
|
+
const isPollingRef = useRef(false)
|
|
53
|
+
|
|
54
|
+
const isEnabled = params?.enabled ?? true
|
|
55
|
+
const intervalMs = params?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS
|
|
56
|
+
const icon = params?.icon
|
|
57
|
+
const listParams = params?.params
|
|
58
|
+
const labelTitle = params?.labels?.title ?? DEFAULT_LABELS.title
|
|
59
|
+
const labelBody = params?.labels?.body ?? DEFAULT_LABELS.body
|
|
15
60
|
|
|
16
|
-
const
|
|
17
|
-
if (!
|
|
61
|
+
const refresh = useCallback(async () => {
|
|
62
|
+
if (!conversationsContext || isPollingRef.current) return
|
|
63
|
+
isPollingRef.current = true
|
|
18
64
|
|
|
19
65
|
try {
|
|
20
|
-
const result =
|
|
66
|
+
const result = conversationsOf(
|
|
67
|
+
await conversationsContext.api.fetchConversations({ limit: DEFAULT_LIMIT, ...listParams }),
|
|
68
|
+
)
|
|
21
69
|
setConversations(result)
|
|
22
70
|
|
|
23
71
|
const currentMap = new Map<string, number>()
|
|
24
|
-
for (const
|
|
25
|
-
currentMap.set(conv.id, conv.unread)
|
|
26
|
-
}
|
|
72
|
+
for (const conversation of result) currentMap.set(conversation.id, conversation.unread)
|
|
27
73
|
|
|
28
|
-
for (const
|
|
29
|
-
if (
|
|
74
|
+
for (const conversation of result) {
|
|
75
|
+
if (conversation.unread <= 0) continue
|
|
30
76
|
|
|
31
|
-
const
|
|
77
|
+
const previousUnread = previousUnreadMap.current.get(conversation.id) ?? 0
|
|
78
|
+
if (conversation.unread <= previousUnread || notifiedIds.current.has(conversation.id)) continue
|
|
32
79
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (typeof window !== 'undefined' && 'Notification' in window && Notification.permission === 'granted') {
|
|
37
|
-
const title = conv.clientName ?? conv.whatsappNumber
|
|
38
|
-
const body = conv.lastContent ?? 'Nova mensagem'
|
|
39
|
-
new Notification(title, { body, icon: '/favicon.ico' })
|
|
40
|
-
}
|
|
80
|
+
notifiedIds.current.add(conversation.id)
|
|
81
|
+
if (typeof window !== 'undefined' && 'Notification' in window && Notification.permission === 'granted') {
|
|
82
|
+
new Notification(labelTitle(conversation), { body: labelBody(conversation), ...(icon ? { icon } : {}) })
|
|
41
83
|
}
|
|
42
84
|
}
|
|
43
85
|
|
|
44
86
|
previousUnreadMap.current = currentMap
|
|
45
87
|
} catch {
|
|
46
|
-
//
|
|
88
|
+
// Contador de badge não interrompe o atendimento: uma falha de rede some no próximo tick.
|
|
89
|
+
} finally {
|
|
90
|
+
isPollingRef.current = false
|
|
47
91
|
}
|
|
48
|
-
}, [
|
|
92
|
+
}, [conversationsContext, listParams, icon, labelTitle, labelBody])
|
|
49
93
|
|
|
50
94
|
useEffect(() => {
|
|
95
|
+
if (!isEnabled) return
|
|
96
|
+
|
|
51
97
|
if (typeof window !== 'undefined' && 'Notification' in window && Notification.permission === 'default') {
|
|
52
98
|
Notification.requestPermission()
|
|
53
99
|
}
|
|
54
100
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const interval = setInterval(pollConversations, 10_000)
|
|
101
|
+
void refresh()
|
|
102
|
+
const interval = setInterval(() => void refresh(), intervalMs)
|
|
58
103
|
return () => clearInterval(interval)
|
|
59
|
-
}, [
|
|
104
|
+
}, [refresh, isEnabled, intervalMs])
|
|
60
105
|
|
|
61
|
-
const unreadCount = conversations.reduce((sum,
|
|
106
|
+
const unreadCount = conversations.reduce((sum, conversation) => sum + conversation.unread, 0)
|
|
62
107
|
|
|
63
|
-
return { unreadCount, conversations }
|
|
108
|
+
return { unreadCount, conversations, refresh }
|
|
64
109
|
}
|