@algenium/blocks 1.1.2 → 1.2.0-rc.2
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 +1576 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +250 -1
- package/dist/index.d.ts +250 -1
- package/dist/index.js +1564 -3
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React2 from 'react';
|
|
2
|
-
import { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import { createContext, useContext, useState, useCallback, useEffect, useRef, useMemo } from 'react';
|
|
3
3
|
import { useTheme } from 'next-themes';
|
|
4
|
-
import { CheckIcon, CircleIcon, ChevronRightIcon, Monitor, Sun, Moon, Languages, Upload, Move, ZoomOut, ZoomIn, RotateCcw, RotateCw, Grid3X3, RefreshCw, X, XIcon, User, Pencil, Check, Loader2, Bell, CheckCheck, XCircle, AlertTriangle, CheckCircle, Info, Trash2 } from 'lucide-react';
|
|
4
|
+
import { CheckIcon, CircleIcon, ChevronRightIcon, Monitor, Sun, Moon, Languages, Upload, Move, ZoomOut, ZoomIn, RotateCcw, RotateCw, Grid3X3, RefreshCw, X, XIcon, User, Pencil, Check, Loader2, Bell, CheckCheck, XCircle, AlertTriangle, CheckCircle, Info, Trash2, Clock, MapPin, Link, CalendarDays, ExternalLink, ChevronLeft, ChevronRight, Plus, HelpCircle, MessageSquare, Wifi, WifiOff, FileIcon, Download, Paperclip, Send, ArrowLeft } from 'lucide-react';
|
|
5
5
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
6
6
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -13,7 +13,25 @@ import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
|
13
13
|
import { Drawer as Drawer$1 } from 'vaul';
|
|
14
14
|
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
15
15
|
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
|
16
|
+
import { startOfMonth, endOfMonth, eachDayOfInterval, endOfWeek, startOfWeek, format, isSameDay, subMonths, addMonths } from 'date-fns';
|
|
17
|
+
import { DayPicker } from 'react-day-picker';
|
|
16
18
|
|
|
19
|
+
var CalendarContext = createContext(null);
|
|
20
|
+
function useCalendarContext() {
|
|
21
|
+
return useContext(CalendarContext);
|
|
22
|
+
}
|
|
23
|
+
var ChatSidebarContext = createContext(
|
|
24
|
+
null
|
|
25
|
+
);
|
|
26
|
+
function useChatSidebar() {
|
|
27
|
+
const ctx = useContext(ChatSidebarContext);
|
|
28
|
+
if (!ctx) {
|
|
29
|
+
throw new Error("useChatSidebar must be used within a ChatSidebarProvider");
|
|
30
|
+
}
|
|
31
|
+
return ctx;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/contexts/index.ts
|
|
17
35
|
var NotificationsContext = createContext(null);
|
|
18
36
|
function useNotificationsContext() {
|
|
19
37
|
return useContext(NotificationsContext);
|
|
@@ -5380,7 +5398,1550 @@ function ScrollBar({
|
|
|
5380
5398
|
}
|
|
5381
5399
|
);
|
|
5382
5400
|
}
|
|
5401
|
+
function UpcomingEvents({
|
|
5402
|
+
events,
|
|
5403
|
+
maxVisible = 5,
|
|
5404
|
+
onEventClick,
|
|
5405
|
+
className
|
|
5406
|
+
}) {
|
|
5407
|
+
const visible = events.slice(0, maxVisible);
|
|
5408
|
+
return /* @__PURE__ */ jsx("div", { className: cn("space-y-1", className), children: visible.map((event) => {
|
|
5409
|
+
const start = new Date(event.dtstart);
|
|
5410
|
+
const end = new Date(event.dtend);
|
|
5411
|
+
const isToday = start.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
|
|
5412
|
+
const dateStr = isToday ? "Today" : start.toLocaleDateString([], {
|
|
5413
|
+
weekday: "short",
|
|
5414
|
+
month: "short",
|
|
5415
|
+
day: "numeric"
|
|
5416
|
+
});
|
|
5417
|
+
const timeStr = event.allDay ? "All day" : `${start.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} \u2013 ${end.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
|
|
5418
|
+
return /* @__PURE__ */ jsxs(
|
|
5419
|
+
"button",
|
|
5420
|
+
{
|
|
5421
|
+
type: "button",
|
|
5422
|
+
onClick: () => onEventClick?.(event),
|
|
5423
|
+
className: "flex w-full items-start gap-3 rounded-md px-2 py-2 text-left transition-colors hover:bg-accent",
|
|
5424
|
+
children: [
|
|
5425
|
+
/* @__PURE__ */ jsx(
|
|
5426
|
+
"div",
|
|
5427
|
+
{
|
|
5428
|
+
className: cn(
|
|
5429
|
+
"mt-0.5 flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md text-xs font-medium",
|
|
5430
|
+
isToday ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"
|
|
5431
|
+
),
|
|
5432
|
+
children: start.getDate()
|
|
5433
|
+
}
|
|
5434
|
+
),
|
|
5435
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
5436
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: event.summary }),
|
|
5437
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
|
|
5438
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
|
|
5439
|
+
dateStr,
|
|
5440
|
+
" \xB7 ",
|
|
5441
|
+
timeStr
|
|
5442
|
+
] }) }),
|
|
5443
|
+
event.location && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground mt-0.5", children: [
|
|
5444
|
+
/* @__PURE__ */ jsx(MapPin, { className: "h-3 w-3" }),
|
|
5445
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: event.location })
|
|
5446
|
+
] })
|
|
5447
|
+
] })
|
|
5448
|
+
]
|
|
5449
|
+
},
|
|
5450
|
+
event.id
|
|
5451
|
+
);
|
|
5452
|
+
}) });
|
|
5453
|
+
}
|
|
5454
|
+
function CalendarSubscribeButton({
|
|
5455
|
+
feedUrl,
|
|
5456
|
+
label
|
|
5457
|
+
}) {
|
|
5458
|
+
const [copied, setCopied] = useState(false);
|
|
5459
|
+
const webcalUrl = feedUrl.replace(/^https?:\/\//, "webcal://");
|
|
5460
|
+
const handleCopy = async () => {
|
|
5461
|
+
try {
|
|
5462
|
+
await navigator.clipboard.writeText(webcalUrl);
|
|
5463
|
+
setCopied(true);
|
|
5464
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
5465
|
+
} catch {
|
|
5466
|
+
window.open(webcalUrl, "_blank");
|
|
5467
|
+
}
|
|
5468
|
+
};
|
|
5469
|
+
return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
5470
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
5471
|
+
Button,
|
|
5472
|
+
{
|
|
5473
|
+
variant: "ghost",
|
|
5474
|
+
size: "sm",
|
|
5475
|
+
className: "text-xs",
|
|
5476
|
+
onClick: handleCopy,
|
|
5477
|
+
children: [
|
|
5478
|
+
copied ? /* @__PURE__ */ jsx(Check, { className: "mr-1 h-3 w-3" }) : /* @__PURE__ */ jsx(Link, { className: "mr-1 h-3 w-3" }),
|
|
5479
|
+
label ?? (copied ? "Copied!" : "Subscribe")
|
|
5480
|
+
]
|
|
5481
|
+
}
|
|
5482
|
+
) }),
|
|
5483
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: "Copy webcal URL to subscribe in your calendar app" })
|
|
5484
|
+
] }) });
|
|
5485
|
+
}
|
|
5486
|
+
function CalendarWidget({
|
|
5487
|
+
calendarUrl,
|
|
5488
|
+
labels = {},
|
|
5489
|
+
onEventClick,
|
|
5490
|
+
className
|
|
5491
|
+
}) {
|
|
5492
|
+
const calCtx = useCalendarContext();
|
|
5493
|
+
const upcomingEvents = (calCtx?.events ?? []).filter((e) => new Date(e.dtstart) >= /* @__PURE__ */ new Date()).sort(
|
|
5494
|
+
(a, b) => new Date(a.dtstart).getTime() - new Date(b.dtstart).getTime()
|
|
5495
|
+
).slice(0, 5);
|
|
5496
|
+
const hasUpcoming = upcomingEvents.length > 0;
|
|
5497
|
+
return /* @__PURE__ */ jsxs(Popover, { children: [
|
|
5498
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
5499
|
+
Button,
|
|
5500
|
+
{
|
|
5501
|
+
variant: "ghost",
|
|
5502
|
+
size: "icon",
|
|
5503
|
+
className: cn("relative h-9 w-9", className),
|
|
5504
|
+
"aria-label": labels.title ?? "Calendar",
|
|
5505
|
+
children: [
|
|
5506
|
+
/* @__PURE__ */ jsx(CalendarDays, { className: "h-4 w-4" }),
|
|
5507
|
+
hasUpcoming && /* @__PURE__ */ jsx("span", { className: "absolute right-1.5 top-1.5 h-2 w-2 rounded-full bg-primary" })
|
|
5508
|
+
]
|
|
5509
|
+
}
|
|
5510
|
+
) }),
|
|
5511
|
+
/* @__PURE__ */ jsxs(PopoverContent, { className: "w-80 p-0", align: "end", children: [
|
|
5512
|
+
/* @__PURE__ */ jsx("div", { className: "border-b px-4 py-3", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: labels.title ?? "Calendar" }) }),
|
|
5513
|
+
/* @__PURE__ */ jsx(ScrollArea, { className: "max-h-[320px]", children: calCtx?.isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }) }) : upcomingEvents.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-center px-4", children: [
|
|
5514
|
+
/* @__PURE__ */ jsx(CalendarDays, { className: "mb-2 h-8 w-8 text-muted-foreground/50" }),
|
|
5515
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: labels.noEvents ?? "No upcoming events" })
|
|
5516
|
+
] }) : /* @__PURE__ */ jsx(
|
|
5517
|
+
UpcomingEvents,
|
|
5518
|
+
{
|
|
5519
|
+
events: upcomingEvents,
|
|
5520
|
+
onEventClick,
|
|
5521
|
+
className: "p-2"
|
|
5522
|
+
}
|
|
5523
|
+
) }),
|
|
5524
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t px-3 py-2", children: [
|
|
5525
|
+
calendarUrl && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", className: "text-xs", asChild: true, children: /* @__PURE__ */ jsxs("a", { href: calendarUrl, children: [
|
|
5526
|
+
labels.viewCalendar ?? "View full calendar",
|
|
5527
|
+
/* @__PURE__ */ jsx(ExternalLink, { className: "ml-1 h-3 w-3" })
|
|
5528
|
+
] }) }),
|
|
5529
|
+
calCtx?.feedUrl && /* @__PURE__ */ jsx(CalendarSubscribeButton, { feedUrl: calCtx.feedUrl })
|
|
5530
|
+
] })
|
|
5531
|
+
] })
|
|
5532
|
+
] });
|
|
5533
|
+
}
|
|
5534
|
+
function CalendarView({
|
|
5535
|
+
events,
|
|
5536
|
+
isLoading,
|
|
5537
|
+
onEventClick,
|
|
5538
|
+
onDateSelect,
|
|
5539
|
+
onCreateEvent,
|
|
5540
|
+
labels = {},
|
|
5541
|
+
className
|
|
5542
|
+
}) {
|
|
5543
|
+
const [viewMode, setViewMode] = useState("month");
|
|
5544
|
+
const [currentDate, setCurrentDate] = useState(/* @__PURE__ */ new Date());
|
|
5545
|
+
const [selectedDate, setSelectedDate] = useState();
|
|
5546
|
+
const navigateBack = () => {
|
|
5547
|
+
if (viewMode === "month") {
|
|
5548
|
+
setCurrentDate((d) => subMonths(d, 1));
|
|
5549
|
+
} else if (viewMode === "week") {
|
|
5550
|
+
setCurrentDate((d) => new Date(d.getTime() - 7 * 864e5));
|
|
5551
|
+
} else {
|
|
5552
|
+
setCurrentDate((d) => new Date(d.getTime() - 864e5));
|
|
5553
|
+
}
|
|
5554
|
+
};
|
|
5555
|
+
const navigateForward = () => {
|
|
5556
|
+
if (viewMode === "month") {
|
|
5557
|
+
setCurrentDate((d) => addMonths(d, 1));
|
|
5558
|
+
} else if (viewMode === "week") {
|
|
5559
|
+
setCurrentDate((d) => new Date(d.getTime() + 7 * 864e5));
|
|
5560
|
+
} else {
|
|
5561
|
+
setCurrentDate((d) => new Date(d.getTime() + 864e5));
|
|
5562
|
+
}
|
|
5563
|
+
};
|
|
5564
|
+
const goToToday = () => {
|
|
5565
|
+
setCurrentDate(/* @__PURE__ */ new Date());
|
|
5566
|
+
setSelectedDate(/* @__PURE__ */ new Date());
|
|
5567
|
+
};
|
|
5568
|
+
const handleDayClick = (date) => {
|
|
5569
|
+
setSelectedDate(date);
|
|
5570
|
+
onDateSelect?.(date);
|
|
5571
|
+
};
|
|
5572
|
+
const eventsByDate = useMemo(() => {
|
|
5573
|
+
const map = /* @__PURE__ */ new Map();
|
|
5574
|
+
for (const event of events) {
|
|
5575
|
+
const key = new Date(event.dtstart).toDateString();
|
|
5576
|
+
const list = map.get(key) ?? [];
|
|
5577
|
+
list.push(event);
|
|
5578
|
+
map.set(key, list);
|
|
5579
|
+
}
|
|
5580
|
+
return map;
|
|
5581
|
+
}, [events]);
|
|
5582
|
+
const visibleDays = useMemo(() => {
|
|
5583
|
+
if (viewMode === "month") {
|
|
5584
|
+
const monthStart = startOfMonth(currentDate);
|
|
5585
|
+
const monthEnd = endOfMonth(currentDate);
|
|
5586
|
+
return eachDayOfInterval({
|
|
5587
|
+
start: startOfWeek(monthStart),
|
|
5588
|
+
end: endOfWeek(monthEnd)
|
|
5589
|
+
});
|
|
5590
|
+
} else if (viewMode === "week") {
|
|
5591
|
+
const weekStart = startOfWeek(currentDate);
|
|
5592
|
+
const weekEnd = endOfWeek(currentDate);
|
|
5593
|
+
return eachDayOfInterval({ start: weekStart, end: weekEnd });
|
|
5594
|
+
}
|
|
5595
|
+
return [currentDate];
|
|
5596
|
+
}, [viewMode, currentDate]);
|
|
5597
|
+
const selectedDayEvents = selectedDate ? eventsByDate.get(selectedDate.toDateString()) ?? [] : [];
|
|
5598
|
+
const headerLabel = viewMode === "day" ? format(currentDate, "EEEE, MMMM d, yyyy") : format(currentDate, "MMMM yyyy");
|
|
5599
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex h-full flex-col", className), children: [
|
|
5600
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [
|
|
5601
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
5602
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
5603
|
+
/* @__PURE__ */ jsx(
|
|
5604
|
+
Button,
|
|
5605
|
+
{
|
|
5606
|
+
variant: "ghost",
|
|
5607
|
+
size: "icon",
|
|
5608
|
+
className: "h-8 w-8",
|
|
5609
|
+
onClick: navigateBack,
|
|
5610
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
|
|
5611
|
+
}
|
|
5612
|
+
),
|
|
5613
|
+
/* @__PURE__ */ jsx(
|
|
5614
|
+
Button,
|
|
5615
|
+
{
|
|
5616
|
+
variant: "ghost",
|
|
5617
|
+
size: "icon",
|
|
5618
|
+
className: "h-8 w-8",
|
|
5619
|
+
onClick: navigateForward,
|
|
5620
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
|
|
5621
|
+
}
|
|
5622
|
+
)
|
|
5623
|
+
] }),
|
|
5624
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: headerLabel }),
|
|
5625
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: goToToday, children: labels.today ?? "Today" })
|
|
5626
|
+
] }),
|
|
5627
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5628
|
+
/* @__PURE__ */ jsx("div", { className: "flex rounded-md border", children: ["month", "week", "day"].map((mode) => /* @__PURE__ */ jsx(
|
|
5629
|
+
Button,
|
|
5630
|
+
{
|
|
5631
|
+
variant: viewMode === mode ? "default" : "ghost",
|
|
5632
|
+
size: "sm",
|
|
5633
|
+
className: "rounded-none first:rounded-l-md last:rounded-r-md",
|
|
5634
|
+
onClick: () => setViewMode(mode),
|
|
5635
|
+
children: labels[mode] ?? mode.charAt(0).toUpperCase() + mode.slice(1)
|
|
5636
|
+
},
|
|
5637
|
+
mode
|
|
5638
|
+
)) }),
|
|
5639
|
+
onCreateEvent && /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: onCreateEvent, children: [
|
|
5640
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1 h-4 w-4" }),
|
|
5641
|
+
labels.newEvent ?? "New event"
|
|
5642
|
+
] })
|
|
5643
|
+
] })
|
|
5644
|
+
] }),
|
|
5645
|
+
isLoading ? /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(Loader2, { className: "h-8 w-8 animate-spin text-muted-foreground" }) }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
|
|
5646
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto p-4", children: viewMode === "month" ? /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-7 gap-px rounded-lg border bg-border", children: [
|
|
5647
|
+
["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map(
|
|
5648
|
+
(day) => /* @__PURE__ */ jsx(
|
|
5649
|
+
"div",
|
|
5650
|
+
{
|
|
5651
|
+
className: "bg-muted px-2 py-1.5 text-center text-xs font-medium text-muted-foreground",
|
|
5652
|
+
children: day
|
|
5653
|
+
},
|
|
5654
|
+
day
|
|
5655
|
+
)
|
|
5656
|
+
),
|
|
5657
|
+
visibleDays.map((day) => {
|
|
5658
|
+
const dayEvents = eventsByDate.get(day.toDateString()) ?? [];
|
|
5659
|
+
const isCurrentMonth = day.getMonth() === currentDate.getMonth();
|
|
5660
|
+
const isToday = isSameDay(day, /* @__PURE__ */ new Date());
|
|
5661
|
+
const isSelected = selectedDate && isSameDay(day, selectedDate);
|
|
5662
|
+
return /* @__PURE__ */ jsxs(
|
|
5663
|
+
"button",
|
|
5664
|
+
{
|
|
5665
|
+
type: "button",
|
|
5666
|
+
onClick: () => handleDayClick(day),
|
|
5667
|
+
className: cn(
|
|
5668
|
+
"min-h-[80px] bg-background p-1 text-left transition-colors hover:bg-accent",
|
|
5669
|
+
!isCurrentMonth && "opacity-40",
|
|
5670
|
+
isSelected && "ring-2 ring-primary ring-inset"
|
|
5671
|
+
),
|
|
5672
|
+
children: [
|
|
5673
|
+
/* @__PURE__ */ jsx(
|
|
5674
|
+
"span",
|
|
5675
|
+
{
|
|
5676
|
+
className: cn(
|
|
5677
|
+
"inline-flex h-6 w-6 items-center justify-center rounded-full text-xs",
|
|
5678
|
+
isToday && "bg-primary text-primary-foreground font-bold"
|
|
5679
|
+
),
|
|
5680
|
+
children: day.getDate()
|
|
5681
|
+
}
|
|
5682
|
+
),
|
|
5683
|
+
dayEvents.slice(0, 3).map((evt) => /* @__PURE__ */ jsx(
|
|
5684
|
+
"div",
|
|
5685
|
+
{
|
|
5686
|
+
className: "mt-0.5 truncate rounded bg-primary/10 px-1 py-0.5 text-[10px] text-primary",
|
|
5687
|
+
onClick: (e) => {
|
|
5688
|
+
e.stopPropagation();
|
|
5689
|
+
onEventClick?.(evt);
|
|
5690
|
+
},
|
|
5691
|
+
role: "button",
|
|
5692
|
+
tabIndex: 0,
|
|
5693
|
+
onKeyDown: (e) => {
|
|
5694
|
+
if (e.key === "Enter") onEventClick?.(evt);
|
|
5695
|
+
},
|
|
5696
|
+
children: evt.summary
|
|
5697
|
+
},
|
|
5698
|
+
evt.id
|
|
5699
|
+
)),
|
|
5700
|
+
dayEvents.length > 3 && /* @__PURE__ */ jsxs("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: [
|
|
5701
|
+
"+",
|
|
5702
|
+
dayEvents.length - 3,
|
|
5703
|
+
" more"
|
|
5704
|
+
] })
|
|
5705
|
+
]
|
|
5706
|
+
},
|
|
5707
|
+
day.toISOString()
|
|
5708
|
+
);
|
|
5709
|
+
})
|
|
5710
|
+
] }) : viewMode === "week" ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-2", children: visibleDays.map((day) => {
|
|
5711
|
+
const dayEvents = eventsByDate.get(day.toDateString()) ?? [];
|
|
5712
|
+
const isToday = isSameDay(day, /* @__PURE__ */ new Date());
|
|
5713
|
+
return /* @__PURE__ */ jsxs("div", { className: "min-h-[200px]", children: [
|
|
5714
|
+
/* @__PURE__ */ jsxs(
|
|
5715
|
+
"div",
|
|
5716
|
+
{
|
|
5717
|
+
className: cn(
|
|
5718
|
+
"mb-2 text-center text-xs",
|
|
5719
|
+
isToday && "font-bold text-primary"
|
|
5720
|
+
),
|
|
5721
|
+
children: [
|
|
5722
|
+
/* @__PURE__ */ jsx("p", { children: format(day, "EEE") }),
|
|
5723
|
+
/* @__PURE__ */ jsx(
|
|
5724
|
+
"p",
|
|
5725
|
+
{
|
|
5726
|
+
className: cn(
|
|
5727
|
+
"inline-flex h-7 w-7 items-center justify-center rounded-full",
|
|
5728
|
+
isToday && "bg-primary text-primary-foreground"
|
|
5729
|
+
),
|
|
5730
|
+
children: day.getDate()
|
|
5731
|
+
}
|
|
5732
|
+
)
|
|
5733
|
+
]
|
|
5734
|
+
}
|
|
5735
|
+
),
|
|
5736
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1", children: dayEvents.map((evt) => /* @__PURE__ */ jsxs(
|
|
5737
|
+
"button",
|
|
5738
|
+
{
|
|
5739
|
+
type: "button",
|
|
5740
|
+
onClick: () => onEventClick?.(evt),
|
|
5741
|
+
className: "w-full rounded bg-primary/10 px-2 py-1 text-left text-xs text-primary transition-colors hover:bg-primary/20",
|
|
5742
|
+
children: [
|
|
5743
|
+
/* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: evt.summary }),
|
|
5744
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] opacity-70", children: format(new Date(evt.dtstart), "h:mm a") })
|
|
5745
|
+
]
|
|
5746
|
+
},
|
|
5747
|
+
evt.id
|
|
5748
|
+
)) })
|
|
5749
|
+
] }, day.toISOString());
|
|
5750
|
+
}) }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: visibleDays.flatMap((day) => {
|
|
5751
|
+
const dayEvents = eventsByDate.get(day.toDateString()) ?? [];
|
|
5752
|
+
if (dayEvents.length === 0) {
|
|
5753
|
+
return [
|
|
5754
|
+
/* @__PURE__ */ jsxs(
|
|
5755
|
+
"div",
|
|
5756
|
+
{
|
|
5757
|
+
className: "flex flex-col items-center justify-center py-16 text-center",
|
|
5758
|
+
children: [
|
|
5759
|
+
/* @__PURE__ */ jsx(CalendarDays, { className: "mb-3 h-10 w-10 text-muted-foreground/50" }),
|
|
5760
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: labels.noEvents ?? "No events for this day" })
|
|
5761
|
+
]
|
|
5762
|
+
},
|
|
5763
|
+
"empty"
|
|
5764
|
+
)
|
|
5765
|
+
];
|
|
5766
|
+
}
|
|
5767
|
+
return dayEvents.map((evt) => /* @__PURE__ */ jsxs(
|
|
5768
|
+
"button",
|
|
5769
|
+
{
|
|
5770
|
+
type: "button",
|
|
5771
|
+
onClick: () => onEventClick?.(evt),
|
|
5772
|
+
className: "flex w-full items-center gap-3 rounded-lg border p-3 text-left transition-colors hover:bg-accent",
|
|
5773
|
+
children: [
|
|
5774
|
+
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-primary flex-shrink-0" }),
|
|
5775
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
5776
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: evt.summary }),
|
|
5777
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
5778
|
+
format(new Date(evt.dtstart), "h:mm a"),
|
|
5779
|
+
" \u2013",
|
|
5780
|
+
" ",
|
|
5781
|
+
format(new Date(evt.dtend), "h:mm a")
|
|
5782
|
+
] }),
|
|
5783
|
+
evt.location && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: evt.location })
|
|
5784
|
+
] })
|
|
5785
|
+
]
|
|
5786
|
+
},
|
|
5787
|
+
evt.id
|
|
5788
|
+
));
|
|
5789
|
+
}) }) }),
|
|
5790
|
+
selectedDate && selectedDayEvents.length > 0 && viewMode === "month" && /* @__PURE__ */ jsxs("div", { className: "w-64 border-l p-3", children: [
|
|
5791
|
+
/* @__PURE__ */ jsx("h3", { className: "mb-2 text-sm font-semibold", children: format(selectedDate, "MMMM d") }),
|
|
5792
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: selectedDayEvents.map((evt) => /* @__PURE__ */ jsxs(
|
|
5793
|
+
"button",
|
|
5794
|
+
{
|
|
5795
|
+
type: "button",
|
|
5796
|
+
onClick: () => onEventClick?.(evt),
|
|
5797
|
+
className: "w-full rounded-md bg-muted p-2 text-left text-sm transition-colors hover:bg-accent",
|
|
5798
|
+
children: [
|
|
5799
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium truncate", children: evt.summary }),
|
|
5800
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: evt.allDay ? "All day" : `${format(new Date(evt.dtstart), "h:mm a")} \u2013 ${format(new Date(evt.dtend), "h:mm a")}` })
|
|
5801
|
+
]
|
|
5802
|
+
},
|
|
5803
|
+
evt.id
|
|
5804
|
+
)) })
|
|
5805
|
+
] })
|
|
5806
|
+
] })
|
|
5807
|
+
] });
|
|
5808
|
+
}
|
|
5809
|
+
function EventDialog({
|
|
5810
|
+
open,
|
|
5811
|
+
onOpenChange,
|
|
5812
|
+
event,
|
|
5813
|
+
onSave,
|
|
5814
|
+
onDelete,
|
|
5815
|
+
labels = {}
|
|
5816
|
+
}) {
|
|
5817
|
+
const isEdit = !!event?.id;
|
|
5818
|
+
const [summary, setSummary] = useState(event?.summary ?? "");
|
|
5819
|
+
const [description, setDescription] = useState(event?.description ?? "");
|
|
5820
|
+
const [location, setLocation] = useState(event?.location ?? "");
|
|
5821
|
+
const [dtstart, setDtstart] = useState(
|
|
5822
|
+
event?.dtstart ? new Date(event.dtstart).toISOString().slice(0, 16) : ""
|
|
5823
|
+
);
|
|
5824
|
+
const [dtend, setDtend] = useState(
|
|
5825
|
+
event?.dtend ? new Date(event.dtend).toISOString().slice(0, 16) : ""
|
|
5826
|
+
);
|
|
5827
|
+
const [allDay, setAllDay] = useState(event?.allDay ?? false);
|
|
5828
|
+
const [saving, setSaving] = useState(false);
|
|
5829
|
+
const handleSave = async () => {
|
|
5830
|
+
if (!summary.trim() || !dtstart || !dtend) return;
|
|
5831
|
+
setSaving(true);
|
|
5832
|
+
try {
|
|
5833
|
+
await onSave({
|
|
5834
|
+
...event?.id ? { id: event.id } : {},
|
|
5835
|
+
summary: summary.trim(),
|
|
5836
|
+
description: description.trim() || null,
|
|
5837
|
+
location: location.trim() || null,
|
|
5838
|
+
dtstart: new Date(dtstart).toISOString(),
|
|
5839
|
+
dtend: new Date(dtend).toISOString(),
|
|
5840
|
+
allDay
|
|
5841
|
+
});
|
|
5842
|
+
onOpenChange(false);
|
|
5843
|
+
} finally {
|
|
5844
|
+
setSaving(false);
|
|
5845
|
+
}
|
|
5846
|
+
};
|
|
5847
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-[480px]", children: [
|
|
5848
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: isEdit ? labels.editTitle ?? "Edit event" : labels.createTitle ?? "New event" }) }),
|
|
5849
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4 py-2", children: [
|
|
5850
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5851
|
+
/* @__PURE__ */ jsx("label", { className: "mb-1 block text-sm font-medium", children: labels.summary ?? "Summary" }),
|
|
5852
|
+
/* @__PURE__ */ jsx(
|
|
5853
|
+
"input",
|
|
5854
|
+
{
|
|
5855
|
+
value: summary,
|
|
5856
|
+
onChange: (e) => setSummary(e.target.value),
|
|
5857
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
5858
|
+
placeholder: "Event name"
|
|
5859
|
+
}
|
|
5860
|
+
)
|
|
5861
|
+
] }),
|
|
5862
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
5863
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5864
|
+
/* @__PURE__ */ jsxs("label", { className: "mb-1 block text-sm font-medium", children: [
|
|
5865
|
+
/* @__PURE__ */ jsx(Clock, { className: "mr-1 inline h-3.5 w-3.5" }),
|
|
5866
|
+
labels.startDate ?? "Start"
|
|
5867
|
+
] }),
|
|
5868
|
+
/* @__PURE__ */ jsx(
|
|
5869
|
+
"input",
|
|
5870
|
+
{
|
|
5871
|
+
type: "datetime-local",
|
|
5872
|
+
value: dtstart,
|
|
5873
|
+
onChange: (e) => setDtstart(e.target.value),
|
|
5874
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
5875
|
+
}
|
|
5876
|
+
)
|
|
5877
|
+
] }),
|
|
5878
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5879
|
+
/* @__PURE__ */ jsx("label", { className: "mb-1 block text-sm font-medium", children: labels.endDate ?? "End" }),
|
|
5880
|
+
/* @__PURE__ */ jsx(
|
|
5881
|
+
"input",
|
|
5882
|
+
{
|
|
5883
|
+
type: "datetime-local",
|
|
5884
|
+
value: dtend,
|
|
5885
|
+
onChange: (e) => setDtend(e.target.value),
|
|
5886
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
5887
|
+
}
|
|
5888
|
+
)
|
|
5889
|
+
] })
|
|
5890
|
+
] }),
|
|
5891
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
|
|
5892
|
+
/* @__PURE__ */ jsx(
|
|
5893
|
+
"input",
|
|
5894
|
+
{
|
|
5895
|
+
type: "checkbox",
|
|
5896
|
+
checked: allDay,
|
|
5897
|
+
onChange: (e) => setAllDay(e.target.checked),
|
|
5898
|
+
className: "rounded border-input"
|
|
5899
|
+
}
|
|
5900
|
+
),
|
|
5901
|
+
labels.allDay ?? "All day"
|
|
5902
|
+
] }),
|
|
5903
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5904
|
+
/* @__PURE__ */ jsxs("label", { className: "mb-1 block text-sm font-medium", children: [
|
|
5905
|
+
/* @__PURE__ */ jsx(MapPin, { className: "mr-1 inline h-3.5 w-3.5" }),
|
|
5906
|
+
labels.location ?? "Location"
|
|
5907
|
+
] }),
|
|
5908
|
+
/* @__PURE__ */ jsx(
|
|
5909
|
+
"input",
|
|
5910
|
+
{
|
|
5911
|
+
value: location,
|
|
5912
|
+
onChange: (e) => setLocation(e.target.value),
|
|
5913
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
5914
|
+
placeholder: "Add location"
|
|
5915
|
+
}
|
|
5916
|
+
)
|
|
5917
|
+
] }),
|
|
5918
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5919
|
+
/* @__PURE__ */ jsx("label", { className: "mb-1 block text-sm font-medium", children: labels.description ?? "Description" }),
|
|
5920
|
+
/* @__PURE__ */ jsx(
|
|
5921
|
+
"textarea",
|
|
5922
|
+
{
|
|
5923
|
+
value: description,
|
|
5924
|
+
onChange: (e) => setDescription(e.target.value),
|
|
5925
|
+
rows: 3,
|
|
5926
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring resize-none",
|
|
5927
|
+
placeholder: "Add description"
|
|
5928
|
+
}
|
|
5929
|
+
)
|
|
5930
|
+
] })
|
|
5931
|
+
] }),
|
|
5932
|
+
/* @__PURE__ */ jsxs(DialogFooter, { className: "gap-2", children: [
|
|
5933
|
+
isEdit && onDelete && /* @__PURE__ */ jsx(Button, { variant: "destructive", size: "sm", onClick: onDelete, children: labels.delete ?? "Delete" }),
|
|
5934
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
5935
|
+
/* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: labels.cancel ?? "Cancel" }) }),
|
|
5936
|
+
/* @__PURE__ */ jsxs(
|
|
5937
|
+
Button,
|
|
5938
|
+
{
|
|
5939
|
+
size: "sm",
|
|
5940
|
+
onClick: handleSave,
|
|
5941
|
+
disabled: saving || !summary.trim() || !dtstart || !dtend,
|
|
5942
|
+
children: [
|
|
5943
|
+
saving && /* @__PURE__ */ jsx(Loader2, { className: "mr-1 h-3.5 w-3.5 animate-spin" }),
|
|
5944
|
+
labels.save ?? "Save"
|
|
5945
|
+
]
|
|
5946
|
+
}
|
|
5947
|
+
)
|
|
5948
|
+
] })
|
|
5949
|
+
] }) });
|
|
5950
|
+
}
|
|
5951
|
+
function MiniCalendar({
|
|
5952
|
+
selected,
|
|
5953
|
+
onSelect,
|
|
5954
|
+
events = [],
|
|
5955
|
+
className
|
|
5956
|
+
}) {
|
|
5957
|
+
const eventDates = new Set(
|
|
5958
|
+
events.map((e) => new Date(e.dtstart).toDateString())
|
|
5959
|
+
);
|
|
5960
|
+
return /* @__PURE__ */ jsx(
|
|
5961
|
+
DayPicker,
|
|
5962
|
+
{
|
|
5963
|
+
mode: "single",
|
|
5964
|
+
selected,
|
|
5965
|
+
onSelect,
|
|
5966
|
+
showOutsideDays: true,
|
|
5967
|
+
className: cn("p-2", className),
|
|
5968
|
+
classNames: {
|
|
5969
|
+
months: "flex flex-col",
|
|
5970
|
+
month: "space-y-2",
|
|
5971
|
+
month_caption: "flex justify-center relative items-center",
|
|
5972
|
+
caption_label: "text-sm font-medium",
|
|
5973
|
+
nav: "space-x-1 flex items-center",
|
|
5974
|
+
button_previous: "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 absolute left-1",
|
|
5975
|
+
button_next: "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 absolute right-1",
|
|
5976
|
+
month_grid: "w-full border-collapse space-y-1",
|
|
5977
|
+
weekdays: "flex",
|
|
5978
|
+
weekday: "text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
|
|
5979
|
+
week: "flex w-full mt-1",
|
|
5980
|
+
day: "relative p-0 text-center text-sm focus-within:relative focus-within:z-20",
|
|
5981
|
+
day_button: cn(
|
|
5982
|
+
"h-8 w-8 p-0 font-normal",
|
|
5983
|
+
"hover:bg-accent hover:text-accent-foreground rounded-md"
|
|
5984
|
+
),
|
|
5985
|
+
selected: "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground rounded-md",
|
|
5986
|
+
today: "bg-accent text-accent-foreground rounded-md",
|
|
5987
|
+
outside: "text-muted-foreground opacity-50",
|
|
5988
|
+
disabled: "text-muted-foreground opacity-50"
|
|
5989
|
+
},
|
|
5990
|
+
modifiers: {
|
|
5991
|
+
hasEvent: (date) => eventDates.has(date.toDateString())
|
|
5992
|
+
},
|
|
5993
|
+
modifiersClassNames: {
|
|
5994
|
+
hasEvent: "font-bold"
|
|
5995
|
+
}
|
|
5996
|
+
}
|
|
5997
|
+
);
|
|
5998
|
+
}
|
|
5999
|
+
var statusConfig = {
|
|
6000
|
+
ACCEPTED: {
|
|
6001
|
+
icon: Check,
|
|
6002
|
+
label: "Accepted",
|
|
6003
|
+
className: "bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/30"
|
|
6004
|
+
},
|
|
6005
|
+
DECLINED: {
|
|
6006
|
+
icon: X,
|
|
6007
|
+
label: "Declined",
|
|
6008
|
+
className: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/30"
|
|
6009
|
+
},
|
|
6010
|
+
TENTATIVE: {
|
|
6011
|
+
icon: HelpCircle,
|
|
6012
|
+
label: "Tentative",
|
|
6013
|
+
className: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 border-yellow-500/30"
|
|
6014
|
+
},
|
|
6015
|
+
"NEEDS-ACTION": {
|
|
6016
|
+
icon: HelpCircle,
|
|
6017
|
+
label: "Pending",
|
|
6018
|
+
className: "bg-muted text-muted-foreground border-border"
|
|
6019
|
+
}
|
|
6020
|
+
};
|
|
6021
|
+
function EventRsvpBadge({
|
|
6022
|
+
eventId,
|
|
6023
|
+
currentStatus,
|
|
6024
|
+
onRsvp,
|
|
6025
|
+
className
|
|
6026
|
+
}) {
|
|
6027
|
+
const [loading, setLoading] = useState(null);
|
|
6028
|
+
const status = currentStatus?.toUpperCase() || "NEEDS-ACTION";
|
|
6029
|
+
const handleRsvp = async (partstat) => {
|
|
6030
|
+
if (partstat === status) return;
|
|
6031
|
+
setLoading(partstat);
|
|
6032
|
+
try {
|
|
6033
|
+
await onRsvp(eventId, partstat);
|
|
6034
|
+
} finally {
|
|
6035
|
+
setLoading(null);
|
|
6036
|
+
}
|
|
6037
|
+
};
|
|
6038
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex items-center gap-1", className), children: ["ACCEPTED", "TENTATIVE", "DECLINED"].map((ps) => {
|
|
6039
|
+
const cfg = statusConfig[ps];
|
|
6040
|
+
const Icon = cfg.icon;
|
|
6041
|
+
const isActive = status === ps;
|
|
6042
|
+
return /* @__PURE__ */ jsxs(
|
|
6043
|
+
Button,
|
|
6044
|
+
{
|
|
6045
|
+
variant: "outline",
|
|
6046
|
+
size: "sm",
|
|
6047
|
+
className: cn("h-7 px-2 text-xs", isActive && cfg.className),
|
|
6048
|
+
onClick: () => handleRsvp(ps),
|
|
6049
|
+
disabled: loading !== null,
|
|
6050
|
+
children: [
|
|
6051
|
+
loading === ps ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1 h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsx(Icon, { className: "mr-1 h-3 w-3" }),
|
|
6052
|
+
cfg.label
|
|
6053
|
+
]
|
|
6054
|
+
},
|
|
6055
|
+
ps
|
|
6056
|
+
);
|
|
6057
|
+
}) });
|
|
6058
|
+
}
|
|
6059
|
+
var STORAGE_KEY = "cartera:chatSidebarOpen";
|
|
6060
|
+
function ChatSidebarProvider({
|
|
6061
|
+
fetchConversations,
|
|
6062
|
+
children,
|
|
6063
|
+
defaultOpen
|
|
6064
|
+
}) {
|
|
6065
|
+
const [isOpen, setIsOpen] = useState(() => {
|
|
6066
|
+
if (defaultOpen !== void 0) return defaultOpen;
|
|
6067
|
+
if (typeof window === "undefined") return false;
|
|
6068
|
+
try {
|
|
6069
|
+
return localStorage.getItem(STORAGE_KEY) === "true";
|
|
6070
|
+
} catch {
|
|
6071
|
+
return false;
|
|
6072
|
+
}
|
|
6073
|
+
});
|
|
6074
|
+
const [view, setView] = useState("list");
|
|
6075
|
+
const [conversations, setConversations] = useState([]);
|
|
6076
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
6077
|
+
const [activeCaseId, setActiveCaseId] = useState(null);
|
|
6078
|
+
const [activeRoomId, setActiveRoomId] = useState(null);
|
|
6079
|
+
const fetchRef = useRef(fetchConversations);
|
|
6080
|
+
fetchRef.current = fetchConversations;
|
|
6081
|
+
const persistOpen = useCallback((open2) => {
|
|
6082
|
+
setIsOpen(open2);
|
|
6083
|
+
try {
|
|
6084
|
+
localStorage.setItem(STORAGE_KEY, String(open2));
|
|
6085
|
+
} catch {
|
|
6086
|
+
}
|
|
6087
|
+
}, []);
|
|
6088
|
+
const loadConversations = useCallback(async () => {
|
|
6089
|
+
setIsLoading(true);
|
|
6090
|
+
try {
|
|
6091
|
+
const result = await fetchRef.current();
|
|
6092
|
+
setConversations(result);
|
|
6093
|
+
} catch {
|
|
6094
|
+
} finally {
|
|
6095
|
+
setIsLoading(false);
|
|
6096
|
+
}
|
|
6097
|
+
}, []);
|
|
6098
|
+
useEffect(() => {
|
|
6099
|
+
void loadConversations();
|
|
6100
|
+
}, [loadConversations]);
|
|
6101
|
+
const toggle = useCallback(() => persistOpen(!isOpen), [isOpen, persistOpen]);
|
|
6102
|
+
const open = useCallback(() => persistOpen(true), [persistOpen]);
|
|
6103
|
+
const close = useCallback(() => {
|
|
6104
|
+
persistOpen(false);
|
|
6105
|
+
setView("list");
|
|
6106
|
+
setActiveCaseId(null);
|
|
6107
|
+
setActiveRoomId(null);
|
|
6108
|
+
}, [persistOpen]);
|
|
6109
|
+
const openChat = useCallback(
|
|
6110
|
+
(caseId, roomId) => {
|
|
6111
|
+
persistOpen(true);
|
|
6112
|
+
setActiveCaseId(caseId);
|
|
6113
|
+
if (roomId) {
|
|
6114
|
+
setActiveRoomId(roomId);
|
|
6115
|
+
setView("chat");
|
|
6116
|
+
} else {
|
|
6117
|
+
const conv = conversations.find((c) => c.caseId === caseId);
|
|
6118
|
+
if (conv && conv.rooms.length === 1) {
|
|
6119
|
+
setActiveRoomId(conv.rooms[0].id);
|
|
6120
|
+
setView("chat");
|
|
6121
|
+
} else {
|
|
6122
|
+
setActiveRoomId(null);
|
|
6123
|
+
setView("list");
|
|
6124
|
+
}
|
|
6125
|
+
}
|
|
6126
|
+
},
|
|
6127
|
+
[persistOpen, conversations]
|
|
6128
|
+
);
|
|
6129
|
+
const selectRoom = useCallback(
|
|
6130
|
+
(roomId, caseId) => {
|
|
6131
|
+
setActiveCaseId(caseId);
|
|
6132
|
+
setActiveRoomId(roomId);
|
|
6133
|
+
setView("chat");
|
|
6134
|
+
if (!isOpen) persistOpen(true);
|
|
6135
|
+
},
|
|
6136
|
+
[isOpen, persistOpen]
|
|
6137
|
+
);
|
|
6138
|
+
const back = useCallback(() => {
|
|
6139
|
+
setView("list");
|
|
6140
|
+
setActiveRoomId(null);
|
|
6141
|
+
}, []);
|
|
6142
|
+
const value = {
|
|
6143
|
+
isOpen,
|
|
6144
|
+
view,
|
|
6145
|
+
conversations,
|
|
6146
|
+
isLoading,
|
|
6147
|
+
activeCaseId,
|
|
6148
|
+
activeRoomId,
|
|
6149
|
+
toggle,
|
|
6150
|
+
open,
|
|
6151
|
+
close,
|
|
6152
|
+
openChat,
|
|
6153
|
+
selectRoom,
|
|
6154
|
+
back,
|
|
6155
|
+
refetch: loadConversations
|
|
6156
|
+
};
|
|
6157
|
+
return /* @__PURE__ */ jsx(ChatSidebarContext.Provider, { value, children });
|
|
6158
|
+
}
|
|
6159
|
+
var PING_INTERVAL = 3e4;
|
|
6160
|
+
var MIN_RECONNECT_DELAY = 1e3;
|
|
6161
|
+
var MAX_RECONNECT_DELAY = 3e4;
|
|
6162
|
+
function useChatRoom(roomId, config, options = {}) {
|
|
6163
|
+
const {
|
|
6164
|
+
minReconnectDelay = MIN_RECONNECT_DELAY,
|
|
6165
|
+
maxReconnectDelay = MAX_RECONNECT_DELAY,
|
|
6166
|
+
pingInterval = PING_INTERVAL
|
|
6167
|
+
} = options;
|
|
6168
|
+
const [messages, setMessages] = useState([]);
|
|
6169
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
6170
|
+
const [error, setError] = useState(null);
|
|
6171
|
+
const [typingUsers, setTypingUsers] = useState([]);
|
|
6172
|
+
const [hasMoreHistory, setHasMoreHistory] = useState(true);
|
|
6173
|
+
const [isLoadingHistory, setIsLoadingHistory] = useState(false);
|
|
6174
|
+
const wsRef = useRef(null);
|
|
6175
|
+
const mountedRef = useRef(true);
|
|
6176
|
+
const reconnectDelayRef = useRef(minReconnectDelay);
|
|
6177
|
+
const reconnectTimerRef = useRef(null);
|
|
6178
|
+
const pingTimerRef = useRef(null);
|
|
6179
|
+
const roomIdRef = useRef(roomId);
|
|
6180
|
+
const cursorRef = useRef(null);
|
|
6181
|
+
const messageIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
6182
|
+
const loadMoreHistoryRef = useRef(
|
|
6183
|
+
() => Promise.resolve()
|
|
6184
|
+
);
|
|
6185
|
+
const configRef = useRef(config);
|
|
6186
|
+
roomIdRef.current = roomId;
|
|
6187
|
+
configRef.current = config;
|
|
6188
|
+
const clearTimers = useCallback(() => {
|
|
6189
|
+
if (reconnectTimerRef.current) {
|
|
6190
|
+
clearTimeout(reconnectTimerRef.current);
|
|
6191
|
+
reconnectTimerRef.current = null;
|
|
6192
|
+
}
|
|
6193
|
+
if (pingTimerRef.current) {
|
|
6194
|
+
clearInterval(pingTimerRef.current);
|
|
6195
|
+
pingTimerRef.current = null;
|
|
6196
|
+
}
|
|
6197
|
+
}, []);
|
|
6198
|
+
const loadMoreHistory = useCallback(async () => {
|
|
6199
|
+
if (!roomIdRef.current || isLoadingHistory || !hasMoreHistory) return;
|
|
6200
|
+
setIsLoadingHistory(true);
|
|
6201
|
+
try {
|
|
6202
|
+
const jwt = await configRef.current.getToken();
|
|
6203
|
+
if (!jwt) return;
|
|
6204
|
+
const params = new URLSearchParams({ limit: "50" });
|
|
6205
|
+
if (cursorRef.current) {
|
|
6206
|
+
params.set("cursor", cursorRef.current);
|
|
6207
|
+
}
|
|
6208
|
+
const res = await fetch(
|
|
6209
|
+
`${configRef.current.baseUrl}/rooms/${roomIdRef.current}/messages?${params}`,
|
|
6210
|
+
{ headers: { Authorization: `Bearer ${jwt}` } }
|
|
6211
|
+
);
|
|
6212
|
+
if (!res.ok) return;
|
|
6213
|
+
const data = await res.json();
|
|
6214
|
+
if (data.success && data.result.length > 0) {
|
|
6215
|
+
const newMessages = data.result.filter(
|
|
6216
|
+
(m) => !messageIdsRef.current.has(m.id)
|
|
6217
|
+
);
|
|
6218
|
+
for (const m of newMessages) {
|
|
6219
|
+
messageIdsRef.current.add(m.id);
|
|
6220
|
+
}
|
|
6221
|
+
setMessages((prev) => [...newMessages, ...prev]);
|
|
6222
|
+
cursorRef.current = data.nextCursor;
|
|
6223
|
+
setHasMoreHistory(data.nextCursor !== null);
|
|
6224
|
+
} else {
|
|
6225
|
+
setHasMoreHistory(false);
|
|
6226
|
+
}
|
|
6227
|
+
} catch {
|
|
6228
|
+
} finally {
|
|
6229
|
+
setIsLoadingHistory(false);
|
|
6230
|
+
}
|
|
6231
|
+
}, [isLoadingHistory, hasMoreHistory]);
|
|
6232
|
+
const sendMessage = useCallback(
|
|
6233
|
+
(content, attachmentIds) => {
|
|
6234
|
+
const ws = wsRef.current;
|
|
6235
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
6236
|
+
ws.send(
|
|
6237
|
+
JSON.stringify({
|
|
6238
|
+
type: "send_message",
|
|
6239
|
+
content,
|
|
6240
|
+
attachmentIds,
|
|
6241
|
+
clientMessageId: crypto.randomUUID()
|
|
6242
|
+
})
|
|
6243
|
+
);
|
|
6244
|
+
},
|
|
6245
|
+
[]
|
|
6246
|
+
);
|
|
6247
|
+
const sendTyping = useCallback((isTyping) => {
|
|
6248
|
+
const ws = wsRef.current;
|
|
6249
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
6250
|
+
ws.send(JSON.stringify({ type: "typing", isTyping }));
|
|
6251
|
+
}, []);
|
|
6252
|
+
useEffect(() => {
|
|
6253
|
+
loadMoreHistoryRef.current = loadMoreHistory;
|
|
6254
|
+
}, [loadMoreHistory]);
|
|
6255
|
+
const connect = useCallback(async () => {
|
|
6256
|
+
if (!roomIdRef.current || !mountedRef.current) return;
|
|
6257
|
+
let jwt = null;
|
|
6258
|
+
try {
|
|
6259
|
+
jwt = await configRef.current.getToken();
|
|
6260
|
+
} catch {
|
|
6261
|
+
if (mountedRef.current) {
|
|
6262
|
+
setError("Failed to get authentication token");
|
|
6263
|
+
}
|
|
6264
|
+
return;
|
|
6265
|
+
}
|
|
6266
|
+
if (!jwt || !mountedRef.current) return;
|
|
6267
|
+
const baseUrl = configRef.current.baseUrl;
|
|
6268
|
+
const wsBase = baseUrl.replace(/^http/, "ws");
|
|
6269
|
+
const wsUrl = `${wsBase}/realtime/chat?roomId=${encodeURIComponent(roomIdRef.current)}&token=${encodeURIComponent(jwt)}`;
|
|
6270
|
+
let ws;
|
|
6271
|
+
try {
|
|
6272
|
+
ws = new WebSocket(wsUrl);
|
|
6273
|
+
} catch (err) {
|
|
6274
|
+
if (mountedRef.current) {
|
|
6275
|
+
setError(
|
|
6276
|
+
err instanceof Error ? err.message : "WebSocket creation failed"
|
|
6277
|
+
);
|
|
6278
|
+
}
|
|
6279
|
+
return;
|
|
6280
|
+
}
|
|
6281
|
+
wsRef.current = ws;
|
|
6282
|
+
ws.onopen = () => {
|
|
6283
|
+
if (!mountedRef.current) {
|
|
6284
|
+
ws.close();
|
|
6285
|
+
return;
|
|
6286
|
+
}
|
|
6287
|
+
setIsConnected(true);
|
|
6288
|
+
setError(null);
|
|
6289
|
+
reconnectDelayRef.current = minReconnectDelay;
|
|
6290
|
+
ws.send(JSON.stringify({ type: "client_hello" }));
|
|
6291
|
+
pingTimerRef.current = setInterval(() => {
|
|
6292
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
6293
|
+
ws.send(JSON.stringify({ type: "ping", timestamp: Date.now() }));
|
|
6294
|
+
}
|
|
6295
|
+
}, pingInterval);
|
|
6296
|
+
};
|
|
6297
|
+
ws.onmessage = (event) => {
|
|
6298
|
+
if (!mountedRef.current) return;
|
|
6299
|
+
try {
|
|
6300
|
+
const msg = JSON.parse(event.data);
|
|
6301
|
+
switch (msg.type) {
|
|
6302
|
+
case "server_hello":
|
|
6303
|
+
void loadMoreHistoryRef.current();
|
|
6304
|
+
break;
|
|
6305
|
+
case "new_message": {
|
|
6306
|
+
const message = msg.message;
|
|
6307
|
+
if (!messageIdsRef.current.has(message.id)) {
|
|
6308
|
+
messageIdsRef.current.add(message.id);
|
|
6309
|
+
setMessages((prev) => [...prev, message]);
|
|
6310
|
+
}
|
|
6311
|
+
setTypingUsers(
|
|
6312
|
+
(prev) => prev.filter((u) => u.userId !== message.senderId)
|
|
6313
|
+
);
|
|
6314
|
+
break;
|
|
6315
|
+
}
|
|
6316
|
+
case "typing_indicator": {
|
|
6317
|
+
const userId = msg.userId;
|
|
6318
|
+
const userName = msg.userName ?? null;
|
|
6319
|
+
const isTyping = msg.isTyping;
|
|
6320
|
+
setTypingUsers((prev) => {
|
|
6321
|
+
if (isTyping) {
|
|
6322
|
+
if (prev.some((u) => u.userId === userId)) return prev;
|
|
6323
|
+
return [...prev, { userId, userName }];
|
|
6324
|
+
}
|
|
6325
|
+
return prev.filter((u) => u.userId !== userId);
|
|
6326
|
+
});
|
|
6327
|
+
break;
|
|
6328
|
+
}
|
|
6329
|
+
case "pong":
|
|
6330
|
+
break;
|
|
6331
|
+
case "error":
|
|
6332
|
+
setError(msg.message ?? "WebSocket error from server");
|
|
6333
|
+
break;
|
|
6334
|
+
}
|
|
6335
|
+
} catch {
|
|
6336
|
+
}
|
|
6337
|
+
};
|
|
6338
|
+
ws.onerror = () => {
|
|
6339
|
+
if (!mountedRef.current) return;
|
|
6340
|
+
setError("WebSocket connection error");
|
|
6341
|
+
};
|
|
6342
|
+
ws.onclose = () => {
|
|
6343
|
+
if (pingTimerRef.current) {
|
|
6344
|
+
clearInterval(pingTimerRef.current);
|
|
6345
|
+
pingTimerRef.current = null;
|
|
6346
|
+
}
|
|
6347
|
+
if (!mountedRef.current) return;
|
|
6348
|
+
setIsConnected(false);
|
|
6349
|
+
const delay = reconnectDelayRef.current;
|
|
6350
|
+
reconnectDelayRef.current = Math.min(delay * 2, maxReconnectDelay);
|
|
6351
|
+
reconnectTimerRef.current = setTimeout(() => {
|
|
6352
|
+
if (mountedRef.current && roomIdRef.current) {
|
|
6353
|
+
void connect();
|
|
6354
|
+
}
|
|
6355
|
+
}, delay);
|
|
6356
|
+
};
|
|
6357
|
+
}, [minReconnectDelay, maxReconnectDelay, pingInterval]);
|
|
6358
|
+
useEffect(() => {
|
|
6359
|
+
mountedRef.current = true;
|
|
6360
|
+
if (!roomId) {
|
|
6361
|
+
setMessages([]);
|
|
6362
|
+
setIsConnected(false);
|
|
6363
|
+
setError(null);
|
|
6364
|
+
setTypingUsers([]);
|
|
6365
|
+
setHasMoreHistory(true);
|
|
6366
|
+
cursorRef.current = null;
|
|
6367
|
+
messageIdsRef.current = /* @__PURE__ */ new Set();
|
|
6368
|
+
return;
|
|
6369
|
+
}
|
|
6370
|
+
setMessages([]);
|
|
6371
|
+
setTypingUsers([]);
|
|
6372
|
+
setHasMoreHistory(true);
|
|
6373
|
+
cursorRef.current = null;
|
|
6374
|
+
messageIdsRef.current = /* @__PURE__ */ new Set();
|
|
6375
|
+
reconnectDelayRef.current = minReconnectDelay;
|
|
6376
|
+
void connect();
|
|
6377
|
+
return () => {
|
|
6378
|
+
mountedRef.current = false;
|
|
6379
|
+
clearTimers();
|
|
6380
|
+
if (wsRef.current) {
|
|
6381
|
+
wsRef.current.close();
|
|
6382
|
+
wsRef.current = null;
|
|
6383
|
+
}
|
|
6384
|
+
};
|
|
6385
|
+
}, [roomId]);
|
|
6386
|
+
return {
|
|
6387
|
+
messages,
|
|
6388
|
+
sendMessage,
|
|
6389
|
+
sendTyping,
|
|
6390
|
+
isConnected,
|
|
6391
|
+
error,
|
|
6392
|
+
typingUsers,
|
|
6393
|
+
loadMoreHistory,
|
|
6394
|
+
hasMoreHistory,
|
|
6395
|
+
isLoadingHistory
|
|
6396
|
+
};
|
|
6397
|
+
}
|
|
6398
|
+
var roleColors = {
|
|
6399
|
+
client: "bg-blue-500/10 text-blue-600 dark:text-blue-400",
|
|
6400
|
+
seller: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
|
|
6401
|
+
org_manager: "bg-violet-500/10 text-violet-600 dark:text-violet-400",
|
|
6402
|
+
credit_agent: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
|
|
6403
|
+
admin: "bg-primary/10 text-primary"
|
|
6404
|
+
};
|
|
6405
|
+
function ChatRoomView({
|
|
6406
|
+
roomId,
|
|
6407
|
+
currentUserId,
|
|
6408
|
+
config,
|
|
6409
|
+
roomLabel,
|
|
6410
|
+
labels = {},
|
|
6411
|
+
roleLabel,
|
|
6412
|
+
className
|
|
6413
|
+
}) {
|
|
6414
|
+
const {
|
|
6415
|
+
messages,
|
|
6416
|
+
sendMessage,
|
|
6417
|
+
sendTyping,
|
|
6418
|
+
isConnected,
|
|
6419
|
+
error,
|
|
6420
|
+
typingUsers,
|
|
6421
|
+
loadMoreHistory,
|
|
6422
|
+
hasMoreHistory,
|
|
6423
|
+
isLoadingHistory
|
|
6424
|
+
} = useChatRoom(roomId, config);
|
|
6425
|
+
const [newMessage, setNewMessage] = useState("");
|
|
6426
|
+
const [pendingAttachments, setPendingAttachments] = useState([]);
|
|
6427
|
+
const scrollRef = useRef(null);
|
|
6428
|
+
const inputRef = useRef(null);
|
|
6429
|
+
const fileInputRef = useRef(null);
|
|
6430
|
+
const typingTimeoutRef = useRef(null);
|
|
6431
|
+
const prevMessageCountRef = useRef(0);
|
|
6432
|
+
useEffect(() => {
|
|
6433
|
+
const el = scrollRef.current;
|
|
6434
|
+
if (!el) return;
|
|
6435
|
+
if (messages.length > prevMessageCountRef.current) {
|
|
6436
|
+
el.scrollTop = el.scrollHeight;
|
|
6437
|
+
}
|
|
6438
|
+
prevMessageCountRef.current = messages.length;
|
|
6439
|
+
}, [messages.length]);
|
|
6440
|
+
const handleScroll = useCallback(() => {
|
|
6441
|
+
const el = scrollRef.current;
|
|
6442
|
+
if (!el) return;
|
|
6443
|
+
if (el.scrollTop < 50 && hasMoreHistory && !isLoadingHistory) {
|
|
6444
|
+
void loadMoreHistory();
|
|
6445
|
+
}
|
|
6446
|
+
}, [hasMoreHistory, isLoadingHistory, loadMoreHistory]);
|
|
6447
|
+
const handleTyping = useCallback(() => {
|
|
6448
|
+
sendTyping(true);
|
|
6449
|
+
if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
|
|
6450
|
+
typingTimeoutRef.current = setTimeout(() => sendTyping(false), 2e3);
|
|
6451
|
+
}, [sendTyping]);
|
|
6452
|
+
const handleSendMessage = useCallback(() => {
|
|
6453
|
+
const content = newMessage.trim();
|
|
6454
|
+
const attachmentIds = pendingAttachments.filter((a) => !a.uploading).map((a) => a.id);
|
|
6455
|
+
if (!content && attachmentIds.length === 0) return;
|
|
6456
|
+
sendMessage(content, attachmentIds.length > 0 ? attachmentIds : void 0);
|
|
6457
|
+
setNewMessage("");
|
|
6458
|
+
setPendingAttachments([]);
|
|
6459
|
+
sendTyping(false);
|
|
6460
|
+
if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
|
|
6461
|
+
inputRef.current?.focus();
|
|
6462
|
+
}, [newMessage, pendingAttachments, sendMessage, sendTyping]);
|
|
6463
|
+
const handleKeyDown = (e) => {
|
|
6464
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
6465
|
+
e.preventDefault();
|
|
6466
|
+
handleSendMessage();
|
|
6467
|
+
}
|
|
6468
|
+
};
|
|
6469
|
+
const handleFileSelect = async (e) => {
|
|
6470
|
+
const files = e.target.files;
|
|
6471
|
+
if (!files || files.length === 0) return;
|
|
6472
|
+
for (const file of Array.from(files)) {
|
|
6473
|
+
const tempId = crypto.randomUUID();
|
|
6474
|
+
const pending = {
|
|
6475
|
+
id: tempId,
|
|
6476
|
+
fileName: file.name,
|
|
6477
|
+
fileSize: file.size,
|
|
6478
|
+
mimeType: file.type || "application/octet-stream",
|
|
6479
|
+
uploading: true
|
|
6480
|
+
};
|
|
6481
|
+
setPendingAttachments((prev) => [...prev, pending]);
|
|
6482
|
+
try {
|
|
6483
|
+
const jwt = await config.getToken();
|
|
6484
|
+
if (!jwt) throw new Error("No auth token");
|
|
6485
|
+
const formData = new FormData();
|
|
6486
|
+
formData.append("file", file);
|
|
6487
|
+
const res = await fetch(
|
|
6488
|
+
`${config.baseUrl}/rooms/${roomId}/attachments/upload`,
|
|
6489
|
+
{
|
|
6490
|
+
method: "POST",
|
|
6491
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
6492
|
+
body: formData
|
|
6493
|
+
}
|
|
6494
|
+
);
|
|
6495
|
+
if (!res.ok) throw new Error("Upload failed");
|
|
6496
|
+
const data = await res.json();
|
|
6497
|
+
setPendingAttachments(
|
|
6498
|
+
(prev) => prev.map(
|
|
6499
|
+
(a) => a.id === tempId ? { ...a, id: data.result.id, uploading: false } : a
|
|
6500
|
+
)
|
|
6501
|
+
);
|
|
6502
|
+
} catch {
|
|
6503
|
+
setPendingAttachments((prev) => prev.filter((a) => a.id !== tempId));
|
|
6504
|
+
}
|
|
6505
|
+
}
|
|
6506
|
+
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
6507
|
+
};
|
|
6508
|
+
const removeAttachment = (id) => {
|
|
6509
|
+
setPendingAttachments((prev) => prev.filter((a) => a.id !== id));
|
|
6510
|
+
};
|
|
6511
|
+
const formatTime = (timestamp) => {
|
|
6512
|
+
return new Date(timestamp).toLocaleTimeString([], {
|
|
6513
|
+
hour: "2-digit",
|
|
6514
|
+
minute: "2-digit"
|
|
6515
|
+
});
|
|
6516
|
+
};
|
|
6517
|
+
const formatDate = (timestamp) => {
|
|
6518
|
+
const date = new Date(timestamp);
|
|
6519
|
+
const today = /* @__PURE__ */ new Date();
|
|
6520
|
+
const yesterday = new Date(today);
|
|
6521
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
6522
|
+
if (date.toDateString() === today.toDateString())
|
|
6523
|
+
return labels.today ?? "Today";
|
|
6524
|
+
if (date.toDateString() === yesterday.toDateString())
|
|
6525
|
+
return labels.yesterday ?? "Yesterday";
|
|
6526
|
+
return date.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
6527
|
+
};
|
|
6528
|
+
const groupedMessages = messages.reduce(
|
|
6529
|
+
(groups, message) => {
|
|
6530
|
+
const date = formatDate(message.createdAt);
|
|
6531
|
+
if (!groups[date]) groups[date] = [];
|
|
6532
|
+
groups[date].push(message);
|
|
6533
|
+
return groups;
|
|
6534
|
+
},
|
|
6535
|
+
{}
|
|
6536
|
+
);
|
|
6537
|
+
const formatFileSize = (bytes) => {
|
|
6538
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
6539
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
6540
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
6541
|
+
};
|
|
6542
|
+
const getAttachmentUrl = (attachmentId) => `${config.baseUrl}/rooms/${roomId}/attachments/${attachmentId}`;
|
|
6543
|
+
const getRoleLabel = (role) => roleLabel ? roleLabel(role) : role;
|
|
6544
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex h-full flex-col", className), children: [
|
|
6545
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 flex items-center justify-between border-b px-4 py-3", children: [
|
|
6546
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
6547
|
+
/* @__PURE__ */ jsx(MessageSquare, { className: "h-4 w-4 text-primary", "aria-hidden": "true" }),
|
|
6548
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: roomLabel ?? "Chat" })
|
|
6549
|
+
] }),
|
|
6550
|
+
/* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
6551
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: isConnected ? /* @__PURE__ */ jsx(Wifi, { className: "h-3.5 w-3.5 text-green-500" }) : /* @__PURE__ */ jsx(WifiOff, { className: "h-3.5 w-3.5 text-destructive" }) }) }),
|
|
6552
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: isConnected ? "Connected" : error ?? "Reconnecting..." })
|
|
6553
|
+
] }) })
|
|
6554
|
+
] }),
|
|
6555
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col overflow-hidden", children: [
|
|
6556
|
+
/* @__PURE__ */ jsxs(
|
|
6557
|
+
"div",
|
|
6558
|
+
{
|
|
6559
|
+
ref: scrollRef,
|
|
6560
|
+
onScroll: handleScroll,
|
|
6561
|
+
className: "flex-1 overflow-y-auto p-3",
|
|
6562
|
+
children: [
|
|
6563
|
+
isLoadingHistory && /* @__PURE__ */ jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) }),
|
|
6564
|
+
messages.length === 0 && !isLoadingHistory ? /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col items-center justify-center py-8 text-center", children: [
|
|
6565
|
+
/* @__PURE__ */ jsx(
|
|
6566
|
+
MessageSquare,
|
|
6567
|
+
{
|
|
6568
|
+
className: "mb-3 h-10 w-10 text-muted-foreground/50",
|
|
6569
|
+
"aria-hidden": "true"
|
|
6570
|
+
}
|
|
6571
|
+
),
|
|
6572
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: labels.noMessages ?? "No messages yet" })
|
|
6573
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "space-y-4", children: Object.entries(groupedMessages).map(([date, dateMessages]) => /* @__PURE__ */ jsxs("div", { children: [
|
|
6574
|
+
/* @__PURE__ */ jsxs("div", { className: "my-3 flex items-center gap-3", children: [
|
|
6575
|
+
/* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-border" }),
|
|
6576
|
+
/* @__PURE__ */ jsx("span", { className: "px-2 text-xs text-muted-foreground", children: date }),
|
|
6577
|
+
/* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-border" })
|
|
6578
|
+
] }),
|
|
6579
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-3", children: dateMessages.map((message) => {
|
|
6580
|
+
const isCurrentUser = message.senderId === currentUserId;
|
|
6581
|
+
return /* @__PURE__ */ jsxs(
|
|
6582
|
+
"div",
|
|
6583
|
+
{
|
|
6584
|
+
className: cn(
|
|
6585
|
+
"flex gap-2",
|
|
6586
|
+
isCurrentUser ? "flex-row-reverse" : "flex-row"
|
|
6587
|
+
),
|
|
6588
|
+
children: [
|
|
6589
|
+
/* @__PURE__ */ jsx(
|
|
6590
|
+
"div",
|
|
6591
|
+
{
|
|
6592
|
+
className: cn(
|
|
6593
|
+
"flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full text-xs font-medium",
|
|
6594
|
+
roleColors[message.senderRole] ?? roleColors.client
|
|
6595
|
+
),
|
|
6596
|
+
children: (message.senderName ?? message.senderId).split(" ").map((n) => n[0]).join("").slice(0, 2).toUpperCase()
|
|
6597
|
+
}
|
|
6598
|
+
),
|
|
6599
|
+
/* @__PURE__ */ jsxs(
|
|
6600
|
+
"div",
|
|
6601
|
+
{
|
|
6602
|
+
className: cn(
|
|
6603
|
+
"flex max-w-[80%] flex-col",
|
|
6604
|
+
isCurrentUser ? "items-end" : "items-start"
|
|
6605
|
+
),
|
|
6606
|
+
children: [
|
|
6607
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-0.5 flex items-center gap-1.5", children: [
|
|
6608
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground", children: message.senderName ?? message.senderId.slice(0, 8) }),
|
|
6609
|
+
/* @__PURE__ */ jsx(
|
|
6610
|
+
"span",
|
|
6611
|
+
{
|
|
6612
|
+
className: cn(
|
|
6613
|
+
"rounded px-1 py-0.5 text-[10px] font-medium",
|
|
6614
|
+
roleColors[message.senderRole] ?? ""
|
|
6615
|
+
),
|
|
6616
|
+
children: getRoleLabel(message.senderRole)
|
|
6617
|
+
}
|
|
6618
|
+
),
|
|
6619
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: formatTime(message.createdAt) })
|
|
6620
|
+
] }),
|
|
6621
|
+
message.content && /* @__PURE__ */ jsx(
|
|
6622
|
+
"div",
|
|
6623
|
+
{
|
|
6624
|
+
className: cn(
|
|
6625
|
+
"rounded-xl px-3 py-2 text-sm",
|
|
6626
|
+
isCurrentUser ? "rounded-br-sm bg-primary text-primary-foreground" : "rounded-bl-sm bg-muted text-foreground"
|
|
6627
|
+
),
|
|
6628
|
+
children: message.content
|
|
6629
|
+
}
|
|
6630
|
+
),
|
|
6631
|
+
message.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-1 flex flex-col gap-1", children: message.attachments.map(
|
|
6632
|
+
(att) => att.mimeType.startsWith("image/") ? /* @__PURE__ */ jsx(
|
|
6633
|
+
"a",
|
|
6634
|
+
{
|
|
6635
|
+
href: getAttachmentUrl(att.id),
|
|
6636
|
+
target: "_blank",
|
|
6637
|
+
rel: "noopener noreferrer",
|
|
6638
|
+
className: "block max-w-[200px] overflow-hidden rounded-lg border",
|
|
6639
|
+
children: /* @__PURE__ */ jsx(
|
|
6640
|
+
"img",
|
|
6641
|
+
{
|
|
6642
|
+
src: getAttachmentUrl(att.id),
|
|
6643
|
+
alt: att.fileName,
|
|
6644
|
+
className: "h-auto w-full object-cover",
|
|
6645
|
+
loading: "lazy"
|
|
6646
|
+
}
|
|
6647
|
+
)
|
|
6648
|
+
},
|
|
6649
|
+
att.id
|
|
6650
|
+
) : /* @__PURE__ */ jsxs(
|
|
6651
|
+
"a",
|
|
6652
|
+
{
|
|
6653
|
+
href: getAttachmentUrl(att.id),
|
|
6654
|
+
target: "_blank",
|
|
6655
|
+
rel: "noopener noreferrer",
|
|
6656
|
+
className: "flex items-center gap-2 rounded-lg border bg-muted/50 px-2 py-1.5 text-xs transition-colors hover:bg-muted",
|
|
6657
|
+
children: [
|
|
6658
|
+
/* @__PURE__ */ jsx(FileIcon, { className: "h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" }),
|
|
6659
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
6660
|
+
/* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: att.fileName }),
|
|
6661
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: formatFileSize(att.fileSize) })
|
|
6662
|
+
] }),
|
|
6663
|
+
/* @__PURE__ */ jsx(Download, { className: "h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" })
|
|
6664
|
+
]
|
|
6665
|
+
},
|
|
6666
|
+
att.id
|
|
6667
|
+
)
|
|
6668
|
+
) })
|
|
6669
|
+
]
|
|
6670
|
+
}
|
|
6671
|
+
)
|
|
6672
|
+
]
|
|
6673
|
+
},
|
|
6674
|
+
message.id
|
|
6675
|
+
);
|
|
6676
|
+
}) })
|
|
6677
|
+
] }, date)) })
|
|
6678
|
+
]
|
|
6679
|
+
}
|
|
6680
|
+
),
|
|
6681
|
+
typingUsers.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 px-3 py-1", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground italic", children: [
|
|
6682
|
+
typingUsers.map((u) => u.userName ?? "Someone").join(", "),
|
|
6683
|
+
" ",
|
|
6684
|
+
typingUsers.length === 1 ? "is" : "are",
|
|
6685
|
+
" typing..."
|
|
6686
|
+
] }) }),
|
|
6687
|
+
pendingAttachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 border-t px-3 py-1.5", children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: pendingAttachments.map((att) => /* @__PURE__ */ jsxs(
|
|
6688
|
+
"div",
|
|
6689
|
+
{
|
|
6690
|
+
className: "flex items-center gap-1.5 rounded bg-muted px-2 py-0.5 text-xs",
|
|
6691
|
+
children: [
|
|
6692
|
+
att.uploading ? /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsx(FileIcon, { className: "h-3 w-3" }),
|
|
6693
|
+
/* @__PURE__ */ jsx("span", { className: "max-w-[100px] truncate", children: att.fileName }),
|
|
6694
|
+
/* @__PURE__ */ jsx(
|
|
6695
|
+
"button",
|
|
6696
|
+
{
|
|
6697
|
+
type: "button",
|
|
6698
|
+
onClick: () => removeAttachment(att.id),
|
|
6699
|
+
className: "text-muted-foreground hover:text-foreground",
|
|
6700
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
|
|
6701
|
+
}
|
|
6702
|
+
)
|
|
6703
|
+
]
|
|
6704
|
+
},
|
|
6705
|
+
att.id
|
|
6706
|
+
)) }) }),
|
|
6707
|
+
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0 border-t p-3", children: /* @__PURE__ */ jsxs(
|
|
6708
|
+
"form",
|
|
6709
|
+
{
|
|
6710
|
+
onSubmit: (e) => {
|
|
6711
|
+
e.preventDefault();
|
|
6712
|
+
handleSendMessage();
|
|
6713
|
+
},
|
|
6714
|
+
className: "flex gap-2",
|
|
6715
|
+
children: [
|
|
6716
|
+
/* @__PURE__ */ jsx(
|
|
6717
|
+
"input",
|
|
6718
|
+
{
|
|
6719
|
+
ref: fileInputRef,
|
|
6720
|
+
type: "file",
|
|
6721
|
+
multiple: true,
|
|
6722
|
+
className: "hidden",
|
|
6723
|
+
onChange: handleFileSelect
|
|
6724
|
+
}
|
|
6725
|
+
),
|
|
6726
|
+
/* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
6727
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
6728
|
+
Button,
|
|
6729
|
+
{
|
|
6730
|
+
type: "button",
|
|
6731
|
+
variant: "ghost",
|
|
6732
|
+
size: "icon",
|
|
6733
|
+
className: "h-9 w-9 flex-shrink-0",
|
|
6734
|
+
onClick: () => fileInputRef.current?.click(),
|
|
6735
|
+
"aria-label": labels.attachFile ?? "Attach file",
|
|
6736
|
+
children: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" })
|
|
6737
|
+
}
|
|
6738
|
+
) }),
|
|
6739
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: labels.attachFile ?? "Attach file" })
|
|
6740
|
+
] }) }),
|
|
6741
|
+
/* @__PURE__ */ jsx(
|
|
6742
|
+
"textarea",
|
|
6743
|
+
{
|
|
6744
|
+
ref: inputRef,
|
|
6745
|
+
value: newMessage,
|
|
6746
|
+
onChange: (e) => {
|
|
6747
|
+
setNewMessage(e.target.value);
|
|
6748
|
+
handleTyping();
|
|
6749
|
+
},
|
|
6750
|
+
onKeyDown: handleKeyDown,
|
|
6751
|
+
placeholder: labels.placeholder ?? "Type a message...",
|
|
6752
|
+
className: "h-9 min-h-[36px] max-h-[100px] flex-1 resize-none rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
6753
|
+
rows: 1
|
|
6754
|
+
}
|
|
6755
|
+
),
|
|
6756
|
+
/* @__PURE__ */ jsx(
|
|
6757
|
+
Button,
|
|
6758
|
+
{
|
|
6759
|
+
type: "submit",
|
|
6760
|
+
size: "sm",
|
|
6761
|
+
className: "h-9",
|
|
6762
|
+
disabled: !newMessage.trim() && pendingAttachments.filter((a) => !a.uploading).length === 0,
|
|
6763
|
+
"aria-label": labels.send ?? "Send",
|
|
6764
|
+
children: /* @__PURE__ */ jsx(Send, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
6765
|
+
}
|
|
6766
|
+
)
|
|
6767
|
+
]
|
|
6768
|
+
}
|
|
6769
|
+
) })
|
|
6770
|
+
] })
|
|
6771
|
+
] });
|
|
6772
|
+
}
|
|
6773
|
+
var roomTypeLabels = {
|
|
6774
|
+
client_agent: "Client \u2194 Agent",
|
|
6775
|
+
dealer_agent: "Dealer \u2194 Agent"
|
|
6776
|
+
};
|
|
6777
|
+
function ChatSidebar({
|
|
6778
|
+
currentUserId,
|
|
6779
|
+
config,
|
|
6780
|
+
labels = {},
|
|
6781
|
+
roleLabel,
|
|
6782
|
+
roomTypeLabel,
|
|
6783
|
+
className
|
|
6784
|
+
}) {
|
|
6785
|
+
const {
|
|
6786
|
+
isOpen,
|
|
6787
|
+
view,
|
|
6788
|
+
conversations,
|
|
6789
|
+
isLoading,
|
|
6790
|
+
activeCaseId,
|
|
6791
|
+
activeRoomId,
|
|
6792
|
+
close,
|
|
6793
|
+
selectRoom,
|
|
6794
|
+
back
|
|
6795
|
+
} = useChatSidebar();
|
|
6796
|
+
useEffect(() => {
|
|
6797
|
+
if (!isOpen || typeof window === "undefined") return;
|
|
6798
|
+
const mq = window.matchMedia("(max-width: 639px)");
|
|
6799
|
+
const apply = () => {
|
|
6800
|
+
if (mq.matches) document.body.style.overflow = "hidden";
|
|
6801
|
+
else document.body.style.overflow = "";
|
|
6802
|
+
};
|
|
6803
|
+
apply();
|
|
6804
|
+
mq.addEventListener("change", apply);
|
|
6805
|
+
return () => {
|
|
6806
|
+
mq.removeEventListener("change", apply);
|
|
6807
|
+
document.body.style.overflow = "";
|
|
6808
|
+
};
|
|
6809
|
+
}, [isOpen]);
|
|
6810
|
+
if (!isOpen) return null;
|
|
6811
|
+
const getRoomTypeLabel = (type) => {
|
|
6812
|
+
if (roomTypeLabel) return roomTypeLabel(type);
|
|
6813
|
+
return roomTypeLabels[type] ?? type;
|
|
6814
|
+
};
|
|
6815
|
+
const activeConversation = activeCaseId ? conversations.find((c) => c.caseId === activeCaseId) : null;
|
|
6816
|
+
const panel = /* @__PURE__ */ jsx(
|
|
6817
|
+
"div",
|
|
6818
|
+
{
|
|
6819
|
+
className: cn(
|
|
6820
|
+
"flex min-h-0 flex-col bg-background",
|
|
6821
|
+
// Below sm (~640px): fullscreen overlay drawer above sticky app headers (often z-50)
|
|
6822
|
+
"fixed inset-0 z-[100] w-full max-w-none border-0 shadow-none",
|
|
6823
|
+
// sm+: docked right sidebar in the app shell flex row
|
|
6824
|
+
"sm:relative sm:inset-auto sm:z-auto sm:h-full sm:w-[360px] sm:max-w-[360px] sm:shrink-0 sm:border-l sm:shadow-none xl:w-[400px] xl:max-w-[400px]",
|
|
6825
|
+
className
|
|
6826
|
+
),
|
|
6827
|
+
children: view === "list" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6828
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [
|
|
6829
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
6830
|
+
/* @__PURE__ */ jsx(MessageSquare, { className: "h-4 w-4 text-primary" }),
|
|
6831
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: labels.title ?? "Chat" })
|
|
6832
|
+
] }),
|
|
6833
|
+
/* @__PURE__ */ jsx(
|
|
6834
|
+
Button,
|
|
6835
|
+
{
|
|
6836
|
+
variant: "ghost",
|
|
6837
|
+
size: "icon",
|
|
6838
|
+
className: "h-7 w-7",
|
|
6839
|
+
onClick: close,
|
|
6840
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
6841
|
+
}
|
|
6842
|
+
)
|
|
6843
|
+
] }),
|
|
6844
|
+
/* @__PURE__ */ jsx(ScrollArea, { className: "flex-1", children: isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }) }) : conversations.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center px-4", children: [
|
|
6845
|
+
/* @__PURE__ */ jsx(MessageSquare, { className: "mb-3 h-8 w-8 text-muted-foreground/50" }),
|
|
6846
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: labels.noConversations ?? "No conversations yet" })
|
|
6847
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "py-1", children: conversations.map((conv) => /* @__PURE__ */ jsxs("div", { className: "px-2", children: [
|
|
6848
|
+
/* @__PURE__ */ jsxs("div", { className: "px-2 pt-3 pb-1", children: [
|
|
6849
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
6850
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-foreground", children: conv.caseNumber }),
|
|
6851
|
+
/* @__PURE__ */ jsx("span", { className: "rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground", children: conv.caseStatus })
|
|
6852
|
+
] }),
|
|
6853
|
+
conv.clientName && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground truncate", children: conv.clientName })
|
|
6854
|
+
] }),
|
|
6855
|
+
conv.rooms.map((room) => /* @__PURE__ */ jsxs(
|
|
6856
|
+
"button",
|
|
6857
|
+
{
|
|
6858
|
+
type: "button",
|
|
6859
|
+
onClick: () => selectRoom(room.id, conv.caseId),
|
|
6860
|
+
className: cn(
|
|
6861
|
+
"flex w-full items-center justify-between rounded-md px-2 py-2 text-left transition-colors hover:bg-accent",
|
|
6862
|
+
activeRoomId === room.id && "bg-accent"
|
|
6863
|
+
),
|
|
6864
|
+
children: [
|
|
6865
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
6866
|
+
/* @__PURE__ */ jsx(MessageSquare, { className: "h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" }),
|
|
6867
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm truncate", children: getRoomTypeLabel(room.type) })
|
|
6868
|
+
] }),
|
|
6869
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
|
|
6870
|
+
room.lastMessageAt && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: formatRelativeTime(room.lastMessageAt) }),
|
|
6871
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
6872
|
+
] })
|
|
6873
|
+
]
|
|
6874
|
+
},
|
|
6875
|
+
room.id
|
|
6876
|
+
))
|
|
6877
|
+
] }, conv.caseId)) }) })
|
|
6878
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6879
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b px-3 py-2", children: [
|
|
6880
|
+
/* @__PURE__ */ jsx(
|
|
6881
|
+
Button,
|
|
6882
|
+
{
|
|
6883
|
+
variant: "ghost",
|
|
6884
|
+
size: "icon",
|
|
6885
|
+
className: "h-7 w-7",
|
|
6886
|
+
onClick: back,
|
|
6887
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
|
|
6888
|
+
}
|
|
6889
|
+
),
|
|
6890
|
+
activeConversation && /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
6891
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: activeConversation.caseNumber }),
|
|
6892
|
+
activeConversation.clientName && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground truncate", children: activeConversation.clientName })
|
|
6893
|
+
] }),
|
|
6894
|
+
/* @__PURE__ */ jsx(
|
|
6895
|
+
Button,
|
|
6896
|
+
{
|
|
6897
|
+
variant: "ghost",
|
|
6898
|
+
size: "icon",
|
|
6899
|
+
className: "h-7 w-7",
|
|
6900
|
+
onClick: close,
|
|
6901
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
6902
|
+
}
|
|
6903
|
+
)
|
|
6904
|
+
] }),
|
|
6905
|
+
activeRoomId && /* @__PURE__ */ jsx(
|
|
6906
|
+
ChatRoomView,
|
|
6907
|
+
{
|
|
6908
|
+
roomId: activeRoomId,
|
|
6909
|
+
currentUserId,
|
|
6910
|
+
config,
|
|
6911
|
+
labels,
|
|
6912
|
+
roleLabel,
|
|
6913
|
+
className: "flex-1"
|
|
6914
|
+
}
|
|
6915
|
+
)
|
|
6916
|
+
] })
|
|
6917
|
+
}
|
|
6918
|
+
);
|
|
6919
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6920
|
+
/* @__PURE__ */ jsx(
|
|
6921
|
+
"div",
|
|
6922
|
+
{
|
|
6923
|
+
className: "fixed inset-0 z-[90] bg-black/50 sm:hidden",
|
|
6924
|
+
"aria-hidden": true,
|
|
6925
|
+
onClick: close
|
|
6926
|
+
}
|
|
6927
|
+
),
|
|
6928
|
+
panel
|
|
6929
|
+
] });
|
|
6930
|
+
}
|
|
6931
|
+
function formatRelativeTime(dateStr) {
|
|
6932
|
+
const date = new Date(dateStr);
|
|
6933
|
+
const now = /* @__PURE__ */ new Date();
|
|
6934
|
+
const diffMs = now.getTime() - date.getTime();
|
|
6935
|
+
const diffMin = Math.floor(diffMs / 6e4);
|
|
6936
|
+
if (diffMin < 1) return "now";
|
|
6937
|
+
if (diffMin < 60) return `${diffMin}m`;
|
|
6938
|
+
const diffH = Math.floor(diffMin / 60);
|
|
6939
|
+
if (diffH < 24) return `${diffH}h`;
|
|
6940
|
+
const diffD = Math.floor(diffH / 24);
|
|
6941
|
+
if (diffD < 7) return `${diffD}d`;
|
|
6942
|
+
return date.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
6943
|
+
}
|
|
5383
6944
|
|
|
5384
|
-
export { AvatarEditor, AvatarEditorDialog, Button, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, LanguageContext, LanguageSwitcher, NotificationsContext, NotificationsWidget, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, ScrollArea, ScrollBar, Slider, ThemeSwitcher, Toggle, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, buttonVariants, cn, defaultLanguages, toggleVariants, useLanguageContext, useNotificationsContext };
|
|
6945
|
+
export { AvatarEditor, AvatarEditorDialog, Button, CalendarContext, CalendarSubscribeButton, CalendarView, CalendarWidget, ChatRoomView, ChatSidebar, ChatSidebarContext, ChatSidebarProvider, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EventDialog, EventRsvpBadge, LanguageContext, LanguageSwitcher, MiniCalendar, NotificationsContext, NotificationsWidget, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, ScrollArea, ScrollBar, Slider, ThemeSwitcher, Toggle, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UpcomingEvents, buttonVariants, cn, defaultLanguages, toggleVariants, useCalendarContext, useChatRoom, useChatSidebar, useLanguageContext, useNotificationsContext };
|
|
5385
6946
|
//# sourceMappingURL=index.js.map
|
|
5386
6947
|
//# sourceMappingURL=index.js.map
|