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