@greatapps/greatchat-ui 0.1.0 → 0.1.1

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.js CHANGED
@@ -101,8 +101,888 @@ import { twMerge } from "tailwind-merge";
101
101
  function cn(...inputs) {
102
102
  return twMerge(clsx(inputs));
103
103
  }
104
+
105
+ // src/utils/group-messages.ts
106
+ function groupMessagesByDate(messages) {
107
+ const groups = [];
108
+ let currentDate = "";
109
+ for (const msg of messages) {
110
+ const dateStr = msg.datetime_add.split("T")[0];
111
+ if (dateStr !== currentDate) {
112
+ currentDate = dateStr;
113
+ groups.push({ date: msg.datetime_add, messages: [] });
114
+ }
115
+ groups[groups.length - 1].messages.push(msg);
116
+ }
117
+ return groups;
118
+ }
119
+
120
+ // src/utils/format-date.ts
121
+ import { format, isToday, isYesterday } from "date-fns";
122
+ import { ptBR } from "date-fns/locale";
123
+ function formatDateGroup(dateStr) {
124
+ const date = new Date(dateStr);
125
+ if (isToday(date)) return "Hoje";
126
+ if (isYesterday(date)) return "Ontem";
127
+ return format(date, "dd 'de' MMMM", { locale: ptBR });
128
+ }
129
+ function formatMessageTime(dateStr) {
130
+ return format(new Date(dateStr), "HH:mm");
131
+ }
132
+
133
+ // src/components/chat-view.tsx
134
+ import { useRef as useRef2, useEffect } from "react";
135
+
136
+ // src/components/message-bubble.tsx
137
+ import { useState } from "react";
138
+ import {
139
+ Check as Check2,
140
+ CheckCheck,
141
+ AlertCircle,
142
+ Clock,
143
+ Video,
144
+ File,
145
+ MapPin,
146
+ Headphones,
147
+ RotateCcw,
148
+ MoreHorizontal,
149
+ Trash2,
150
+ Pencil,
151
+ Ban
152
+ } from "lucide-react";
153
+
154
+ // src/components/ui/dropdown-menu.tsx
155
+ import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
156
+ import { Check, ChevronRight } from "lucide-react";
157
+ import { jsx, jsxs } from "react/jsx-runtime";
158
+ function DropdownMenu({
159
+ ...props
160
+ }) {
161
+ return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
162
+ }
163
+ function DropdownMenuTrigger({
164
+ ...props
165
+ }) {
166
+ return /* @__PURE__ */ jsx(
167
+ DropdownMenuPrimitive.Trigger,
168
+ {
169
+ "data-slot": "dropdown-menu-trigger",
170
+ ...props
171
+ }
172
+ );
173
+ }
174
+ function DropdownMenuContent({
175
+ className,
176
+ align = "start",
177
+ sideOffset = 4,
178
+ ...props
179
+ }) {
180
+ return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx(
181
+ DropdownMenuPrimitive.Content,
182
+ {
183
+ "data-slot": "dropdown-menu-content",
184
+ sideOffset,
185
+ align,
186
+ className: cn(
187
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-md p-1 shadow-md ring-1 duration-100 z-50 overflow-x-hidden overflow-y-auto",
188
+ className
189
+ ),
190
+ ...props
191
+ }
192
+ ) });
193
+ }
194
+ function DropdownMenuItem({
195
+ className,
196
+ inset,
197
+ variant = "default",
198
+ ...props
199
+ }) {
200
+ return /* @__PURE__ */ jsx(
201
+ DropdownMenuPrimitive.Item,
202
+ {
203
+ "data-slot": "dropdown-menu-item",
204
+ "data-inset": inset,
205
+ "data-variant": variant,
206
+ className: cn(
207
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive gap-2 rounded-sm px-2 py-1.5 text-sm data-inset:pl-8 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
208
+ className
209
+ ),
210
+ ...props
211
+ }
212
+ );
213
+ }
214
+
215
+ // src/components/ui/alert-dialog.tsx
216
+ import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
217
+
218
+ // src/components/ui/button.tsx
219
+ import { cva } from "class-variance-authority";
220
+ import { Slot } from "radix-ui";
221
+ import { jsx as jsx2 } from "react/jsx-runtime";
222
+ var buttonVariants = cva(
223
+ "focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border border-transparent text-sm font-medium focus-visible:ring-3 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none select-none",
224
+ {
225
+ variants: {
226
+ variant: {
227
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
228
+ outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 shadow-xs",
229
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
230
+ ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50",
231
+ destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:bg-destructive/20 text-destructive",
232
+ link: "text-primary underline-offset-4 hover:underline"
233
+ },
234
+ size: {
235
+ default: "h-9 gap-1.5 px-2.5",
236
+ xs: "h-6 gap-1 px-2 text-xs [&_svg:not([class*='size-'])]:size-3",
237
+ sm: "h-8 gap-1 px-2.5",
238
+ lg: "h-10 gap-1.5 px-2.5",
239
+ icon: "size-9",
240
+ "icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
241
+ "icon-sm": "size-8",
242
+ "icon-lg": "size-10"
243
+ }
244
+ },
245
+ defaultVariants: {
246
+ variant: "default",
247
+ size: "default"
248
+ }
249
+ }
250
+ );
251
+ function Button({
252
+ className,
253
+ variant = "default",
254
+ size = "default",
255
+ asChild = false,
256
+ ...props
257
+ }) {
258
+ const Comp = asChild ? Slot.Root : "button";
259
+ return /* @__PURE__ */ jsx2(
260
+ Comp,
261
+ {
262
+ "data-slot": "button",
263
+ className: cn(buttonVariants({ variant, size, className })),
264
+ ...props
265
+ }
266
+ );
267
+ }
268
+
269
+ // src/components/ui/alert-dialog.tsx
270
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
271
+ function AlertDialog({
272
+ ...props
273
+ }) {
274
+ return /* @__PURE__ */ jsx3(AlertDialogPrimitive.Root, { "data-slot": "alert-dialog", ...props });
275
+ }
276
+ function AlertDialogPortal({
277
+ ...props
278
+ }) {
279
+ return /* @__PURE__ */ jsx3(AlertDialogPrimitive.Portal, { "data-slot": "alert-dialog-portal", ...props });
280
+ }
281
+ function AlertDialogOverlay({
282
+ className,
283
+ ...props
284
+ }) {
285
+ return /* @__PURE__ */ jsx3(
286
+ AlertDialogPrimitive.Overlay,
287
+ {
288
+ "data-slot": "alert-dialog-overlay",
289
+ className: cn(
290
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50",
291
+ className
292
+ ),
293
+ ...props
294
+ }
295
+ );
296
+ }
297
+ function AlertDialogContent({
298
+ className,
299
+ ...props
300
+ }) {
301
+ return /* @__PURE__ */ jsxs2(AlertDialogPortal, { children: [
302
+ /* @__PURE__ */ jsx3(AlertDialogOverlay, {}),
303
+ /* @__PURE__ */ jsx3(
304
+ AlertDialogPrimitive.Content,
305
+ {
306
+ "data-slot": "alert-dialog-content",
307
+ className: cn(
308
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-6 rounded-xl p-6 ring-1 duration-100 sm:max-w-lg fixed top-1/2 left-1/2 z-50 grid w-full max-w-xs -translate-x-1/2 -translate-y-1/2 outline-none",
309
+ className
310
+ ),
311
+ ...props
312
+ }
313
+ )
314
+ ] });
315
+ }
316
+ function AlertDialogHeader({
317
+ className,
318
+ ...props
319
+ }) {
320
+ return /* @__PURE__ */ jsx3(
321
+ "div",
322
+ {
323
+ "data-slot": "alert-dialog-header",
324
+ className: cn("grid gap-1.5 text-center sm:text-left", className),
325
+ ...props
326
+ }
327
+ );
328
+ }
329
+ function AlertDialogFooter({
330
+ className,
331
+ ...props
332
+ }) {
333
+ return /* @__PURE__ */ jsx3(
334
+ "div",
335
+ {
336
+ "data-slot": "alert-dialog-footer",
337
+ className: cn(
338
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
339
+ className
340
+ ),
341
+ ...props
342
+ }
343
+ );
344
+ }
345
+ function AlertDialogTitle({
346
+ className,
347
+ ...props
348
+ }) {
349
+ return /* @__PURE__ */ jsx3(
350
+ AlertDialogPrimitive.Title,
351
+ {
352
+ "data-slot": "alert-dialog-title",
353
+ className: cn("text-lg font-medium", className),
354
+ ...props
355
+ }
356
+ );
357
+ }
358
+ function AlertDialogDescription({
359
+ className,
360
+ ...props
361
+ }) {
362
+ return /* @__PURE__ */ jsx3(
363
+ AlertDialogPrimitive.Description,
364
+ {
365
+ "data-slot": "alert-dialog-description",
366
+ className: cn("text-muted-foreground text-sm", className),
367
+ ...props
368
+ }
369
+ );
370
+ }
371
+ function AlertDialogAction({
372
+ className,
373
+ variant = "default",
374
+ size = "default",
375
+ ...props
376
+ }) {
377
+ return /* @__PURE__ */ jsx3(Button, { variant, size, asChild: true, children: /* @__PURE__ */ jsx3(
378
+ AlertDialogPrimitive.Action,
379
+ {
380
+ "data-slot": "alert-dialog-action",
381
+ className: cn(className),
382
+ ...props
383
+ }
384
+ ) });
385
+ }
386
+ function AlertDialogCancel({
387
+ className,
388
+ variant = "outline",
389
+ size = "default",
390
+ ...props
391
+ }) {
392
+ return /* @__PURE__ */ jsx3(Button, { variant, size, asChild: true, children: /* @__PURE__ */ jsx3(
393
+ AlertDialogPrimitive.Cancel,
394
+ {
395
+ "data-slot": "alert-dialog-cancel",
396
+ className: cn(className),
397
+ ...props
398
+ }
399
+ ) });
400
+ }
401
+
402
+ // src/components/ui/textarea.tsx
403
+ import { jsx as jsx4 } from "react/jsx-runtime";
404
+ function Textarea({ className, ...props }) {
405
+ return /* @__PURE__ */ jsx4(
406
+ "textarea",
407
+ {
408
+ "data-slot": "textarea",
409
+ className: cn(
410
+ "border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] focus-visible:ring-3 md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
411
+ className
412
+ ),
413
+ ...props
414
+ }
415
+ );
416
+ }
417
+
418
+ // src/components/message-bubble.tsx
419
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
420
+ var statusIcons = {
421
+ pending: /* @__PURE__ */ jsx5(Clock, { className: "h-3 w-3 animate-pulse" }),
422
+ sent: /* @__PURE__ */ jsx5(Check2, { className: "h-3 w-3" }),
423
+ delivered: /* @__PURE__ */ jsx5(CheckCheck, { className: "h-3 w-3" }),
424
+ read: /* @__PURE__ */ jsx5(CheckCheck, { className: "h-3 w-3 text-blue-500" }),
425
+ failed: /* @__PURE__ */ jsx5(AlertCircle, { className: "h-3 w-3 text-destructive" })
426
+ };
427
+ function parseMetadata(metadata) {
428
+ if (!metadata) return {};
429
+ try {
430
+ return JSON.parse(metadata);
431
+ } catch {
432
+ return {};
433
+ }
434
+ }
435
+ function MediaContent({ message }) {
436
+ switch (message.content_type) {
437
+ case "image":
438
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
439
+ message.content_url && /* @__PURE__ */ jsx5(
440
+ "img",
441
+ {
442
+ src: message.content_url,
443
+ alt: "Imagem",
444
+ className: "max-w-[240px] rounded-md"
445
+ }
446
+ ),
447
+ message.content && /* @__PURE__ */ jsx5("p", { className: "text-sm", children: message.content })
448
+ ] });
449
+ case "audio":
450
+ return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
451
+ /* @__PURE__ */ jsx5(Headphones, { className: "h-4 w-4" }),
452
+ /* @__PURE__ */ jsx5("span", { className: "text-sm", children: "Mensagem de \xE1udio" })
453
+ ] });
454
+ case "video":
455
+ return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
456
+ /* @__PURE__ */ jsx5(Video, { className: "h-4 w-4" }),
457
+ /* @__PURE__ */ jsx5("span", { className: "text-sm", children: "V\xEDdeo" }),
458
+ message.content && /* @__PURE__ */ jsx5("p", { className: "text-sm", children: message.content })
459
+ ] });
460
+ case "document": {
461
+ const meta = parseMetadata(message.metadata);
462
+ return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
463
+ /* @__PURE__ */ jsx5(File, { className: "h-4 w-4" }),
464
+ /* @__PURE__ */ jsx5("span", { className: "text-sm", children: meta.filename || "Documento" })
465
+ ] });
466
+ }
467
+ case "location":
468
+ return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
469
+ /* @__PURE__ */ jsx5(MapPin, { className: "h-4 w-4" }),
470
+ /* @__PURE__ */ jsx5("span", { className: "text-sm", children: "Localiza\xE7\xE3o" })
471
+ ] });
472
+ default:
473
+ return null;
474
+ }
475
+ }
476
+ function MessageBubble({
477
+ message,
478
+ onRetry,
479
+ onRevoke,
480
+ onEdit,
481
+ renderActions
482
+ }) {
483
+ const [showRevokeDialog, setShowRevokeDialog] = useState(false);
484
+ const [editing, setEditing] = useState(false);
485
+ const [editContent, setEditContent] = useState(message.content || "");
486
+ const isOutbound = message.direction === "outbound";
487
+ const isPending = message.status === "pending";
488
+ const isFailed = message.status === "failed";
489
+ const time = formatMessageTime(message.datetime_add);
490
+ const meta = parseMetadata(message.metadata);
491
+ const isRevoked = meta.revoked === true;
492
+ const isEdited = meta.edited === true;
493
+ const agentLabel = isOutbound && message.source !== "contact" ? meta.agent_name || (message.source === "bot" ? "Bot" : "Agente") : null;
494
+ const canAct = isOutbound && message.id > 0 && !isPending && !isFailed && !isRevoked && !!message.external_id;
495
+ const canEdit = canAct && message.content_type === "text";
496
+ function handleSaveEdit() {
497
+ const trimmed = editContent.trim();
498
+ if (!trimmed || trimmed === message.content) {
499
+ setEditing(false);
500
+ return;
501
+ }
502
+ onEdit?.(message, trimmed);
503
+ setEditing(false);
504
+ }
505
+ if (isRevoked) {
506
+ return /* @__PURE__ */ jsx5(
507
+ "div",
508
+ {
509
+ className: cn("flex", isOutbound ? "justify-end" : "justify-start"),
510
+ children: /* @__PURE__ */ jsx5("div", { className: "max-w-[75%]", children: /* @__PURE__ */ jsxs3(
511
+ "div",
512
+ {
513
+ className: cn(
514
+ "rounded-lg px-3 py-2",
515
+ isOutbound ? "bg-primary/40" : "bg-muted/60"
516
+ ),
517
+ children: [
518
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1.5", children: [
519
+ /* @__PURE__ */ jsx5(
520
+ Ban,
521
+ {
522
+ className: cn(
523
+ "h-3.5 w-3.5",
524
+ isOutbound ? "text-primary-foreground/50" : "text-muted-foreground"
525
+ )
526
+ }
527
+ ),
528
+ /* @__PURE__ */ jsx5(
529
+ "p",
530
+ {
531
+ className: cn(
532
+ "text-sm italic",
533
+ isOutbound ? "text-primary-foreground/50" : "text-muted-foreground"
534
+ ),
535
+ children: "Mensagem apagada"
536
+ }
537
+ )
538
+ ] }),
539
+ /* @__PURE__ */ jsx5(
540
+ "div",
541
+ {
542
+ className: cn(
543
+ "mt-1 flex items-center justify-end gap-1",
544
+ isOutbound ? "text-primary-foreground/40" : "text-muted-foreground/60"
545
+ ),
546
+ children: /* @__PURE__ */ jsx5("span", { className: "text-[10px]", children: time })
547
+ }
548
+ )
549
+ ]
550
+ }
551
+ ) })
552
+ }
553
+ );
554
+ }
555
+ return /* @__PURE__ */ jsxs3(
556
+ "div",
557
+ {
558
+ className: cn(
559
+ "flex group",
560
+ isOutbound ? "justify-end" : "justify-start"
561
+ ),
562
+ children: [
563
+ /* @__PURE__ */ jsxs3("div", { className: "max-w-[75%] relative", children: [
564
+ canAct && /* @__PURE__ */ jsx5("div", { className: "absolute -top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity z-10", children: /* @__PURE__ */ jsxs3(DropdownMenu, { children: [
565
+ /* @__PURE__ */ jsx5(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx5("button", { className: "h-6 w-6 rounded-full bg-background/80 shadow-sm flex items-center justify-center hover:bg-background", children: /* @__PURE__ */ jsx5(MoreHorizontal, { className: "h-3.5 w-3.5 text-muted-foreground" }) }) }),
566
+ /* @__PURE__ */ jsxs3(DropdownMenuContent, { align: "end", className: "w-40", children: [
567
+ canEdit && /* @__PURE__ */ jsxs3(
568
+ DropdownMenuItem,
569
+ {
570
+ onSelect: (e) => {
571
+ e.preventDefault();
572
+ setEditing(true);
573
+ setEditContent(message.content || "");
574
+ },
575
+ children: [
576
+ /* @__PURE__ */ jsx5(Pencil, { className: "h-4 w-4 mr-2" }),
577
+ "Editar"
578
+ ]
579
+ }
580
+ ),
581
+ /* @__PURE__ */ jsxs3(
582
+ DropdownMenuItem,
583
+ {
584
+ onSelect: (e) => {
585
+ e.preventDefault();
586
+ setShowRevokeDialog(true);
587
+ },
588
+ className: "text-destructive focus:text-destructive",
589
+ children: [
590
+ /* @__PURE__ */ jsx5(Trash2, { className: "h-4 w-4 mr-2" }),
591
+ "Apagar para todos"
592
+ ]
593
+ }
594
+ )
595
+ ] })
596
+ ] }) }),
597
+ renderActions?.(message),
598
+ /* @__PURE__ */ jsxs3(
599
+ "div",
600
+ {
601
+ className: cn(
602
+ "rounded-lg px-3 py-2",
603
+ isOutbound ? "bg-primary text-primary-foreground" : "bg-muted",
604
+ isPending && "opacity-70",
605
+ isFailed && "bg-destructive/10 border border-destructive/30"
606
+ ),
607
+ children: [
608
+ agentLabel && /* @__PURE__ */ jsx5(
609
+ "p",
610
+ {
611
+ className: cn(
612
+ "mb-0.5 text-[10px] font-medium",
613
+ isFailed ? "text-destructive/70" : isOutbound ? "text-primary-foreground/70" : "text-muted-foreground"
614
+ ),
615
+ children: agentLabel
616
+ }
617
+ ),
618
+ editing ? /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
619
+ /* @__PURE__ */ jsx5(
620
+ Textarea,
621
+ {
622
+ value: editContent,
623
+ onChange: (e) => setEditContent(e.target.value),
624
+ rows: 2,
625
+ className: "min-h-[36px] max-h-[120px] resize-none bg-background text-foreground text-sm",
626
+ autoFocus: true,
627
+ onKeyDown: (e) => {
628
+ if (e.key === "Enter" && !e.shiftKey) {
629
+ e.preventDefault();
630
+ handleSaveEdit();
631
+ }
632
+ if (e.key === "Escape") {
633
+ setEditing(false);
634
+ }
635
+ }
636
+ }
637
+ ),
638
+ /* @__PURE__ */ jsxs3("div", { className: "flex justify-end gap-1.5", children: [
639
+ /* @__PURE__ */ jsx5(
640
+ Button,
641
+ {
642
+ size: "sm",
643
+ variant: "ghost",
644
+ className: "h-6 text-xs px-2",
645
+ onClick: () => setEditing(false),
646
+ children: "Cancelar"
647
+ }
648
+ ),
649
+ /* @__PURE__ */ jsx5(
650
+ Button,
651
+ {
652
+ size: "sm",
653
+ className: "h-6 text-xs px-2",
654
+ onClick: handleSaveEdit,
655
+ disabled: !editContent.trim() || editContent.trim() === message.content,
656
+ children: "Salvar"
657
+ }
658
+ )
659
+ ] })
660
+ ] }) : message.content_type === "text" ? /* @__PURE__ */ jsx5(
661
+ "p",
662
+ {
663
+ className: cn(
664
+ "text-sm whitespace-pre-wrap break-words",
665
+ isFailed && "text-foreground"
666
+ ),
667
+ children: message.content
668
+ }
669
+ ) : /* @__PURE__ */ jsx5(MediaContent, { message }),
670
+ !editing && /* @__PURE__ */ jsxs3(
671
+ "div",
672
+ {
673
+ className: cn(
674
+ "mt-1 flex items-center justify-end gap-1",
675
+ isFailed ? "text-destructive" : isOutbound ? "text-primary-foreground/60" : "text-muted-foreground"
676
+ ),
677
+ children: [
678
+ isEdited && /* @__PURE__ */ jsx5("span", { className: "text-[10px] italic", children: "editada" }),
679
+ /* @__PURE__ */ jsx5("span", { className: "text-[10px]", children: time }),
680
+ isOutbound && statusIcons[message.status]
681
+ ]
682
+ }
683
+ )
684
+ ]
685
+ }
686
+ ),
687
+ isFailed && /* @__PURE__ */ jsxs3("div", { className: "mt-1 flex items-center gap-1.5 justify-end px-1", children: [
688
+ /* @__PURE__ */ jsx5(AlertCircle, { className: "h-3 w-3 shrink-0 text-destructive" }),
689
+ /* @__PURE__ */ jsx5("span", { className: "text-[11px] text-destructive", children: message._error || "Erro ao enviar" }),
690
+ onRetry && /* @__PURE__ */ jsxs3(
691
+ "button",
692
+ {
693
+ onClick: () => onRetry(message),
694
+ className: "inline-flex items-center gap-0.5 text-[11px] text-destructive font-medium hover:text-destructive/80 hover:underline cursor-pointer ml-1",
695
+ children: [
696
+ /* @__PURE__ */ jsx5(RotateCcw, { className: "h-3 w-3" }),
697
+ "Reenviar"
698
+ ]
699
+ }
700
+ )
701
+ ] })
702
+ ] }),
703
+ /* @__PURE__ */ jsx5(AlertDialog, { open: showRevokeDialog, onOpenChange: setShowRevokeDialog, children: /* @__PURE__ */ jsxs3(AlertDialogContent, { children: [
704
+ /* @__PURE__ */ jsxs3(AlertDialogHeader, { children: [
705
+ /* @__PURE__ */ jsx5(AlertDialogTitle, { children: "Apagar mensagem?" }),
706
+ /* @__PURE__ */ jsx5(AlertDialogDescription, { children: 'A mensagem ser\xE1 apagada para todos no WhatsApp. Ela continuar\xE1 vis\xEDvel aqui como "mensagem apagada".' })
707
+ ] }),
708
+ /* @__PURE__ */ jsxs3(AlertDialogFooter, { children: [
709
+ /* @__PURE__ */ jsx5(AlertDialogCancel, { children: "Cancelar" }),
710
+ /* @__PURE__ */ jsx5(
711
+ AlertDialogAction,
712
+ {
713
+ className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
714
+ onClick: () => onRevoke?.(message),
715
+ children: "Apagar para todos"
716
+ }
717
+ )
718
+ ] })
719
+ ] }) })
720
+ ]
721
+ }
722
+ );
723
+ }
724
+
725
+ // src/components/chat-input.tsx
726
+ import { useState as useState2, useRef } from "react";
727
+ import { Send } from "lucide-react";
728
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
729
+ function ChatInput({ onSend, disabled }) {
730
+ const [content, setContent] = useState2("");
731
+ const textareaRef = useRef(null);
732
+ function handleSend() {
733
+ const text = content.trim();
734
+ if (!text || disabled) return;
735
+ setContent("");
736
+ onSend(text);
737
+ textareaRef.current?.focus();
738
+ }
739
+ function handleKeyDown(e) {
740
+ if (e.key === "Enter" && !e.shiftKey) {
741
+ e.preventDefault();
742
+ handleSend();
743
+ }
744
+ }
745
+ return /* @__PURE__ */ jsx6("div", { className: "border-t p-3", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-end gap-2", children: [
746
+ /* @__PURE__ */ jsx6(
747
+ Textarea,
748
+ {
749
+ ref: textareaRef,
750
+ placeholder: "Digite uma mensagem...",
751
+ value: content,
752
+ onChange: (e) => setContent(e.target.value),
753
+ onKeyDown: handleKeyDown,
754
+ rows: 1,
755
+ className: "min-h-[40px] max-h-[120px] resize-none"
756
+ }
757
+ ),
758
+ /* @__PURE__ */ jsx6(
759
+ Button,
760
+ {
761
+ size: "icon",
762
+ onClick: handleSend,
763
+ disabled: !content.trim() || disabled,
764
+ className: "shrink-0",
765
+ children: /* @__PURE__ */ jsx6(Send, { className: "h-4 w-4" })
766
+ }
767
+ )
768
+ ] }) });
769
+ }
770
+
771
+ // src/components/ui/skeleton.tsx
772
+ import { jsx as jsx7 } from "react/jsx-runtime";
773
+ function Skeleton({
774
+ className,
775
+ ...props
776
+ }) {
777
+ return /* @__PURE__ */ jsx7(
778
+ "div",
779
+ {
780
+ "data-slot": "skeleton",
781
+ className: cn("bg-muted rounded-md animate-pulse", className),
782
+ ...props
783
+ }
784
+ );
785
+ }
786
+
787
+ // src/components/ui/select.tsx
788
+ import { Select as SelectPrimitive } from "radix-ui";
789
+ import { Check as Check3, ChevronDown, ChevronUp, ChevronsUpDown } from "lucide-react";
790
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
791
+ function Select({
792
+ ...props
793
+ }) {
794
+ return /* @__PURE__ */ jsx8(SelectPrimitive.Root, { "data-slot": "select", ...props });
795
+ }
796
+ function SelectValue({
797
+ ...props
798
+ }) {
799
+ return /* @__PURE__ */ jsx8(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
800
+ }
801
+ function SelectTrigger({
802
+ className,
803
+ size = "default",
804
+ children,
805
+ ...props
806
+ }) {
807
+ return /* @__PURE__ */ jsxs5(
808
+ SelectPrimitive.Trigger,
809
+ {
810
+ "data-slot": "select-trigger",
811
+ "data-size": size,
812
+ className: cn(
813
+ "border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 gap-1.5 rounded-md border bg-transparent py-2 pr-2 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] focus-visible:ring-3 data-[size=default]:h-9 data-[size=sm]:h-8 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
814
+ className
815
+ ),
816
+ ...props,
817
+ children: [
818
+ children,
819
+ /* @__PURE__ */ jsx8(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx8(ChevronsUpDown, { className: "text-muted-foreground size-4 pointer-events-none" }) })
820
+ ]
821
+ }
822
+ );
823
+ }
824
+ function SelectContent({
825
+ className,
826
+ children,
827
+ position = "item-aligned",
828
+ align = "center",
829
+ ...props
830
+ }) {
831
+ return /* @__PURE__ */ jsx8(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs5(
832
+ SelectPrimitive.Content,
833
+ {
834
+ "data-slot": "select-content",
835
+ className: cn(
836
+ "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-md shadow-md ring-1 duration-100 relative z-50 max-h-(--radix-select-content-available-height) overflow-x-hidden overflow-y-auto",
837
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
838
+ className
839
+ ),
840
+ position,
841
+ align,
842
+ ...props,
843
+ children: [
844
+ /* @__PURE__ */ jsx8(SelectScrollUpButton, {}),
845
+ /* @__PURE__ */ jsx8(SelectPrimitive.Viewport, { children }),
846
+ /* @__PURE__ */ jsx8(SelectScrollDownButton, {})
847
+ ]
848
+ }
849
+ ) });
850
+ }
851
+ function SelectItem({
852
+ className,
853
+ children,
854
+ ...props
855
+ }) {
856
+ return /* @__PURE__ */ jsxs5(
857
+ SelectPrimitive.Item,
858
+ {
859
+ "data-slot": "select-item",
860
+ className: cn(
861
+ "focus:bg-accent focus:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
862
+ className
863
+ ),
864
+ ...props,
865
+ children: [
866
+ /* @__PURE__ */ jsx8("span", { className: "pointer-events-none absolute right-2 flex size-4 items-center justify-center", children: /* @__PURE__ */ jsx8(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx8(Check3, { className: "pointer-events-none" }) }) }),
867
+ /* @__PURE__ */ jsx8(SelectPrimitive.ItemText, { children })
868
+ ]
869
+ }
870
+ );
871
+ }
872
+ function SelectScrollUpButton({
873
+ className,
874
+ ...props
875
+ }) {
876
+ return /* @__PURE__ */ jsx8(
877
+ SelectPrimitive.ScrollUpButton,
878
+ {
879
+ "data-slot": "select-scroll-up-button",
880
+ className: cn(
881
+ "bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4",
882
+ className
883
+ ),
884
+ ...props,
885
+ children: /* @__PURE__ */ jsx8(ChevronUp, {})
886
+ }
887
+ );
888
+ }
889
+ function SelectScrollDownButton({
890
+ className,
891
+ ...props
892
+ }) {
893
+ return /* @__PURE__ */ jsx8(
894
+ SelectPrimitive.ScrollDownButton,
895
+ {
896
+ "data-slot": "select-scroll-down-button",
897
+ className: cn(
898
+ "bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4",
899
+ className
900
+ ),
901
+ ...props,
902
+ children: /* @__PURE__ */ jsx8(ChevronDown, {})
903
+ }
904
+ );
905
+ }
906
+
907
+ // src/components/chat-view.tsx
908
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
909
+ function ChatView({
910
+ messages,
911
+ isLoading,
912
+ inboxStatus,
913
+ onStatusChange,
914
+ onSend,
915
+ onRetry,
916
+ onRevoke,
917
+ onEdit,
918
+ sendDisabled,
919
+ renderHeader,
920
+ renderStatusDropdown
921
+ }) {
922
+ const scrollRef = useRef2(null);
923
+ const prevMessageCount = useRef2(0);
924
+ useEffect(() => {
925
+ if (messages && messages.length > prevMessageCount.current) {
926
+ const el = scrollRef.current;
927
+ if (el) {
928
+ setTimeout(() => {
929
+ el.scrollTop = el.scrollHeight;
930
+ }, 50);
931
+ }
932
+ }
933
+ prevMessageCount.current = messages?.length || 0;
934
+ }, [messages]);
935
+ const groups = messages ? groupMessagesByDate(messages) : [];
936
+ return /* @__PURE__ */ jsxs6("div", { className: "flex h-full flex-col", children: [
937
+ renderHeader !== void 0 ? renderHeader : /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [
938
+ /* @__PURE__ */ jsx9("div", { className: "flex-1" }),
939
+ renderStatusDropdown !== void 0 ? renderStatusDropdown : onStatusChange ? /* @__PURE__ */ jsxs6(
940
+ Select,
941
+ {
942
+ value: inboxStatus,
943
+ onValueChange: onStatusChange,
944
+ children: [
945
+ /* @__PURE__ */ jsx9(SelectTrigger, { className: "w-[130px] h-8 text-xs", children: /* @__PURE__ */ jsx9(SelectValue, { placeholder: "Status" }) }),
946
+ /* @__PURE__ */ jsxs6(SelectContent, { children: [
947
+ /* @__PURE__ */ jsx9(SelectItem, { value: "open", children: "Aberta" }),
948
+ /* @__PURE__ */ jsx9(SelectItem, { value: "pending", children: "Pendente" }),
949
+ /* @__PURE__ */ jsx9(SelectItem, { value: "resolved", children: "Resolvida" })
950
+ ] })
951
+ ]
952
+ }
953
+ ) : null
954
+ ] }),
955
+ /* @__PURE__ */ jsx9("div", { ref: scrollRef, className: "flex-1 overflow-y-auto p-4", children: isLoading ? /* @__PURE__ */ jsx9("div", { className: "space-y-4", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsx9(
956
+ "div",
957
+ {
958
+ className: `flex ${i % 2 === 0 ? "justify-start" : "justify-end"}`,
959
+ children: /* @__PURE__ */ jsx9(Skeleton, { className: "h-12 w-64 rounded-lg" })
960
+ },
961
+ i
962
+ )) }) : groups.length === 0 ? /* @__PURE__ */ jsx9("div", { className: "flex h-full items-center justify-center text-sm text-muted-foreground", children: "Nenhuma mensagem ainda" }) : /* @__PURE__ */ jsx9("div", { className: "space-y-4", children: groups.map((group, gi) => /* @__PURE__ */ jsxs6("div", { children: [
963
+ /* @__PURE__ */ jsx9("div", { className: "mb-3 flex justify-center", children: /* @__PURE__ */ jsx9("span", { className: "rounded-full bg-muted px-3 py-1 text-[11px] text-muted-foreground", children: formatDateGroup(group.date) }) }),
964
+ /* @__PURE__ */ jsx9("div", { className: "space-y-1", children: group.messages.map((msg) => /* @__PURE__ */ jsx9(
965
+ MessageBubble,
966
+ {
967
+ message: msg,
968
+ onRetry,
969
+ onRevoke,
970
+ onEdit
971
+ },
972
+ msg.id
973
+ )) })
974
+ ] }, gi)) }) }),
975
+ /* @__PURE__ */ jsx9(ChatInput, { onSend, disabled: sendDisabled })
976
+ ] });
977
+ }
104
978
  export {
979
+ ChatInput,
980
+ ChatView,
981
+ MessageBubble,
105
982
  cn,
106
- createGchatClient
983
+ createGchatClient,
984
+ formatDateGroup,
985
+ formatMessageTime,
986
+ groupMessagesByDate
107
987
  };
108
988
  //# sourceMappingURL=index.js.map