"use client"; import { CalendarDays, Plus, Trash2, Users, Video } from "lucide-react"; import { useSearchParams } from "next/navigation"; import { type FormEvent, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { MeetingRoom } from "@/components/meetings/meeting-room"; import { ScheduleMeetingDialog } from "@/components/meetings/schedule-meeting-dialog"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; import { Dialog, DialogClose, DialogDescription, DialogFooter, DialogHeader, DialogPanel, DialogPopup, DialogTitle, } from "@/components/ui/dialog"; import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, } from "@/components/ui/empty"; import { Input } from "@/components/ui/input"; import { authClient } from "@/lib/auth-client"; import { createMeetingRoom, deleteMeetingRoom, listMeetingEvents, listMeetingRooms, type MeetingRoom as Room, type ScheduledMeeting, } from "@/lib/meetings"; import { getSocket } from "@/lib/socket"; import { notify } from "@/lib/toast"; import { cn } from "@/lib/utils"; type Tab = "rooms" | "calendar"; // Local YYYY-MM-DD key for a Date (no UTC drift), matching how meetings store date. function keyOf(date: Date): string { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; } export function MeetingsView() { const { t } = useTranslation(); const { data: session } = authClient.useSession(); const selfName = session?.user?.name ?? ""; const searchParams = useSearchParams(); const deepLinkRoom = searchParams.get("room"); // ?with= from the Messages inbox "call" button — open the scheduler // pre-targeted at that person so the user can connect with them. const deepLinkWith = searchParams.get("with"); const openedDeepLink = useRef(null); const openedWith = useRef(null); const [tab, setTab] = useState("rooms"); // Rooms / live calls. const [rooms, setRooms] = useState([]); const [activeRoom, setActiveRoom] = useState(null); const [presence, setPresence] = useState>({}); const [createOpen, setCreateOpen] = useState(false); const [newName, setNewName] = useState(""); const [creating, setCreating] = useState(false); const [roomToDelete, setRoomToDelete] = useState(null); const [deleting, setDeleting] = useState(false); // Scheduled meetings (calendar). const [events, setEvents] = useState([]); const [selectedDay, setSelectedDay] = useState(new Date()); const [scheduleOpen, setScheduleOpen] = useState(false); useEffect(() => { listMeetingRooms() .then(setRooms) .catch(() => {}); }, []); const loadEvents = () => { listMeetingEvents() .then(setEvents) .catch(() => {}); }; useEffect(loadEvents, []); // Live room occupancy. useEffect(() => { const socket = getSocket(); const onPresence = ({ roomId, count }: { roomId: string; count: number }) => { setPresence((prev) => ({ ...prev, [roomId]: count })); }; socket.on("call:presence", onPresence); return () => { socket.off("call:presence", onPresence); }; }, []); // Auto-join a room deep-linked from an invite (?room=). useEffect(() => { if (!deepLinkRoom || rooms.length === 0) return; if (openedDeepLink.current === deepLinkRoom) return; const room = rooms.find((r) => r.id === deepLinkRoom); if (!room) return; openedDeepLink.current = deepLinkRoom; setTab("rooms"); setActiveRoom(room); }, [deepLinkRoom, rooms]); // Open the scheduler pre-targeted at a person (?with=) from the inbox. useEffect(() => { if (!deepLinkWith || openedWith.current === deepLinkWith) return; openedWith.current = deepLinkWith; setTab("calendar"); setScheduleOpen(true); }, [deepLinkWith]); const createRoom = async (event: FormEvent) => { event.preventDefault(); const name = newName.trim(); if (!name || creating) return; setCreating(true); try { const room = await createMeetingRoom(name); setRooms((prev) => [...prev, room]); setCreateOpen(false); setNewName(""); setActiveRoom(room); } catch { notify.error(t("meetings.createFailedTitle"), t("meetings.createFailedBody")); } finally { setCreating(false); } }; const confirmDeleteRoom = async () => { if (!roomToDelete || deleting) return; setDeleting(true); try { await deleteMeetingRoom(roomToDelete.id); setRooms((prev) => prev.filter((r) => r.id !== roomToDelete.id)); if (activeRoom?.id === roomToDelete.id) setActiveRoom(null); setRoomToDelete(null); } catch { notify.error(t("meetings.deleteFailedTitle"), t("meetings.deleteFailedBody")); } finally { setDeleting(false); } }; const meetingDays = useMemo( () => events.map((e) => new Date(`${e.date}T00:00:00`)), [events], ); const dayEvents = useMemo( () => events .filter((e) => e.date === keyOf(selectedDay)) .sort((a, b) => a.time.localeCompare(b.time)), [events, selectedDay], ); // Midnight today — used to disable past calendar dates and filter "upcoming". const today = useMemo(() => { const d = new Date(); d.setHours(0, 0, 0, 0); return d; }, []); // Next few meetings from today onward, soonest first. const upcoming = useMemo(() => { const now = new Date(); return events .filter((e) => new Date(`${e.date}T${e.time}`) >= now) .sort((a, b) => `${a.date}T${a.time}`.localeCompare(`${b.date}T${b.time}`), ) .slice(0, 4); }, [events]); return (
{/* Header: Rooms / Calendar tabs */}
{tab === "rooms" ? (
{/* Room (channel) list */} {/* Live call or lobby */}
{activeRoom ? ( setActiveRoom(null)} roomId={activeRoom.id} roomName={activeRoom.name} selfName={selfName} /> ) : (
{t("meetings.emptyTitle")} {t("meetings.emptyDescription")}
)}
) : ( // Calendar tab
d && setSelectedDay(d)} required selected={selectedDay} />
{t("meetings.upcoming.title")} {upcoming.length === 0 ? (

{t("meetings.upcoming.empty")}

) : (
{upcoming.map((e) => ( ))}
)}

{selectedDay.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", })}

{dayEvents.length === 0 ? ( {t("meetings.calendarEmpty")} {t("meetings.calendarEmptyHint")} ) : ( dayEvents.map((e) => (
{e.time}
{e.title} {e.participantNames.join(", ")}
{e.participantNames.length}
)) )}
)} {/* Create room dialog */} {t("meetings.newRoom")} {t("meetings.newRoomDescription")}
setNewName(e.target.value)} placeholder={t("meetings.roomNamePlaceholder")} value={newName} /> }> {t("meetings.cancel")}
!open && setRoomToDelete(null)} open={roomToDelete !== null} > {t("meetings.deleteRoomConfirmTitle")} {t("meetings.deleteRoomConfirmBody", { name: roomToDelete?.name ?? "", })} }> {t("meetings.cancel")}
); }