@better-zap/react 0.1.0 → 0.2.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.cjs DELETED
@@ -1,687 +0,0 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region \0rolldown/runtime.js
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
- get: ((k) => from[k]).bind(null, key),
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
- value: mod,
21
- enumerable: true
22
- }) : target, mod));
23
- //#endregion
24
- let react = require("react");
25
- react = __toESM(react);
26
- let clsx = require("clsx");
27
- let tailwind_merge = require("tailwind-merge");
28
- let react_jsx_runtime = require("react/jsx-runtime");
29
- let _hugeicons_react = require("@hugeicons/react");
30
- let _hugeicons_core_free_icons = require("@hugeicons/core-free-icons");
31
- let _legendapp_list_react = require("@legendapp/list/react");
32
- let class_variance_authority = require("class-variance-authority");
33
- let better_zap = require("better-zap");
34
- //#region src/utils.ts
35
- function cn(...inputs) {
36
- return (0, tailwind_merge.twMerge)((0, clsx.clsx)(inputs));
37
- }
38
- function getDisplayDate(dateStr) {
39
- const dateObj = new Date(dateStr);
40
- const now = /* @__PURE__ */ new Date();
41
- const isToday = dateObj.toDateString() === now.toDateString();
42
- const yesterday = new Date(now);
43
- yesterday.setDate(yesterday.getDate() - 1);
44
- const isYesterday = dateObj.toDateString() === yesterday.toDateString();
45
- if (isToday) return "HOJE";
46
- else if (isYesterday) return "ONTEM";
47
- else return dateObj.toLocaleDateString("pt-BR", {
48
- day: "2-digit",
49
- month: "2-digit",
50
- year: "numeric"
51
- });
52
- }
53
- //#endregion
54
- //#region src/whatsapp-dashboard.tsx
55
- const MOBILE_BREAKPOINT = 1024;
56
- function useIsMobile() {
57
- const [isMobile, setIsMobile] = (0, react.useState)(false);
58
- (0, react.useEffect)(() => {
59
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
60
- const onChange = (e) => {
61
- setIsMobile(e.matches);
62
- };
63
- onChange(mql);
64
- mql.addEventListener("change", onChange);
65
- return () => mql.removeEventListener("change", onChange);
66
- }, []);
67
- return isMobile;
68
- }
69
- const WhatsappDashboardContext = (0, react.createContext)(null);
70
- function useWhatsappDashboard() {
71
- const ctx = (0, react.useContext)(WhatsappDashboardContext);
72
- if (!ctx) throw new Error("useWhatsappDashboard must be used within <WhatsappDashboard>");
73
- return ctx;
74
- }
75
- function WhatsappDashboard({ children, className, defaultMobileView = "list", ...props }) {
76
- const isMobile = useIsMobile();
77
- const [mobileView, setMobileView] = (0, react.useState)(defaultMobileView);
78
- const handleSetMobileView = (0, react.useCallback)((view) => {
79
- setMobileView(view);
80
- }, []);
81
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WhatsappDashboardContext.Provider, {
82
- value: {
83
- isMobile,
84
- mobileView,
85
- setMobileView: handleSetMobileView
86
- },
87
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
88
- className: cn("bg-background flex h-full w-full overflow-hidden", className),
89
- ...props,
90
- children
91
- })
92
- });
93
- }
94
- //#endregion
95
- //#region src/message-bubble.tsx
96
- const bubbleVariants = (0, class_variance_authority.cva)("relative shadow-[0_1px_0.5px_rgba(11,20,26,0.13)] rounded-lg px-3 py-2 max-w-[65%]", { variants: { variant: {
97
- outgoing: "bg-green-100 text-green-900 rounded-tr-none",
98
- incoming: "bg-gray-100 text-gray-900 rounded-tl-none",
99
- failed: "bg-red-100 text-red-900 rounded-tr-none border border-red-200"
100
- } } });
101
- const statusVariants = (0, class_variance_authority.cva)("text-[13px] leading-none", {
102
- variants: { variant: {
103
- default: "opacity-60",
104
- read: "text-blue-500",
105
- failed: "text-red-500"
106
- } },
107
- defaultVariants: { variant: "default" }
108
- });
109
- function MessageBubble({ content, sender, timestamp, status, templateName, label, className, ...props }) {
110
- const isIncoming = sender === "user";
111
- const isFailed = status === "failed";
112
- const bubbleVariant = isIncoming ? "incoming" : isFailed ? "failed" : "outgoing";
113
- const statusVariant = status === "read" ? "read" : isFailed ? "failed" : "default";
114
- const displayContent = content || (templateName ? `[Template: ${templateName}]` : "[Conteúdo não disponível]");
115
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
116
- className: cn("flex w-full mb-1", isIncoming ? "justify-start" : "justify-end", className),
117
- ...props,
118
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
119
- className: bubbleVariants({ variant: bubbleVariant }),
120
- children: [
121
- label && !isIncoming && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
122
- className: "mb-1 block text-xs font-medium opacity-70",
123
- children: label
124
- }),
125
- templateName && !label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
126
- className: "mb-1 border-b border-black/5 pb-1",
127
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
128
- className: "text-[11px] font-medium opacity-70",
129
- children: ["📋 ", templateName]
130
- })
131
- }),
132
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
133
- className: "text-[14.5px] leading-normal whitespace-pre-wrap break-words select-text",
134
- children: [
135
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormattedMessage, { text: displayContent }),
136
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
137
- className: "float-right -mb-1 ml-2 mt-1.5 flex items-center justify-end gap-1 shrink-0 h-[15px] select-none",
138
- children: [timestamp && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
139
- className: "text-[11px] opacity-60 leading-none whitespace-nowrap",
140
- children: timestamp
141
- }), !isIncoming && status && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
142
- className: statusVariants({ variant: statusVariant }),
143
- children: status === "read" || status === "delivered" ? "✓✓" : isFailed ? "✕" : "✓"
144
- })]
145
- }),
146
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "clear-both" })
147
- ]
148
- })
149
- ]
150
- })
151
- });
152
- }
153
- function FormattedMessage({ text }) {
154
- if (!text) return null;
155
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: text.split(/(\n)/g).map((line, lineIndex) => {
156
- if (line === "\n") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}, lineIndex);
157
- if (!line) return null;
158
- const parts = line.split(/(```[\s\S]*?```|`[^`]+`|\*[^\s*](?:[^*]*[^\s*])?\*|_[^\s_](?:[^_]*[^\s_])?_|~[^\s~](?:[^~]*[^\s~])?~|https?:\/\/[^\s]+)/g);
159
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: parts.map((part, index) => {
160
- if (!part) return null;
161
- if (part.startsWith("```") && part.endsWith("```")) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
162
- className: "my-1 block whitespace-pre-wrap rounded bg-black/5 px-1 py-0.5 font-mono text-[13px]",
163
- children: part.slice(3, -3)
164
- }, index);
165
- if (part.startsWith("`") && part.endsWith("`")) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
166
- className: "rounded bg-black/5 px-1 font-mono text-[13px] text-[#df0165]",
167
- children: part.slice(1, -1)
168
- }, index);
169
- if (part.startsWith("*") && part.endsWith("*")) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
170
- className: "font-bold",
171
- children: part.slice(1, -1)
172
- }, index);
173
- if (part.startsWith("_") && part.endsWith("_")) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("em", {
174
- className: "italic",
175
- children: part.slice(1, -1)
176
- }, index);
177
- if (part.startsWith("~") && part.endsWith("~")) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("del", {
178
- className: "text-gray-500 line-through",
179
- children: part.slice(1, -1)
180
- }, index);
181
- if (part.match(/^https?:\/\/[^\s]+$/)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
182
- href: part,
183
- target: "_blank",
184
- rel: "noopener noreferrer",
185
- className: "text-[#027eb5] hover:underline",
186
- children: part
187
- }, index);
188
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: part }, index);
189
- }) }, lineIndex);
190
- }) });
191
- }
192
- //#endregion
193
- //#region src/message-view.tsx
194
- function MessageView({ children, className, ...props }) {
195
- const { isMobile, mobileView } = useWhatsappDashboard();
196
- const hasContent = react.default.Children.count(children) > 0;
197
- const isVisible = !isMobile || mobileView === "chat";
198
- if (!hasContent) {
199
- if (isMobile) return null;
200
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageViewEmpty, {
201
- className,
202
- ...props
203
- });
204
- }
205
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
206
- className: cn("relative flex flex-1 flex-col bg-[#efeae2]", className),
207
- style: isVisible ? void 0 : { display: "none" },
208
- ...props,
209
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
210
- position: "absolute",
211
- backgroundImage: `url(${new URL("./wpp-bg.webp", require("url").pathToFileURL(__filename).href).href})`,
212
- backgroundRepeat: "repeat",
213
- opacity: .15,
214
- inset: 0
215
- } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
216
- className: "relative flex flex-1 flex-col min-h-0",
217
- children
218
- })]
219
- });
220
- }
221
- function MessageViewHeader({ conversation, onBack, onInfoClick, className }) {
222
- const { isMobile, setMobileView } = useWhatsappDashboard();
223
- const handleBack = () => {
224
- setMobileView("list");
225
- onBack?.();
226
- };
227
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
228
- className: cn("flex h-16 shrink-0 items-center justify-between border-b bg-[#f0f2f5] px-4 z-20", className),
229
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
230
- className: "flex items-center gap-3",
231
- children: [isMobile && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
232
- type: "button",
233
- className: "inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-black/5",
234
- onClick: handleBack,
235
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
236
- icon: _hugeicons_core_free_icons.ArrowLeft02Icon,
237
- size: 20
238
- })
239
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
240
- className: "flex flex-col",
241
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
242
- className: "text-[15px] font-medium text-[#111b21] leading-tight",
243
- children: conversation.contactName || conversation.phone
244
- }), conversation.contactName && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
245
- className: "text-sm text-[#667781] leading-tight",
246
- children: conversation.phone
247
- })]
248
- })]
249
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
250
- type: "button",
251
- className: "inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-black/5",
252
- onClick: onInfoClick,
253
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
254
- icon: _hugeicons_core_free_icons.InformationCircleIcon,
255
- size: 20
256
- })
257
- })]
258
- });
259
- }
260
- const MessageViewScrollContext = react.default.createContext(null);
261
- function MessageViewContent({ children, autoScroll = true, onScrollTop, className, ...props }) {
262
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageViewScrollContext.Provider, {
263
- value: {
264
- autoScroll,
265
- onScrollTop
266
- },
267
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
268
- className: cn("flex min-h-0 flex-1 flex-col p-4 pb-0", className),
269
- ...props,
270
- children
271
- })
272
- });
273
- }
274
- function MessageList({ messages, renderMessageLabel, className }) {
275
- const scrollContext = (0, react.useContext)(MessageViewScrollContext);
276
- const autoScroll = scrollContext?.autoScroll ?? true;
277
- const onScrollTop = scrollContext?.onScrollTop;
278
- const { items, stickyHeaderIndices } = (0, react.useMemo)(() => {
279
- const itemsNew = [];
280
- const stickyHeaderIndicesNew = [];
281
- let currentDate = null;
282
- messages.forEach((msg) => {
283
- const displayDate = getDisplayDate(msg.sentAt);
284
- if (currentDate !== displayDate) {
285
- currentDate = displayDate;
286
- stickyHeaderIndicesNew.push(itemsNew.length);
287
- itemsNew.push({
288
- type: "date",
289
- id: `date:${displayDate}`,
290
- date: displayDate
291
- });
292
- }
293
- itemsNew.push({
294
- type: "message",
295
- id: msg.id,
296
- message: msg
297
- });
298
- });
299
- return {
300
- items: itemsNew,
301
- stickyHeaderIndices: stickyHeaderIndicesNew
302
- };
303
- }, [messages]);
304
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_legendapp_list_react.LegendList, {
305
- alignItemsAtEnd: true,
306
- className: cn("chat-scrollbar", className),
307
- contentContainerStyle: { paddingBottom: 16 },
308
- data: items,
309
- estimatedItemSize: 72,
310
- getItemType: (item) => item.type,
311
- initialScrollAtEnd: autoScroll,
312
- keyExtractor: (item) => item.id,
313
- maintainScrollAtEnd: autoScroll,
314
- maintainVisibleContentPosition: true,
315
- onStartReached: onScrollTop ? () => onScrollTop() : void 0,
316
- onStartReachedThreshold: .1,
317
- recycleItems: true,
318
- renderItem: ({ item }) => item.type === "date" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DateDivider, { date: item.date }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageBubble, {
319
- content: item.message.content || "",
320
- sender: item.message.direction === "incoming" ? "user" : "bot",
321
- timestamp: new Date(item.message.sentAt).toLocaleTimeString("pt-BR", {
322
- hour: "2-digit",
323
- minute: "2-digit"
324
- }),
325
- status: item.message.status,
326
- templateName: item.message.templateName || void 0,
327
- label: renderMessageLabel?.(item.message)
328
- }),
329
- stickyHeaderIndices,
330
- style: {
331
- height: "100%",
332
- minHeight: 0
333
- }
334
- });
335
- }
336
- function DateDivider({ date }) {
337
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
338
- className: "sticky top-0 z-10 flex justify-center w-full py-2 pointer-events-none",
339
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
340
- className: "bg-white border border-[#e9edef] shadow-[0_1px_0.5px_rgba(11,20,26,0.13)] text-[#54656f] text-[12.5px] font-medium px-3 py-1.5 rounded-lg uppercase pointer-events-auto",
341
- children: date
342
- })
343
- });
344
- }
345
- function MessageViewEmpty({ className, ...props }) {
346
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
347
- className: cn("relative flex flex-1 flex-col items-center justify-center bg-[#f8f9fa] text-[#667781] p-6", className),
348
- ...props,
349
- children: [
350
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
351
- className: "mb-8 flex h-48 w-48 items-center justify-center rounded-full bg-[#f0f2f5] shadow-sm",
352
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
353
- icon: _hugeicons_core_free_icons.Message01Icon,
354
- size: 80,
355
- className: "text-[#bbc5cb]"
356
- })
357
- }),
358
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
359
- className: "mb-3 text-2xl font-semibold text-[#41525d]",
360
- children: "Better Zap"
361
- }),
362
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
363
- className: "max-w-sm space-y-3 text-center",
364
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
365
- className: "text-[15px] leading-relaxed",
366
- children: [
367
- "Esta é uma interface dedicada para visualização e monitoramento de mensagens da ",
368
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "API Oficial do WhatsApp" }),
369
- "."
370
- ]
371
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
372
- className: "text-sm opacity-80",
373
- children: "Acompanhe o histórico de conversas, verifique o status de entrega e gerencie as interações do Cloud API de forma profissional."
374
- })]
375
- })
376
- ]
377
- });
378
- }
379
- //#endregion
380
- //#region src/conversation-search.tsx
381
- function ConversationSearch({ value, onChange, className }) {
382
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
383
- className: cn("border-b border-[#e9edef] shrink-0 p-2", className),
384
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
385
- className: "flex items-center bg-[#f0f2f5] rounded-full px-4 h-[38px] gap-3 focus-within:bg-white focus-within:ring-1 focus-within:ring-green-200 focus-within:shadow-md transition-all border border-transparent focus-within:border-transparent",
386
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
387
- icon: _hugeicons_core_free_icons.Search01Icon,
388
- size: 18,
389
- className: "text-[#54656f] shrink-0"
390
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
391
- type: "text",
392
- placeholder: "Buscar conversa",
393
- value,
394
- onChange: (e) => onChange(e.target.value),
395
- className: "flex-1 border-none bg-transparent text-[15px] text-[#111b21] focus:outline-none h-full placeholder:text-[#667781]"
396
- })]
397
- })
398
- });
399
- }
400
- //#endregion
401
- //#region src/conversation-filter-chips.tsx
402
- const chips = [{
403
- label: "Tudo",
404
- value: "all"
405
- }, {
406
- label: "Não lidas",
407
- value: "unread"
408
- }];
409
- function ConversationFilterChips({ value, onValueChange, unreadCount = 0, className }) {
410
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
411
- className: cn("flex items-center gap-2 px-3 py-3", className),
412
- children: chips.map((chip) => {
413
- const isActive = chip.value === value;
414
- const showCount = chip.value === "unread" && unreadCount > 0;
415
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
416
- type: "button",
417
- onClick: () => onValueChange(chip.value),
418
- "aria-pressed": isActive,
419
- className: cn("inline-flex h-8 items-center rounded-full border px-4 text-[15px] font-medium transition-colors", isActive ? "border-[#b8e6c1] bg-[#e7fce3] text-[#017561]" : "border-[#d1d7db] bg-white text-[#54656f] hover:bg-[#f5f6f6]"),
420
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: chip.label }), showCount ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
421
- className: "ml-1",
422
- children: unreadCount
423
- }) : null]
424
- }, chip.value);
425
- })
426
- });
427
- }
428
- //#endregion
429
- //#region src/conversation-list.tsx
430
- function ConversationList({ conversations, isLoading, isError, selectedConversationId, onSelect, className }) {
431
- const { isMobile, mobileView, setMobileView } = useWhatsappDashboard();
432
- const [search, setSearch] = (0, react.useState)("");
433
- const [filter, setFilter] = (0, react.useState)("all");
434
- const normalizedSearch = search.trim().toLowerCase();
435
- const unreadConversationsCount = conversations.filter((c) => c.unreadCount > 0).length;
436
- const filtered = conversations.filter((conversation) => {
437
- const matchesSearch = normalizedSearch.length === 0 || conversation.phone.toLowerCase().includes(normalizedSearch) || conversation.contactName?.toLowerCase().includes(normalizedSearch);
438
- const matchesFilter = filter === "all" || conversation.unreadCount > 0;
439
- return matchesSearch && matchesFilter;
440
- });
441
- const handleSelect = (id) => {
442
- onSelect(id);
443
- setMobileView("chat");
444
- };
445
- const isVisible = !isMobile || mobileView === "list";
446
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
447
- className: cn("flex flex-col h-full bg-white border-r border-[#e9edef]", isMobile ? "w-full" : "min-w-[320px] max-w-105", className),
448
- style: isVisible ? void 0 : { display: "none" },
449
- children: [
450
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationSearch, {
451
- value: search,
452
- onChange: setSearch
453
- }),
454
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationFilterChips, {
455
- value: filter,
456
- onValueChange: setFilter,
457
- unreadCount: unreadConversationsCount
458
- }),
459
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
460
- className: "min-h-0 flex-1",
461
- children: isLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
462
- className: "flex items-center justify-center h-full text-sm text-[#667781]",
463
- children: "Carregando..."
464
- }) : isError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
465
- className: "flex items-center justify-center h-full text-sm text-red-500",
466
- children: "Erro ao carregar conversas"
467
- }) : filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
468
- className: "flex flex-col items-center justify-center h-full gap-2 text-[#667781]",
469
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
470
- icon: _hugeicons_core_free_icons.Message01Icon,
471
- size: 32
472
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
473
- className: "text-sm",
474
- children: "Nenhuma conversa encontrada"
475
- })]
476
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_legendapp_list_react.LegendList, {
477
- className: "chat-scrollbar",
478
- data: filtered,
479
- estimatedItemSize: 72,
480
- extraData: selectedConversationId,
481
- getFixedItemSize: () => 72,
482
- keyExtractor: (conversation) => conversation.id,
483
- recycleItems: true,
484
- renderItem: ({ item: conversation }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationItem, {
485
- conversation,
486
- isSelected: selectedConversationId === conversation.id,
487
- onClick: () => handleSelect(conversation.id)
488
- }),
489
- style: {
490
- height: "100%",
491
- overflowX: "hidden"
492
- }
493
- })
494
- })
495
- ]
496
- });
497
- }
498
- function ConversationItem({ conversation, isSelected, onClick }) {
499
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
500
- onClick,
501
- "data-selected": isSelected,
502
- className: "group flex items-center w-full h-[72px] px-3 gap-3 transition-all cursor-pointer text-left relative overflow-hidden hover:bg-[#f5f6f6] data-[selected=true]:bg-[#075e54] data-[selected=true]:hover:bg-[#064940]",
503
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
504
- className: "w-[49px] h-[49px] rounded-full bg-[#dfe5e7] flex items-center justify-center shrink-0 group-data-[selected=true]:bg-white/20",
505
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
506
- icon: _hugeicons_core_free_icons.UserIcon,
507
- size: 28,
508
- className: "text-[#aebac1] group-data-[selected=true]:text-white/80"
509
- })
510
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
511
- className: "flex-1 min-w-0 border-b border-[#f2f2f2] h-full flex flex-col justify-center pr-1 group-last:border-none group-data-[selected=true]:border-transparent",
512
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
513
- className: "flex justify-between items-baseline mb-0.5",
514
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
515
- className: "text-[17px] font-normal text-[#111b21] truncate group-data-[selected=true]:text-white",
516
- children: conversation.contactName || formatPhone(conversation.phone)
517
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
518
- className: "text-xs text-[#667781] shrink-0 group-data-[selected=true]:text-white/90",
519
- children: formatTime(conversation.lastMessageAt)
520
- })]
521
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
522
- className: "flex justify-between items-center gap-2",
523
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
524
- className: "text-[14px] text-[#667781] truncate group-data-[selected=true]:text-white/90",
525
- children: [conversation.lastDirection === "incoming" ? "" : "Você: ", conversation.lastMessagePreview || "Sem mensagem"]
526
- }), conversation.unreadCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
527
- className: "bg-[#25d366] text-white text-[11px] font-bold rounded-full min-w-[20px] h-[20px] flex items-center justify-center px-1.5 shrink-0 group-data-[selected=true]:bg-white",
528
- children: conversation.unreadCount
529
- })]
530
- })]
531
- })]
532
- });
533
- }
534
- function formatPhone(phone) {
535
- if (phone.length === 13 && phone.startsWith("55")) return `(${phone.slice(2, 4)}) ${phone.slice(4, 9)}-${phone.slice(9)}`;
536
- return phone;
537
- }
538
- function formatTime(dateStr) {
539
- try {
540
- const date = new Date(dateStr);
541
- const now = /* @__PURE__ */ new Date();
542
- if (date.toDateString() === now.toDateString()) return date.toLocaleTimeString("pt-BR", {
543
- hour: "2-digit",
544
- minute: "2-digit"
545
- });
546
- const yesterday = new Date(now);
547
- yesterday.setDate(yesterday.getDate() - 1);
548
- if (date.toDateString() === yesterday.toDateString()) return "Ontem";
549
- return date.toLocaleDateString("pt-BR", {
550
- day: "2-digit",
551
- month: "2-digit"
552
- });
553
- } catch {
554
- return "";
555
- }
556
- }
557
- //#endregion
558
- //#region src/message-input.tsx
559
- function MessageInput({ onSend, conversation, messages, disabled, placeholder = "Digite uma mensagem", className, contextWindowOpen = true }) {
560
- const [text, setText] = (0, react.useState)("");
561
- const [isSending, setIsSending] = (0, react.useState)(false);
562
- const textareaRef = (0, react.useRef)(null);
563
- const isContextWindowOpen = (conversation ? (0, better_zap.resolveConversationFreeformMessageWindow)(conversation, messages) : {
564
- isOpen: contextWindowOpen,
565
- lastIncomingMessageAt: null,
566
- expiresAt: null
567
- }).isOpen;
568
- const isDisabled = disabled || isSending || !isContextWindowOpen;
569
- const handleSend = async () => {
570
- const trimmedText = text.trim();
571
- if (!trimmedText || isDisabled) return;
572
- setIsSending(true);
573
- try {
574
- await onSend(trimmedText);
575
- setText("");
576
- if (textareaRef.current) textareaRef.current.style.height = "auto";
577
- } finally {
578
- setIsSending(false);
579
- }
580
- };
581
- const handleKeyDown = (e) => {
582
- if (e.key === "Enter" && !e.shiftKey) {
583
- e.preventDefault();
584
- handleSend();
585
- }
586
- };
587
- (0, react.useEffect)(() => {
588
- if (textareaRef.current) {
589
- textareaRef.current.style.height = "auto";
590
- textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
591
- }
592
- }, [text]);
593
- const hasText = text.trim().length > 0;
594
- if (!isContextWindowOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
595
- className: cn("w-full flex flex-col p-4 z-10 shrink-0", className),
596
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
597
- className: "flex items-center gap-2 px-4 py-3 min-h-[62px] bg-[#fef3c7] rounded-2xl border border-[#f59e0b]/20",
598
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
599
- icon: _hugeicons_core_free_icons.Clock01Icon,
600
- size: 20,
601
- className: "text-[#92400e] shrink-0"
602
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
603
- className: "text-[13px] leading-[18px] text-[#92400e]",
604
- children: "A janela de 24h expirou. Mensagens de texto livre só podem ser enviadas dentro de 24h após a última mensagem do contato."
605
- })]
606
- })
607
- });
608
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
609
- className: cn("w-full flex flex-col p-4 z-10 shrink-0", className),
610
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
611
- className: "flex items-center gap-1 px-2 py-1 min-h-[62px] bg-white rounded-2xl shadow-lg border border-gray-100",
612
- children: [
613
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
614
- className: "flex items-center gap-1 text-[#54656f] shrink-0",
615
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
616
- type: "button",
617
- "aria-label": "Emojis",
618
- className: "p-2 rounded-full hover:bg-black/5 transition-colors",
619
- title: "Emojis",
620
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
621
- icon: _hugeicons_core_free_icons.SmileIcon,
622
- size: 24
623
- })
624
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
625
- type: "button",
626
- "aria-label": "Anexar arquivo",
627
- className: "p-2 rounded-full hover:bg-black/5 transition-colors",
628
- title: "Anexar",
629
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
630
- icon: _hugeicons_core_free_icons.Add01Icon,
631
- size: 24
632
- })
633
- })]
634
- }),
635
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
636
- className: "flex-1 flex items-center min-h-[42px] py-3 px-2",
637
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
638
- ref: textareaRef,
639
- className: "flex-1 bg-transparent border-none text-[15px] leading-[22px] text-[#111b21] resize-none focus:outline-none max-h-[120px] placeholder:text-[#8696a0]",
640
- name: "message",
641
- "aria-label": "Mensagem",
642
- autoComplete: "off",
643
- placeholder: isSending ? "Enviando..." : placeholder,
644
- value: text,
645
- onChange: (e) => setText(e.target.value),
646
- onKeyDown: handleKeyDown,
647
- rows: 1,
648
- disabled: isDisabled
649
- })
650
- }),
651
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
652
- className: "flex items-center pr-1 shrink-0",
653
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
654
- type: "button",
655
- "aria-label": hasText ? "Enviar" : "Gravar áudio",
656
- className: cn("p-2 rounded-full transition-colors disabled:opacity-40 disabled:cursor-not-allowed", hasText ? "text-[#00a884]" : "text-[#54656f] hover:bg-black/5"),
657
- onClick: hasText ? handleSend : void 0,
658
- disabled: isDisabled,
659
- children: hasText ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
660
- icon: _hugeicons_core_free_icons.Sent02Icon,
661
- size: 24
662
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
663
- icon: _hugeicons_core_free_icons.Mic01Icon,
664
- size: 24
665
- })
666
- })
667
- })
668
- ]
669
- })
670
- });
671
- }
672
- //#endregion
673
- exports.ConversationFilterChips = ConversationFilterChips;
674
- exports.ConversationItem = ConversationItem;
675
- exports.ConversationList = ConversationList;
676
- exports.FormattedMessage = FormattedMessage;
677
- exports.MessageBubble = MessageBubble;
678
- exports.MessageInput = MessageInput;
679
- exports.MessageList = MessageList;
680
- exports.MessageView = MessageView;
681
- exports.MessageViewContent = MessageViewContent;
682
- exports.MessageViewEmpty = MessageViewEmpty;
683
- exports.MessageViewHeader = MessageViewHeader;
684
- exports.WhatsappDashboard = WhatsappDashboard;
685
- exports.cn = cn;
686
- exports.getDisplayDate = getDisplayDate;
687
- exports.useWhatsappDashboard = useWhatsappDashboard;