@adatechnology/conversations-ui 0.1.0-rc.8 → 0.1.0
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/ConversationSimulatorPanel--5fIzXWY.d.ts +804 -0
- package/dist/chunk-BJNRLLDO.js +2708 -0
- package/dist/chunk-DKPXKQGC.js +110 -0
- package/dist/{chunk-OGRRHQQW.js → chunk-WCBDXZ3X.js} +68 -4
- package/dist/flows/index.d.ts +422 -5
- package/dist/flows/index.js +2502 -678
- package/dist/index.d.ts +921 -17
- package/dist/index.js +3677 -678
- package/dist/preview/index.d.ts +62 -105
- package/dist/preview/index.js +162 -284
- package/dist/styles.css +893 -0
- package/package.json +9 -8
- package/src/AudioPlayer.tsx +8 -0
- package/src/AudioRecorderButton.test.tsx +30 -0
- package/src/AudioRecorderButton.tsx +248 -0
- package/src/AudioTranscription.test.tsx +115 -0
- package/src/AudioTranscription.tsx +252 -0
- package/src/Avatar.tsx +1 -1
- package/src/ConversationContextPanel.tsx +218 -44
- package/src/ConversationDocumentsPanel.tsx +11 -6
- package/src/ConversationHeader.test.tsx +66 -0
- package/src/ConversationHeader.tsx +147 -47
- package/src/ConversationListItem.tsx +8 -6
- package/src/ConversationLocalesProvider.tsx +28 -0
- package/src/ConversationRow.tsx +53 -7
- package/src/DarkModeToggle.test.tsx +76 -0
- package/src/DarkModeToggle.tsx +92 -0
- package/src/DocumentsLibrary.tsx +67 -7
- package/src/EmojiPicker.tsx +2 -1
- package/src/InteractiveMessage.tsx +3 -0
- package/src/Lightbox.tsx +1 -1
- package/src/MediaRenderer.tsx +88 -15
- package/src/MessageBubble.test.tsx +41 -0
- package/src/MessageBubble.tsx +47 -5
- package/src/MessageComposer.test.tsx +35 -0
- package/src/MessageComposer.tsx +122 -17
- package/src/MessageText.tsx +2 -1
- package/src/MessageTimestamp.tsx +2 -1
- package/src/RichMessageComposer.test.tsx +113 -0
- package/src/RichMessageComposer.tsx +551 -0
- package/src/SimpleEmojiPicker.tsx +5 -3
- package/src/StatusTicks.tsx +1 -1
- package/src/Toast.tsx +4 -0
- package/src/Tooltip.test.ts +42 -0
- package/src/Tooltip.tsx +167 -0
- package/src/Wallpaper.test.tsx +21 -0
- package/src/Wallpaper.tsx +67 -7
- package/src/WhatsAppMessageEditor.tsx +10 -7
- package/src/WindowExpiredNotice.tsx +12 -4
- package/src/{preview/audioRecorderFormat.test.ts → audioRecorderFormat.test.ts} +1 -1
- package/src/buildOutput.test.ts +79 -0
- package/src/composer.constant.ts +33 -0
- package/src/conversationTranscript.test.ts +57 -0
- package/src/conversationTranscript.ts +29 -4
- package/src/conversationWindow.ts +7 -5
- package/src/documentTypeLabel.test.ts +57 -0
- package/src/documents/DocumentsWorkspace.tsx +550 -0
- package/src/documents/index.ts +8 -0
- package/src/documents/labels.ts +92 -0
- package/src/flows/FlowConnectionEdge.tsx +104 -0
- package/src/flows/FlowGroupHeader.tsx +12 -2
- package/src/flows/FlowLegend.tsx +125 -0
- package/src/flows/FlowMapCanvas.tsx +15 -12
- package/src/flows/FlowMapNode.tsx +4 -1
- package/src/flows/FlowNodeCard.tsx +219 -34
- package/src/flows/FlowNodePanel.tsx +153 -39
- package/src/flows/FlowPalette.tsx +156 -70
- package/src/flows/FlowPortalNode.tsx +1 -1
- package/src/flows/FlowWhatsAppPreview.tsx +14 -3
- package/src/flows/FlowsWorkspace.tsx +1255 -0
- package/src/flows/flowCanvasModel.test.ts +456 -0
- package/src/flows/flowCanvasModel.ts +378 -0
- package/src/flows/flowEditorOps.test.ts +276 -0
- package/src/flows/flowEditorOps.ts +202 -0
- package/src/flows/flowGraph.ts +78 -53
- package/src/flows/flowMenuPlacement.test.ts +130 -0
- package/src/flows/flowMenuPlacement.ts +86 -0
- package/src/flows/index.ts +51 -2
- package/src/flows/labels.ts +180 -0
- package/src/flows/workspaceContract.test.ts +126 -0
- package/src/hooks/useContainerWidth.ts +35 -0
- package/src/hooks/useConversationRealtime.ts +10 -8
- package/src/hooks/useScrollToLatestMessage.ts +127 -0
- package/src/hooks/useUrlFilterState.ts +107 -0
- package/src/icon.constant.ts +12 -0
- package/src/index.ts +100 -0
- package/src/lib/composer-formatting.test.ts +78 -0
- package/src/lib/composer-formatting.ts +145 -0
- package/src/lib/whatsapp-formatting.test.tsx +37 -0
- package/src/lib/whatsapp-formatting.tsx +28 -3
- package/src/listing/index.tsx +202 -0
- package/src/pagination.constant.ts +10 -0
- package/src/preview/ConversationPreview.tsx +84 -45
- package/src/preview/ConversationSimulatorClient.ts +143 -0
- package/src/preview/ConversationSimulatorPanel.test.tsx +55 -0
- package/src/preview/ConversationSimulatorPanel.tsx +131 -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 +96 -0
- package/src/preview/createPreviewWebhookClient.ts +99 -3
- package/src/preview/index.ts +36 -2
- package/src/preview/previewMediaUploader.test.ts +61 -0
- package/src/providers/ConversationsProvider.tsx +8 -6
- package/src/providers/types.ts +59 -2
- package/src/quickReply.test.ts +58 -0
- package/src/replyLatency.test.ts +71 -0
- package/src/replyLatency.ts +57 -0
- package/src/settings/MessagesWorkspace.tsx +571 -0
- package/src/settings/TopicsForm.tsx +2 -0
- package/src/settings/TranscriptionSettingsForm.test.tsx +81 -0
- package/src/settings/TranscriptionSettingsForm.tsx +190 -0
- package/src/settings/WelcomeFarewellForm.tsx +1 -0
- package/src/settings/WhatsAppCreateTemplateForm.tsx +1 -0
- package/src/settings/WhatsAppTemplateSettingsForm.tsx +5 -2
- package/src/settings/WhatsAppTemplatesSettings.test.tsx +61 -0
- package/src/settings/WhatsAppTemplatesSettings.tsx +22 -2
- package/src/styles.css +858 -0
- package/src/theme.ts +13 -0
- package/src/types.ts +26 -0
- package/src/workspace/BulkTemplateModal.tsx +132 -0
- package/src/workspace/ConversationPane.tsx +432 -0
- package/src/workspace/ConversationsInboxList.tsx +194 -0
- package/src/workspace/ConversationsWorkspace.tsx +423 -0
- package/src/workspace/index.ts +17 -0
- package/src/workspace/labels.test.ts +17 -0
- package/src/workspace/labels.ts +85 -0
- package/src/workspace/useConversationsInbox.ts +332 -0
- package/dist/chunk-73MW5HNT.js +0 -1717
- package/dist/chunk-NV2RZ5KT.js +0 -56
- package/dist/types-B5C1DLu1.d.ts +0 -365
- package/src/preview/AudioRecorderButton.tsx +0 -117
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { FormEvent } from 'react'
|
|
2
|
+
import { AudioLines, Save } from 'lucide-react'
|
|
3
|
+
import type { TranscriptionMode } from '../types'
|
|
4
|
+
|
|
5
|
+
export interface TranscriptionSettingsFormLabels {
|
|
6
|
+
sectionTitle: string
|
|
7
|
+
enabled: string
|
|
8
|
+
enabledHint: string
|
|
9
|
+
modeTitle: string
|
|
10
|
+
modeAuto: string
|
|
11
|
+
modeAutoHint: string
|
|
12
|
+
modeOnDemand: string
|
|
13
|
+
modeOnDemandHint: string
|
|
14
|
+
unavailable: string
|
|
15
|
+
save: string
|
|
16
|
+
saving: string
|
|
17
|
+
saveSuccess: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const DEFAULT_LABELS: TranscriptionSettingsFormLabels = {
|
|
21
|
+
sectionTitle: 'Transcrição de áudio',
|
|
22
|
+
enabled: 'Transcrever notas de voz',
|
|
23
|
+
enabledHint: 'Converte o áudio do cliente em texto, que o atendente lê e copia direto da conversa.',
|
|
24
|
+
modeTitle: 'Quando transcrever',
|
|
25
|
+
modeAuto: 'Automaticamente',
|
|
26
|
+
modeAutoHint: 'Toda nota de voz recebida é transcrita na hora. Mais cômodo, consome mais cota.',
|
|
27
|
+
modeOnDemand: 'Quando o atendente pedir',
|
|
28
|
+
modeOnDemandHint: 'Transcreve só ao clicar no botão da conversa. Consome cota apenas com áudio que alguém vai ler.',
|
|
29
|
+
unavailable: 'Transcrição não está disponível neste ambiente — fale com quem administra a instalação.',
|
|
30
|
+
save: 'Salvar',
|
|
31
|
+
saving: 'Salvando...',
|
|
32
|
+
saveSuccess: 'Configuração de transcrição salva.',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface TranscriptionSettingsFormProps {
|
|
36
|
+
enabled: boolean
|
|
37
|
+
onEnabledChange: (value: boolean) => void
|
|
38
|
+
mode: TranscriptionMode
|
|
39
|
+
onModeChange: (value: TranscriptionMode) => void
|
|
40
|
+
onSave: (event: FormEvent) => void
|
|
41
|
+
/**
|
|
42
|
+
* A capacidade existe no servidor (engine e credencial configurados)?
|
|
43
|
+
*
|
|
44
|
+
* Distinto de `enabled`: isto é "o ambiente consegue", aquilo é "esta empresa quer". Sem a
|
|
45
|
+
* distinção, um lojista ligaria o interruptor num ambiente sem engine e concluiria que o produto
|
|
46
|
+
* está quebrado — o texto nunca apareceria e nada explicaria por quê.
|
|
47
|
+
*/
|
|
48
|
+
isAvailable?: boolean
|
|
49
|
+
saving?: boolean
|
|
50
|
+
saveSuccess?: boolean
|
|
51
|
+
labels?: Partial<TranscriptionSettingsFormLabels>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Interruptor de transcrição por empresa, para a tela de configurações.
|
|
56
|
+
*
|
|
57
|
+
* Puramente apresentacional, como os outros forms daqui: quem persiste é o host. O pacote não sabe a
|
|
58
|
+
* rota, e a política vive nas configurações do módulo (`settings.transcriptionEnabled`), não em
|
|
59
|
+
* variável de ambiente — mudar de ideia sobre transcrever não deveria exigir deploy.
|
|
60
|
+
*/
|
|
61
|
+
export function TranscriptionSettingsForm({
|
|
62
|
+
enabled,
|
|
63
|
+
onEnabledChange,
|
|
64
|
+
mode,
|
|
65
|
+
onModeChange,
|
|
66
|
+
onSave,
|
|
67
|
+
isAvailable = true,
|
|
68
|
+
saving = false,
|
|
69
|
+
saveSuccess = false,
|
|
70
|
+
labels: labelsOverride,
|
|
71
|
+
}: TranscriptionSettingsFormProps) {
|
|
72
|
+
const labels = { ...DEFAULT_LABELS, ...labelsOverride }
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<section className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
76
|
+
<div className="px-6 py-4 border-b border-gray-100 dark:border-gray-700">
|
|
77
|
+
<h2 className="text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-1.5">
|
|
78
|
+
<AudioLines size={14} className="text-blue-600 dark:text-blue-400" />
|
|
79
|
+
{labels.sectionTitle}
|
|
80
|
+
</h2>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<form onSubmit={onSave} className="p-6 space-y-5">
|
|
84
|
+
{!isAvailable && (
|
|
85
|
+
<div className="bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800 rounded-xl px-4 py-3 text-sm text-amber-800 dark:text-amber-300">
|
|
86
|
+
{labels.unavailable}
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
<label className="flex items-start gap-3 cursor-pointer">
|
|
91
|
+
<input
|
|
92
|
+
type="checkbox"
|
|
93
|
+
checked={enabled}
|
|
94
|
+
onChange={(event) => onEnabledChange(event.target.checked)}
|
|
95
|
+
disabled={!isAvailable}
|
|
96
|
+
className="mt-0.5 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 disabled:opacity-50"
|
|
97
|
+
/>
|
|
98
|
+
<span>
|
|
99
|
+
<span className="block text-sm font-medium text-gray-700 dark:text-gray-300">{labels.enabled}</span>
|
|
100
|
+
<span className="block text-xs text-gray-500 dark:text-gray-400">{labels.enabledHint}</span>
|
|
101
|
+
</span>
|
|
102
|
+
</label>
|
|
103
|
+
|
|
104
|
+
{/* O modo só aparece quando ligado: escolher "quando transcrever" sem transcrever nada é uma
|
|
105
|
+
pergunta sem consequência, e ler as duas opções gasta atenção do operador à toa. */}
|
|
106
|
+
{enabled && (
|
|
107
|
+
<fieldset disabled={!isAvailable} className="space-y-2.5">
|
|
108
|
+
<legend className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{labels.modeTitle}</legend>
|
|
109
|
+
|
|
110
|
+
<ModeOption
|
|
111
|
+
value="onDemand"
|
|
112
|
+
current={mode}
|
|
113
|
+
onSelect={onModeChange}
|
|
114
|
+
title={labels.modeOnDemand}
|
|
115
|
+
hint={labels.modeOnDemandHint}
|
|
116
|
+
/>
|
|
117
|
+
<ModeOption
|
|
118
|
+
value="auto"
|
|
119
|
+
current={mode}
|
|
120
|
+
onSelect={onModeChange}
|
|
121
|
+
title={labels.modeAuto}
|
|
122
|
+
hint={labels.modeAutoHint}
|
|
123
|
+
/>
|
|
124
|
+
</fieldset>
|
|
125
|
+
)}
|
|
126
|
+
|
|
127
|
+
{saveSuccess && (
|
|
128
|
+
<div className="bg-green-50 dark:bg-green-950/40 border border-green-200 dark:border-green-800 rounded-xl px-4 py-3 text-sm text-green-700 dark:text-green-400">
|
|
129
|
+
{labels.saveSuccess}
|
|
130
|
+
</div>
|
|
131
|
+
)}
|
|
132
|
+
|
|
133
|
+
<div className="flex justify-end">
|
|
134
|
+
<button
|
|
135
|
+
data-cv-tooltip={saving ? labels.saving : labels.save} aria-label={saving ? labels.saving : labels.save}
|
|
136
|
+
type="submit"
|
|
137
|
+
disabled={saving || !isAvailable}
|
|
138
|
+
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-semibold px-5 py-2.5 rounded-xl transition-colors text-sm"
|
|
139
|
+
>
|
|
140
|
+
{saving ? (
|
|
141
|
+
<span className="w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" />
|
|
142
|
+
) : (
|
|
143
|
+
<Save size={14} />
|
|
144
|
+
)}
|
|
145
|
+
{saving ? labels.saving : labels.save}
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
</form>
|
|
149
|
+
</section>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function ModeOption({
|
|
154
|
+
value,
|
|
155
|
+
current,
|
|
156
|
+
onSelect,
|
|
157
|
+
title,
|
|
158
|
+
hint,
|
|
159
|
+
}: {
|
|
160
|
+
value: TranscriptionMode
|
|
161
|
+
current: TranscriptionMode
|
|
162
|
+
onSelect: (value: TranscriptionMode) => void
|
|
163
|
+
title: string
|
|
164
|
+
hint: string
|
|
165
|
+
}) {
|
|
166
|
+
const isSelected = current === value
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<label
|
|
170
|
+
className={`flex items-start gap-3 cursor-pointer rounded-xl border px-4 py-3 transition-colors ${
|
|
171
|
+
isSelected
|
|
172
|
+
? 'border-blue-500 bg-blue-50 dark:border-blue-500 dark:bg-blue-950/30'
|
|
173
|
+
: 'border-gray-200 hover:border-gray-300 dark:border-gray-700 dark:hover:border-gray-600'
|
|
174
|
+
}`}
|
|
175
|
+
>
|
|
176
|
+
<input
|
|
177
|
+
type="radio"
|
|
178
|
+
name="transcription-mode"
|
|
179
|
+
value={value}
|
|
180
|
+
checked={isSelected}
|
|
181
|
+
onChange={() => onSelect(value)}
|
|
182
|
+
className="mt-0.5 h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
183
|
+
/>
|
|
184
|
+
<span>
|
|
185
|
+
<span className="block text-sm font-medium text-gray-700 dark:text-gray-300">{title}</span>
|
|
186
|
+
<span className="block text-xs text-gray-500 dark:text-gray-400">{hint}</span>
|
|
187
|
+
</span>
|
|
188
|
+
</label>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
@@ -104,6 +104,7 @@ export function WelcomeFarewellForm({
|
|
|
104
104
|
|
|
105
105
|
<div className="flex justify-end">
|
|
106
106
|
<button
|
|
107
|
+
data-cv-tooltip={saving ? labels.saving : labels.save} aria-label={saving ? labels.saving : labels.save}
|
|
107
108
|
type="submit"
|
|
108
109
|
disabled={saving}
|
|
109
110
|
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-semibold px-5 py-2.5 rounded-xl transition-colors text-sm"
|
|
@@ -297,6 +297,7 @@ export function WhatsAppCreateTemplateForm({
|
|
|
297
297
|
|
|
298
298
|
<div className="flex justify-end">
|
|
299
299
|
<button
|
|
300
|
+
data-cv-tooltip={submitting ? labels.sending : labels.sendForApproval} aria-label={submitting ? labels.sending : labels.sendForApproval}
|
|
300
301
|
type="submit"
|
|
301
302
|
disabled={submitting || !value.name || !value.bodyText}
|
|
302
303
|
className="flex items-center gap-2 bg-emerald-600 hover:bg-emerald-700 disabled:bg-emerald-400 text-white font-semibold px-5 py-2.5 rounded-xl transition-colors text-sm"
|
|
@@ -132,6 +132,7 @@ export function WhatsAppTemplateSettingsForm({
|
|
|
132
132
|
<div className="flex items-center justify-between mb-1.5">
|
|
133
133
|
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">{labels.templateLabel}</label>
|
|
134
134
|
<button
|
|
135
|
+
data-cv-tooltip={labels.refreshList} aria-label={labels.refreshList}
|
|
135
136
|
type="button"
|
|
136
137
|
onClick={onRefreshTemplates}
|
|
137
138
|
disabled={loadingTemplates}
|
|
@@ -180,6 +181,7 @@ export function WhatsAppTemplateSettingsForm({
|
|
|
180
181
|
<div className="flex items-center justify-between mb-1.5">
|
|
181
182
|
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">{labels.variablesLabel}</label>
|
|
182
183
|
<button
|
|
184
|
+
data-cv-tooltip={labels.addVariable} aria-label={labels.addVariable}
|
|
183
185
|
type="button"
|
|
184
186
|
onClick={() => onVariablesChange([...variables, ''])}
|
|
185
187
|
className="flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline"
|
|
@@ -198,7 +200,7 @@ export function WhatsAppTemplateSettingsForm({
|
|
|
198
200
|
key={available.token}
|
|
199
201
|
type="button"
|
|
200
202
|
onClick={() => insertVariable(available.token)}
|
|
201
|
-
|
|
203
|
+
data-cv-tooltip={`Inserir ${available.token}`} aria-label={`Inserir ${available.token}`}
|
|
202
204
|
className="inline-flex items-center gap-1 rounded-md border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-800 px-2 py-1 text-xs hover:border-blue-400 dark:hover:border-blue-500 transition-colors"
|
|
203
205
|
>
|
|
204
206
|
<code className="text-blue-600 dark:text-blue-300">{available.token}</code>
|
|
@@ -233,7 +235,7 @@ export function WhatsAppTemplateSettingsForm({
|
|
|
233
235
|
type="button"
|
|
234
236
|
onClick={() => removeVariable(index)}
|
|
235
237
|
className="text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors flex-shrink-0"
|
|
236
|
-
|
|
238
|
+
data-cv-tooltip={labels.removeVariable} aria-label={labels.removeVariable}
|
|
237
239
|
>
|
|
238
240
|
<Trash2 size={14} />
|
|
239
241
|
</button>
|
|
@@ -250,6 +252,7 @@ export function WhatsAppTemplateSettingsForm({
|
|
|
250
252
|
|
|
251
253
|
<div className="flex justify-end">
|
|
252
254
|
<button
|
|
255
|
+
data-cv-tooltip={saving ? labels.saving : labels.save} aria-label={saving ? labels.saving : labels.save}
|
|
253
256
|
type="submit"
|
|
254
257
|
disabled={saving || !selectedTemplateName.trim()}
|
|
255
258
|
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-semibold px-5 py-2.5 rounded-xl transition-colors text-sm"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
|
|
4
|
+
import { WhatsAppTemplatesSettings } from './WhatsAppTemplatesSettings'
|
|
5
|
+
import type { WhatsAppTemplatesSettingsProps } from './WhatsAppTemplatesSettings'
|
|
6
|
+
|
|
7
|
+
function render(overrides: Partial<WhatsAppTemplatesSettingsProps> = {}): string {
|
|
8
|
+
return renderToStaticMarkup(
|
|
9
|
+
<WhatsAppTemplatesSettings
|
|
10
|
+
templates={[]}
|
|
11
|
+
selectedTemplateName=""
|
|
12
|
+
onSelectTemplate={() => {}}
|
|
13
|
+
variables={[]}
|
|
14
|
+
onVariablesChange={() => {}}
|
|
15
|
+
onSave={() => {}}
|
|
16
|
+
{...overrides}
|
|
17
|
+
/>,
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('WhatsAppTemplatesSettings', () => {
|
|
22
|
+
it('renderiza os formulários extras de papel na aba de seleção', () => {
|
|
23
|
+
const markup = render({ extraRoleForms: <div>MARCADOR_PAPEL_EXTRA</div> })
|
|
24
|
+
|
|
25
|
+
expect(markup).toContain('MARCADOR_PAPEL_EXTRA')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('sem extraRoleForms, o comportamento é idêntico ao de antes', () => {
|
|
29
|
+
const markup = render()
|
|
30
|
+
|
|
31
|
+
expect(markup).not.toContain('MARCADOR_PAPEL_EXTRA')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('aceita previewCompanyName e variableExamples em create sem quebrar a tipagem', () => {
|
|
35
|
+
const markup = render({
|
|
36
|
+
create: {
|
|
37
|
+
value: {
|
|
38
|
+
name: '',
|
|
39
|
+
category: 'UTILITY',
|
|
40
|
+
language: 'pt_BR',
|
|
41
|
+
headerType: 'NONE',
|
|
42
|
+
headerText: '',
|
|
43
|
+
bodyText: '',
|
|
44
|
+
footerText: '',
|
|
45
|
+
},
|
|
46
|
+
onChange: () => {},
|
|
47
|
+
onSubmit: () => {},
|
|
48
|
+
previewCompanyName: 'Empresa Teste',
|
|
49
|
+
variableExamples: ['João', '123'],
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
expect(markup).toContain('Criar template')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('sem create, a aba de criação não aparece', () => {
|
|
57
|
+
const markup = render()
|
|
58
|
+
|
|
59
|
+
expect(markup).not.toContain('Criar template')
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
* Segue presentacional: nenhuma chamada de rede aqui. Templates, estado e handlers vêm por props.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { useState, type FormEvent } from 'react'
|
|
11
|
+
import { useState, type FormEvent, type ReactNode } from 'react'
|
|
12
12
|
import {
|
|
13
13
|
WhatsAppTemplateSettingsForm,
|
|
14
14
|
type WhatsAppTemplateSummary,
|
|
15
15
|
type WhatsAppTemplateVariableSuggestion,
|
|
16
|
+
type WhatsAppTemplateSettingsFormLabels,
|
|
16
17
|
} from './WhatsAppTemplateSettingsForm'
|
|
17
18
|
import {
|
|
18
19
|
WhatsAppCreateTemplateForm,
|
|
20
|
+
type WhatsAppCreateTemplateFormLabels,
|
|
19
21
|
type WhatsAppCreateTemplateResult,
|
|
20
22
|
type WhatsAppCreateTemplateState,
|
|
21
23
|
} from './WhatsAppCreateTemplateForm'
|
|
@@ -56,13 +58,26 @@ export interface WhatsAppTemplatesSettingsProps {
|
|
|
56
58
|
onSubmit: (event: FormEvent) => void
|
|
57
59
|
submitting?: boolean
|
|
58
60
|
result?: WhatsAppCreateTemplateResult | null
|
|
61
|
+
labels?: Partial<WhatsAppCreateTemplateFormLabels>
|
|
62
|
+
/** Nome exibido na prévia do template criado; sem isto o form usa seu próprio fallback. */
|
|
63
|
+
previewCompanyName?: string
|
|
64
|
+
variableExamples?: readonly string[]
|
|
59
65
|
}
|
|
60
66
|
labels?: Partial<WhatsAppTemplatesSettingsLabels>
|
|
67
|
+
/** Vocabulário do formulário de seleção — separado de `labels`, que nomeia só as abas daqui. */
|
|
68
|
+
settingsLabels?: Partial<WhatsAppTemplateSettingsFormLabels>
|
|
69
|
+
/**
|
|
70
|
+
* Formulários extras de seleção (um por papel adicional de template, ex.: despedida), empilhados
|
|
71
|
+
* abaixo do principal na mesma sub-aba. Ausente = só o papel principal, comportamento de sempre.
|
|
72
|
+
*/
|
|
73
|
+
extraRoleForms?: ReactNode
|
|
61
74
|
}
|
|
62
75
|
|
|
63
76
|
export function WhatsAppTemplatesSettings({
|
|
64
77
|
labels: labelsOverride,
|
|
78
|
+
settingsLabels,
|
|
65
79
|
create,
|
|
80
|
+
extraRoleForms,
|
|
66
81
|
...settingsProps
|
|
67
82
|
}: WhatsAppTemplatesSettingsProps) {
|
|
68
83
|
const labels = { ...DEFAULT_TEMPLATES_SETTINGS_LABELS, ...labelsOverride }
|
|
@@ -72,6 +87,7 @@ export function WhatsAppTemplatesSettings({
|
|
|
72
87
|
<div className="space-y-4">
|
|
73
88
|
<nav className="flex gap-1 border-b" role="tablist">
|
|
74
89
|
<button
|
|
90
|
+
data-cv-tooltip={labels.selectTab} aria-label={labels.selectTab}
|
|
75
91
|
type="button"
|
|
76
92
|
role="tab"
|
|
77
93
|
aria-selected={tab === TEMPLATE_SETTINGS_TAB.SELECT}
|
|
@@ -85,6 +101,7 @@ export function WhatsAppTemplatesSettings({
|
|
|
85
101
|
que não leva a nada. */}
|
|
86
102
|
{create ? (
|
|
87
103
|
<button
|
|
104
|
+
data-cv-tooltip={labels.createTab} aria-label={labels.createTab}
|
|
88
105
|
type="button"
|
|
89
106
|
role="tab"
|
|
90
107
|
aria-selected={tab === TEMPLATE_SETTINGS_TAB.CREATE}
|
|
@@ -97,7 +114,10 @@ export function WhatsAppTemplatesSettings({
|
|
|
97
114
|
</nav>
|
|
98
115
|
|
|
99
116
|
{tab === TEMPLATE_SETTINGS_TAB.SELECT ? (
|
|
100
|
-
<
|
|
117
|
+
<div className="space-y-6">
|
|
118
|
+
<WhatsAppTemplateSettingsForm {...settingsProps} {...(settingsLabels ? { labels: settingsLabels } : {})} />
|
|
119
|
+
{extraRoleForms}
|
|
120
|
+
</div>
|
|
101
121
|
) : create ? (
|
|
102
122
|
<WhatsAppCreateTemplateForm {...create} />
|
|
103
123
|
) : null}
|