@adatechnology/conversations-ui 0.1.0-rc.21 → 0.1.0-rc.23
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 +237 -3
- package/dist/index.js +854 -0
- package/dist/preview/index.d.ts +23 -1
- package/dist/preview/index.js +48 -14
- package/dist/styles.css +559 -0
- package/package.json +1 -1
- package/src/index.ts +15 -0
- package/src/preview/ConversationSimulatorPanel.test.tsx +55 -0
- package/src/preview/ConversationSimulatorPanel.tsx +89 -0
- package/src/preview/createPreviewWebhookClient.test.ts +11 -4
- package/src/preview/index.ts +3 -0
- package/src/styles.css +542 -0
- package/src/workspace/BulkTemplateModal.tsx +130 -0
- package/src/workspace/ConversationPane.tsx +287 -0
- package/src/workspace/ConversationsInboxList.tsx +185 -0
- package/src/workspace/ConversationsWorkspace.tsx +306 -0
- package/src/workspace/index.ts +12 -0
- package/src/workspace/labels.test.ts +17 -0
- package/src/workspace/labels.ts +80 -0
- package/src/workspace/useConversationsInbox.ts +329 -0
package/src/styles.css
CHANGED
|
@@ -192,3 +192,545 @@
|
|
|
192
192
|
:where(button:not(:disabled), [role='button']:not(:disabled), summary) {
|
|
193
193
|
cursor: pointer;
|
|
194
194
|
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Simulador de cliente. Largura fixa porque é painel auxiliar: a thread ao lado é que deve
|
|
198
|
+
* ganhar o espaço restante quando a janela encolhe.
|
|
199
|
+
*/
|
|
200
|
+
.cv-simulator-panel {
|
|
201
|
+
display: flex;
|
|
202
|
+
flex-direction: column;
|
|
203
|
+
width: 24rem;
|
|
204
|
+
flex-shrink: 0;
|
|
205
|
+
border-left: 1px solid rgb(229 231 235);
|
|
206
|
+
background: #fff;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.cv-simulator-panel__header {
|
|
210
|
+
display: flex;
|
|
211
|
+
align-items: center;
|
|
212
|
+
justify-content: space-between;
|
|
213
|
+
gap: 0.5rem;
|
|
214
|
+
padding: 0.75rem 1rem;
|
|
215
|
+
border-bottom: 1px solid rgb(229 231 235);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.cv-simulator-panel__heading { min-width: 0; }
|
|
219
|
+
|
|
220
|
+
.cv-simulator-panel__title {
|
|
221
|
+
font-size: 0.875rem;
|
|
222
|
+
font-weight: 600;
|
|
223
|
+
color: rgb(17 24 39);
|
|
224
|
+
overflow: hidden;
|
|
225
|
+
text-overflow: ellipsis;
|
|
226
|
+
white-space: nowrap;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.cv-simulator-panel__subtitle {
|
|
230
|
+
font-size: 0.75rem;
|
|
231
|
+
color: rgb(156 163 175);
|
|
232
|
+
overflow: hidden;
|
|
233
|
+
text-overflow: ellipsis;
|
|
234
|
+
white-space: nowrap;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.cv-simulator-panel__actions {
|
|
238
|
+
display: flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
gap: 0.25rem;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.cv-simulator-panel__close {
|
|
244
|
+
display: inline-flex;
|
|
245
|
+
padding: 0.375rem;
|
|
246
|
+
border: 0;
|
|
247
|
+
border-radius: 0.5rem;
|
|
248
|
+
background: transparent;
|
|
249
|
+
color: rgb(156 163 175);
|
|
250
|
+
transition: color 150ms, background-color 150ms;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.cv-simulator-panel__close:hover {
|
|
254
|
+
color: rgb(55 65 81);
|
|
255
|
+
background: rgb(243 244 246);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* `min-height: 0` para o transcript rolar dentro do painel em vez de esticar o `<aside>`. */
|
|
259
|
+
.cv-simulator-panel__body { flex: 1; min-height: 0; }
|
|
260
|
+
|
|
261
|
+
.dark .cv-simulator-panel {
|
|
262
|
+
border-left-color: rgb(31 41 55);
|
|
263
|
+
background: rgb(17 24 39);
|
|
264
|
+
}
|
|
265
|
+
.dark .cv-simulator-panel__header { border-bottom-color: rgb(31 41 55); }
|
|
266
|
+
.dark .cv-simulator-panel__title { color: rgb(243 244 246); }
|
|
267
|
+
.dark .cv-simulator-panel__subtitle { color: rgb(107 114 128); }
|
|
268
|
+
.dark .cv-simulator-panel__close:hover { color: rgb(229 231 235); background: rgb(31 41 55); }
|
|
269
|
+
|
|
270
|
+
/* Painel auxiliar não cabe ao lado da thread em tela estreita: vira sobreposição de tela cheia. */
|
|
271
|
+
@media (max-width: 1023px) {
|
|
272
|
+
.cv-simulator-panel {
|
|
273
|
+
position: fixed;
|
|
274
|
+
inset: 0;
|
|
275
|
+
width: 100%;
|
|
276
|
+
z-index: 40;
|
|
277
|
+
border-left: 0;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Workspace de atendimento: a grade da tela inteira (lista · conversa · simulador).
|
|
283
|
+
*
|
|
284
|
+
* Cada coluna é um cartão sobre fundo neutro, com respiro entre elas — separação por espaço lê mais
|
|
285
|
+
* rápido que por borda. Todas as alturas presas em `min-height: 0` porque, em grid e flex, o padrão
|
|
286
|
+
* é não encolher abaixo do conteúdo: sem isso o scroll interno nunca ativa e quem rola é a página.
|
|
287
|
+
*/
|
|
288
|
+
.cv-workspace {
|
|
289
|
+
display: flex;
|
|
290
|
+
flex-direction: column;
|
|
291
|
+
height: 100%;
|
|
292
|
+
min-height: 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.cv-workspace-header {
|
|
296
|
+
display: flex;
|
|
297
|
+
align-items: center;
|
|
298
|
+
justify-content: space-between;
|
|
299
|
+
gap: 0.5rem;
|
|
300
|
+
padding: 0.75rem 1rem;
|
|
301
|
+
border-bottom: 1px solid rgb(229 231 235);
|
|
302
|
+
}
|
|
303
|
+
.dark .cv-workspace-header { border-color: rgb(51 65 85); }
|
|
304
|
+
|
|
305
|
+
.cv-workspace-header__titles { min-width: 0; }
|
|
306
|
+
.cv-workspace-header__titles h1 {
|
|
307
|
+
font-size: 1.125rem;
|
|
308
|
+
font-weight: 600;
|
|
309
|
+
overflow: hidden;
|
|
310
|
+
text-overflow: ellipsis;
|
|
311
|
+
white-space: nowrap;
|
|
312
|
+
}
|
|
313
|
+
.cv-workspace-header__titles p {
|
|
314
|
+
display: flex;
|
|
315
|
+
flex-wrap: wrap;
|
|
316
|
+
gap: 0.25rem 0.75rem;
|
|
317
|
+
font-size: 0.875rem;
|
|
318
|
+
color: rgb(107 114 128);
|
|
319
|
+
}
|
|
320
|
+
.cv-workspace-header__waiting { color: rgb(217 119 6); }
|
|
321
|
+
.dark .cv-workspace-header__waiting { color: rgb(251 191 36); }
|
|
322
|
+
|
|
323
|
+
.cv-workspace-header__actions {
|
|
324
|
+
display: flex;
|
|
325
|
+
flex-shrink: 0;
|
|
326
|
+
align-items: center;
|
|
327
|
+
gap: 0.5rem;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.cv-workspace-toggle {
|
|
331
|
+
border: 1px solid rgb(229 231 235);
|
|
332
|
+
border-radius: 0.375rem;
|
|
333
|
+
padding: 0.5rem 0.75rem;
|
|
334
|
+
font-size: 0.875rem;
|
|
335
|
+
/* 44px de área de toque em mobile — abaixo disso o dedo erra o alvo. */
|
|
336
|
+
min-height: 2.75rem;
|
|
337
|
+
}
|
|
338
|
+
.cv-workspace-toggle:disabled { opacity: 0.5; }
|
|
339
|
+
.cv-workspace-toggle--on { background: rgb(37 99 235); color: #fff; border-color: rgb(37 99 235); }
|
|
340
|
+
.dark .cv-workspace-toggle { border-color: rgb(71 85 105); color: rgb(226 232 240); }
|
|
341
|
+
|
|
342
|
+
.cv-workspace-failure {
|
|
343
|
+
border-bottom: 1px solid rgb(253 230 138);
|
|
344
|
+
background: rgb(255 251 235);
|
|
345
|
+
padding: 0.5rem 1rem;
|
|
346
|
+
font-size: 0.875rem;
|
|
347
|
+
color: rgb(146 64 14);
|
|
348
|
+
}
|
|
349
|
+
.dark .cv-workspace-failure { background: rgb(69 26 3); color: rgb(253 230 138); border-color: rgb(120 53 15); }
|
|
350
|
+
|
|
351
|
+
.cv-workspace-grid {
|
|
352
|
+
display: grid;
|
|
353
|
+
flex: 1;
|
|
354
|
+
min-height: 0;
|
|
355
|
+
grid-template-columns: minmax(0, 1fr);
|
|
356
|
+
gap: 0.75rem;
|
|
357
|
+
padding: 0.75rem;
|
|
358
|
+
background: rgb(243 244 246);
|
|
359
|
+
}
|
|
360
|
+
.dark .cv-workspace-grid { background: rgb(17 24 39); }
|
|
361
|
+
|
|
362
|
+
.cv-workspace-list,
|
|
363
|
+
.cv-workspace-detail,
|
|
364
|
+
.cv-workspace-simulator {
|
|
365
|
+
display: flex;
|
|
366
|
+
flex-direction: column;
|
|
367
|
+
min-height: 0;
|
|
368
|
+
min-width: 0;
|
|
369
|
+
overflow: hidden;
|
|
370
|
+
border: 1px solid rgb(229 231 235);
|
|
371
|
+
border-radius: 0.75rem;
|
|
372
|
+
background: #fff;
|
|
373
|
+
}
|
|
374
|
+
.dark .cv-workspace-list,
|
|
375
|
+
.dark .cv-workspace-detail,
|
|
376
|
+
.dark .cv-workspace-simulator { background: rgb(31 41 55); border-color: rgb(51 65 85); }
|
|
377
|
+
|
|
378
|
+
/* Em linha, e não em coluna: o painel do simulador não tem teto de altura — empilhado ele cresce
|
|
379
|
+
com o transcript e transborda o cartão; em linha herda a altura da faixa e rola por dentro. */
|
|
380
|
+
.cv-workspace-simulator { flex-direction: row; }
|
|
381
|
+
|
|
382
|
+
/* Master/detail abaixo de 1024px: com conversa aberta, some a lista; sem ela, some o detalhe.
|
|
383
|
+
Empilhar os dois na mesma altura fixa dava duas fatias inúteis. */
|
|
384
|
+
@media (max-width: 1023px) {
|
|
385
|
+
.cv-workspace-grid[data-detail='open'] .cv-workspace-list { display: none; }
|
|
386
|
+
.cv-workspace-grid[data-detail='closed'] .cv-workspace-detail { display: none; }
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
@media (min-width: 1024px) {
|
|
390
|
+
.cv-workspace-grid { grid-template-columns: 360px minmax(0, 1fr); }
|
|
391
|
+
/* Entre `lg` e `xl` as três não cabem — sobrariam ~100px para a conversa e o texto quebraria uma
|
|
392
|
+
palavra por linha. Enquanto se testa UMA conversa, é a lista que menos importa. */
|
|
393
|
+
.cv-workspace-grid--with-simulator { grid-template-columns: minmax(0, 1fr) 24rem; }
|
|
394
|
+
.cv-workspace-grid--with-simulator .cv-workspace-list { display: none; }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@media (min-width: 1280px) {
|
|
398
|
+
.cv-workspace-grid--with-simulator { grid-template-columns: 320px minmax(0, 1fr) 24rem; }
|
|
399
|
+
.cv-workspace-grid--with-simulator .cv-workspace-list { display: flex; }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.cv-workspace-pane {
|
|
403
|
+
display: flex;
|
|
404
|
+
flex-direction: column;
|
|
405
|
+
height: 100%;
|
|
406
|
+
min-height: 0;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.cv-workspace-transcript {
|
|
410
|
+
position: relative;
|
|
411
|
+
flex: 1;
|
|
412
|
+
min-height: 0;
|
|
413
|
+
overflow-y: auto;
|
|
414
|
+
padding: 0.75rem 1rem;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.cv-workspace-failure__link {
|
|
418
|
+
text-decoration: underline;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.cv-workspace-selection {
|
|
422
|
+
display: flex;
|
|
423
|
+
align-items: center;
|
|
424
|
+
justify-content: space-between;
|
|
425
|
+
gap: 0.5rem;
|
|
426
|
+
border-top: 1px solid #bfdbfe;
|
|
427
|
+
background: #eff6ff;
|
|
428
|
+
padding: 0.5rem 0.75rem;
|
|
429
|
+
font-size: 0.8125rem;
|
|
430
|
+
color: #1d4ed8;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.cv-workspace-selection__actions {
|
|
434
|
+
display: flex;
|
|
435
|
+
gap: 0.5rem;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.cv-workspace-selection__actions button {
|
|
439
|
+
min-height: 2.75rem;
|
|
440
|
+
border-radius: 0.375rem;
|
|
441
|
+
border: 1px solid #bfdbfe;
|
|
442
|
+
padding: 0 0.625rem;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.dark .cv-workspace-selection {
|
|
446
|
+
border-color: #1e40af;
|
|
447
|
+
background: rgb(23 37 84 / 40%);
|
|
448
|
+
color: #93c5fd;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.dark .cv-workspace-selection__actions button {
|
|
452
|
+
border-color: #1e40af;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.cv-workspace-notice {
|
|
456
|
+
border-top: 1px solid var(--cv-border, #e5e7eb);
|
|
457
|
+
background: #f3f4f6;
|
|
458
|
+
padding: 0.75rem 1rem;
|
|
459
|
+
text-align: center;
|
|
460
|
+
font-size: 0.875rem;
|
|
461
|
+
color: #6b7280;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.dark .cv-workspace-notice {
|
|
465
|
+
border-top-color: #374151;
|
|
466
|
+
background: #1f2937;
|
|
467
|
+
color: #9ca3af;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.cv-workspace-alert {
|
|
471
|
+
border-top: 1px solid rgb(254 202 202);
|
|
472
|
+
background: rgb(254 242 242);
|
|
473
|
+
padding: 0.5rem 1rem;
|
|
474
|
+
font-size: 0.875rem;
|
|
475
|
+
color: rgb(185 28 28);
|
|
476
|
+
}
|
|
477
|
+
.dark .cv-workspace-alert { background: rgb(69 10 10); color: rgb(252 165 165); border-color: rgb(127 29 29); }
|
|
478
|
+
|
|
479
|
+
.cv-workspace-filters {
|
|
480
|
+
display: flex;
|
|
481
|
+
flex-direction: column;
|
|
482
|
+
gap: 0.5rem;
|
|
483
|
+
padding: 0.75rem;
|
|
484
|
+
border-bottom: 1px solid rgb(229 231 235);
|
|
485
|
+
}
|
|
486
|
+
.dark .cv-workspace-filters { border-color: rgb(51 65 85); }
|
|
487
|
+
|
|
488
|
+
.cv-workspace-search {
|
|
489
|
+
width: 100%;
|
|
490
|
+
border: 1px solid rgb(229 231 235);
|
|
491
|
+
border-radius: 0.375rem;
|
|
492
|
+
padding: 0.5rem 0.75rem;
|
|
493
|
+
font-size: 0.875rem;
|
|
494
|
+
background: transparent;
|
|
495
|
+
}
|
|
496
|
+
.dark .cv-workspace-search { border-color: rgb(71 85 105); color: rgb(226 232 240); }
|
|
497
|
+
|
|
498
|
+
.cv-workspace-chips {
|
|
499
|
+
display: flex;
|
|
500
|
+
flex-wrap: wrap;
|
|
501
|
+
align-items: center;
|
|
502
|
+
gap: 0.5rem;
|
|
503
|
+
font-size: 0.75rem;
|
|
504
|
+
}
|
|
505
|
+
.cv-workspace-chips__legend { color: rgb(107 114 128); }
|
|
506
|
+
.cv-workspace-chip {
|
|
507
|
+
display: flex;
|
|
508
|
+
align-items: center;
|
|
509
|
+
gap: 0.25rem;
|
|
510
|
+
border-radius: 0.375rem;
|
|
511
|
+
padding: 0.25rem 0.5rem;
|
|
512
|
+
}
|
|
513
|
+
.cv-workspace-chip--on { background: rgb(229 231 235); }
|
|
514
|
+
.dark .cv-workspace-chip--on { background: rgb(51 65 85); }
|
|
515
|
+
.cv-workspace-dot { width: 0.5rem; height: 0.5rem; border-radius: 9999px; }
|
|
516
|
+
|
|
517
|
+
.cv-workspace-selectall {
|
|
518
|
+
display: flex;
|
|
519
|
+
align-items: center;
|
|
520
|
+
gap: 0.5rem;
|
|
521
|
+
font-size: 0.75rem;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.cv-workspace-rows { flex: 1; min-height: 0; overflow-y: auto; }
|
|
525
|
+
|
|
526
|
+
.cv-workspace-empty { padding: 1rem; font-size: 0.875rem; color: rgb(107 114 128); }
|
|
527
|
+
|
|
528
|
+
.cv-workspace-bulk {
|
|
529
|
+
display: flex;
|
|
530
|
+
flex-wrap: wrap;
|
|
531
|
+
align-items: center;
|
|
532
|
+
justify-content: space-between;
|
|
533
|
+
gap: 0.5rem;
|
|
534
|
+
padding: 0.5rem 0.75rem;
|
|
535
|
+
border-bottom: 1px solid rgb(191 219 254);
|
|
536
|
+
background: rgb(239 246 255);
|
|
537
|
+
font-size: 0.75rem;
|
|
538
|
+
}
|
|
539
|
+
.dark .cv-workspace-bulk { background: rgb(23 37 84); border-color: rgb(30 58 138); }
|
|
540
|
+
.cv-workspace-bulk__count { font-weight: 500; color: rgb(29 78 216); }
|
|
541
|
+
.dark .cv-workspace-bulk__count { color: rgb(147 197 253); }
|
|
542
|
+
.cv-workspace-bulk__actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
|
543
|
+
.cv-workspace-bulk__actions button {
|
|
544
|
+
border: 1px solid rgb(191 219 254);
|
|
545
|
+
border-radius: 0.375rem;
|
|
546
|
+
padding: 0.25rem 0.5rem;
|
|
547
|
+
}
|
|
548
|
+
.cv-workspace-bulk__actions button:disabled { opacity: 0.5; }
|
|
549
|
+
|
|
550
|
+
.cv-workspace-pager {
|
|
551
|
+
display: flex;
|
|
552
|
+
align-items: center;
|
|
553
|
+
justify-content: space-between;
|
|
554
|
+
gap: 0.5rem;
|
|
555
|
+
border-top: 1px solid rgb(229 231 235);
|
|
556
|
+
padding: 0.5rem 0.75rem;
|
|
557
|
+
font-size: 0.75rem;
|
|
558
|
+
color: rgb(107 114 128);
|
|
559
|
+
}
|
|
560
|
+
.dark .cv-workspace-pager { border-color: rgb(51 65 85); }
|
|
561
|
+
.cv-workspace-pager__buttons { display: flex; align-items: center; gap: 0.25rem; }
|
|
562
|
+
.cv-workspace-pager__buttons button { padding: 0 0.5rem; }
|
|
563
|
+
.cv-workspace-pager__buttons button:disabled { opacity: 0.4; }
|
|
564
|
+
|
|
565
|
+
/* Modal de template do envio em lote. Sobre a tela inteira e não dentro da coluna da lista: a
|
|
566
|
+
escolha vale para todas as conversas marcadas, e ancorado na lista parecia filtro dela. */
|
|
567
|
+
.cv-workspace-modal {
|
|
568
|
+
position: fixed;
|
|
569
|
+
inset: 0;
|
|
570
|
+
z-index: 50;
|
|
571
|
+
display: flex;
|
|
572
|
+
align-items: center;
|
|
573
|
+
justify-content: center;
|
|
574
|
+
padding: 1rem;
|
|
575
|
+
background: rgb(0 0 0 / 50%);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.cv-workspace-modal__box {
|
|
579
|
+
display: flex;
|
|
580
|
+
width: 100%;
|
|
581
|
+
max-width: 28rem;
|
|
582
|
+
max-height: 80vh;
|
|
583
|
+
flex-direction: column;
|
|
584
|
+
overflow: hidden;
|
|
585
|
+
border-radius: 0.75rem;
|
|
586
|
+
border: 1px solid #e5e7eb;
|
|
587
|
+
background: #fff;
|
|
588
|
+
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 25%);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.cv-workspace-modal__header {
|
|
592
|
+
display: flex;
|
|
593
|
+
align-items: flex-start;
|
|
594
|
+
justify-content: space-between;
|
|
595
|
+
gap: 0.75rem;
|
|
596
|
+
border-bottom: 1px solid #e5e7eb;
|
|
597
|
+
padding: 1rem 1.25rem;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.cv-workspace-modal__header h3 {
|
|
601
|
+
font-weight: 600;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.cv-workspace-modal__header p {
|
|
605
|
+
margin-top: 0.125rem;
|
|
606
|
+
font-size: 0.75rem;
|
|
607
|
+
color: #6b7280;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.cv-workspace-modal__search {
|
|
611
|
+
border-bottom: 1px solid #f3f4f6;
|
|
612
|
+
padding: 0.75rem 1rem;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.cv-workspace-modal__search input {
|
|
616
|
+
width: 100%;
|
|
617
|
+
border-radius: 0.5rem;
|
|
618
|
+
border: 1px solid #e5e7eb;
|
|
619
|
+
padding: 0.5rem 0.75rem;
|
|
620
|
+
font-size: 0.875rem;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.cv-workspace-modal__list {
|
|
624
|
+
flex: 1;
|
|
625
|
+
overflow-y: auto;
|
|
626
|
+
padding: 0.5rem;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.cv-workspace-modal__item {
|
|
630
|
+
display: flex;
|
|
631
|
+
width: 100%;
|
|
632
|
+
flex-direction: column;
|
|
633
|
+
gap: 0.125rem;
|
|
634
|
+
border: 1px solid transparent;
|
|
635
|
+
border-radius: 0.5rem;
|
|
636
|
+
padding: 0.625rem 0.75rem;
|
|
637
|
+
text-align: left;
|
|
638
|
+
font-size: 0.875rem;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.cv-workspace-modal__item:hover {
|
|
642
|
+
background: #f9fafb;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.cv-workspace-modal__item--on {
|
|
646
|
+
border-color: #bfdbfe;
|
|
647
|
+
background: #eff6ff;
|
|
648
|
+
color: #1d4ed8;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.cv-workspace-modal__item-name {
|
|
652
|
+
overflow: hidden;
|
|
653
|
+
text-overflow: ellipsis;
|
|
654
|
+
white-space: nowrap;
|
|
655
|
+
font-weight: 500;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.cv-workspace-modal__item-meta {
|
|
659
|
+
font-size: 0.75rem;
|
|
660
|
+
color: #9ca3af;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.cv-workspace-modal__warning {
|
|
664
|
+
border-top: 1px solid #fde68a;
|
|
665
|
+
background: #fffbeb;
|
|
666
|
+
padding: 0.5rem 1rem;
|
|
667
|
+
font-size: 0.75rem;
|
|
668
|
+
color: #b45309;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.cv-workspace-modal__footer {
|
|
672
|
+
display: flex;
|
|
673
|
+
justify-content: flex-end;
|
|
674
|
+
gap: 0.5rem;
|
|
675
|
+
border-top: 1px solid #e5e7eb;
|
|
676
|
+
padding: 0.75rem 1rem;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.cv-workspace-modal__footer button {
|
|
680
|
+
min-height: 2.75rem;
|
|
681
|
+
border-radius: 0.5rem;
|
|
682
|
+
border: 1px solid #e5e7eb;
|
|
683
|
+
padding: 0 0.875rem;
|
|
684
|
+
font-size: 0.875rem;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.cv-workspace-modal__footer button:last-child {
|
|
688
|
+
border-color: transparent;
|
|
689
|
+
background: #2563eb;
|
|
690
|
+
color: #fff;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.cv-workspace-modal__footer button:disabled {
|
|
694
|
+
opacity: 0.5;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.dark .cv-workspace-modal__box {
|
|
698
|
+
border-color: #374151;
|
|
699
|
+
background: #1f2937;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.dark .cv-workspace-modal__header,
|
|
703
|
+
.dark .cv-workspace-modal__footer {
|
|
704
|
+
border-color: #374151;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.dark .cv-workspace-modal__search {
|
|
708
|
+
border-color: #374151;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.dark .cv-workspace-modal__search input {
|
|
712
|
+
border-color: #4b5563;
|
|
713
|
+
background: #374151;
|
|
714
|
+
color: #f3f4f6;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.dark .cv-workspace-modal__item:hover {
|
|
718
|
+
background: rgb(55 65 81 / 50%);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.dark .cv-workspace-modal__item--on {
|
|
722
|
+
border-color: #1e40af;
|
|
723
|
+
background: rgb(23 37 84 / 40%);
|
|
724
|
+
color: #93c5fd;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
.dark .cv-workspace-modal__warning {
|
|
728
|
+
border-color: #92400e;
|
|
729
|
+
background: rgb(69 26 3 / 40%);
|
|
730
|
+
color: #fbbf24;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.dark .cv-workspace-modal__footer button {
|
|
734
|
+
border-color: #4b5563;
|
|
735
|
+
color: #f3f4f6;
|
|
736
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escolha do template para o envio em lote.
|
|
3
|
+
*
|
|
4
|
+
* Fora da janela de 24h só template abre conversa, e a lista aprovada muda por conta da Meta — daí
|
|
5
|
+
* a busca: quem tem trinta templates não acha o certo rolando. Só aparece quando o host implementa
|
|
6
|
+
* `listTemplates`; sem a listagem não há o que escolher, e o envio cai no template padrão.
|
|
7
|
+
*
|
|
8
|
+
* O aviso de "nenhuma selecionada está fora da janela" existe porque o envio parecia falhar em
|
|
9
|
+
* silêncio: dentro da janela a Meta recusa o template, e a tela não dizia por quê.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
13
|
+
|
|
14
|
+
import { useConversations } from '../providers/ConversationsProvider'
|
|
15
|
+
import type { ConversationTemplate } from '../providers/types'
|
|
16
|
+
import type { ConversationsWorkspaceLabels } from './labels'
|
|
17
|
+
|
|
18
|
+
export interface BulkTemplateModalProps {
|
|
19
|
+
readonly labels: ConversationsWorkspaceLabels
|
|
20
|
+
/** Quantas das selecionadas estão fora da janela — as únicas que o template atinge. */
|
|
21
|
+
readonly expiredCount: number
|
|
22
|
+
readonly sending: boolean
|
|
23
|
+
readonly onClose: () => void
|
|
24
|
+
readonly onSend: (templateName: string | undefined) => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function BulkTemplateModal({
|
|
28
|
+
labels,
|
|
29
|
+
expiredCount,
|
|
30
|
+
sending,
|
|
31
|
+
onClose,
|
|
32
|
+
onSend,
|
|
33
|
+
}: BulkTemplateModalProps) {
|
|
34
|
+
const context = useConversations()
|
|
35
|
+
if (!context) {
|
|
36
|
+
throw new Error('BulkTemplateModal requires an ancestor <ConversationsProvider>')
|
|
37
|
+
}
|
|
38
|
+
const { api } = context
|
|
39
|
+
|
|
40
|
+
const [templates, setTemplates] = useState<readonly ConversationTemplate[]>([])
|
|
41
|
+
const [search, setSearch] = useState('')
|
|
42
|
+
const [selected, setSelected] = useState('')
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
let active = true
|
|
46
|
+
void api
|
|
47
|
+
.listTemplates?.()
|
|
48
|
+
.then((loaded) => {
|
|
49
|
+
if (active) setTemplates(loaded)
|
|
50
|
+
})
|
|
51
|
+
.catch(() => {
|
|
52
|
+
// Lista vazia já comunica o que houve; um alerta a mais competiria com o atendimento.
|
|
53
|
+
})
|
|
54
|
+
return () => {
|
|
55
|
+
active = false
|
|
56
|
+
}
|
|
57
|
+
}, [api])
|
|
58
|
+
|
|
59
|
+
const filtered = useMemo(() => {
|
|
60
|
+
const term = search.trim().toLowerCase()
|
|
61
|
+
if (!term) return templates
|
|
62
|
+
return templates.filter((template) => template.name.toLowerCase().includes(term))
|
|
63
|
+
}, [templates, search])
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className="cv-workspace-modal" role="dialog" aria-modal="true" aria-label={labels.templateModalTitle}>
|
|
67
|
+
<div className="cv-workspace-modal__box">
|
|
68
|
+
<header className="cv-workspace-modal__header">
|
|
69
|
+
<div>
|
|
70
|
+
<h3>{labels.templateModalTitle}</h3>
|
|
71
|
+
<p>{labels.templateModalAvailable(templates.length)}</p>
|
|
72
|
+
</div>
|
|
73
|
+
<button type="button" onClick={onClose} aria-label={labels.templateModalCancel}>
|
|
74
|
+
✕
|
|
75
|
+
</button>
|
|
76
|
+
</header>
|
|
77
|
+
|
|
78
|
+
<div className="cv-workspace-modal__search">
|
|
79
|
+
<input
|
|
80
|
+
type="search"
|
|
81
|
+
value={search}
|
|
82
|
+
onChange={(event) => setSearch(event.target.value)}
|
|
83
|
+
placeholder={labels.templateModalSearch}
|
|
84
|
+
autoFocus
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div className="cv-workspace-modal__list">
|
|
89
|
+
{filtered.length === 0 ? <p className="cv-workspace-empty">{labels.templateModalEmpty}</p> : null}
|
|
90
|
+
{filtered.map((template) => (
|
|
91
|
+
<button
|
|
92
|
+
key={template.name}
|
|
93
|
+
type="button"
|
|
94
|
+
// Reclicar desmarca: sem isso não havia como voltar ao template padrão depois de
|
|
95
|
+
// escolher um por engano.
|
|
96
|
+
onClick={() => setSelected(template.name === selected ? '' : template.name)}
|
|
97
|
+
aria-pressed={template.name === selected}
|
|
98
|
+
className={`cv-workspace-modal__item${
|
|
99
|
+
template.name === selected ? ' cv-workspace-modal__item--on' : ''
|
|
100
|
+
}`}
|
|
101
|
+
>
|
|
102
|
+
<span className="cv-workspace-modal__item-name">{template.name}</span>
|
|
103
|
+
<span className="cv-workspace-modal__item-meta">
|
|
104
|
+
{template.language}
|
|
105
|
+
{template.category ? ` · ${template.category.toLowerCase()}` : ''}
|
|
106
|
+
</span>
|
|
107
|
+
</button>
|
|
108
|
+
))}
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{expiredCount === 0 ? (
|
|
112
|
+
<p className="cv-workspace-modal__warning">{labels.templateModalNoneExpired}</p>
|
|
113
|
+
) : null}
|
|
114
|
+
|
|
115
|
+
<footer className="cv-workspace-modal__footer">
|
|
116
|
+
<button type="button" onClick={onClose}>
|
|
117
|
+
{labels.templateModalCancel}
|
|
118
|
+
</button>
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
disabled={sending || expiredCount === 0}
|
|
122
|
+
onClick={() => onSend(selected || undefined)}
|
|
123
|
+
>
|
|
124
|
+
{sending ? labels.templateModalSending : labels.templateModalSend(expiredCount)}
|
|
125
|
+
</button>
|
|
126
|
+
</footer>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
)
|
|
130
|
+
}
|