import React, { useState } from "react"; import { Box, Table, Link, Flex, IconButton, Text, Spinner, Badge, VStack, Icon, } from "@chakra-ui/react"; import { LuLink, LuRefreshCw } from "react-icons/lu"; import { FaCalendarAlt } from "react-icons/fa"; import type { components } from "../../../reflector-api"; import { useRoomActiveMeetings, useRoomUpcomingMeetings, useRoomIcsSync, } from "../../../lib/apiHooks"; type Room = components["schemas"]["Room"]; type Meeting = components["schemas"]["Meeting"]; type CalendarEventResponse = components["schemas"]["CalendarEventResponse"]; import { RoomActionsMenu } from "./RoomActionsMenu"; import { MEETING_DEFAULT_TIME_MINUTES } from "../../../[roomName]/[meetingId]/constants"; import { NonEmptyString, parseNonEmptyString } from "../../../lib/utils"; // Custom icon component that combines calendar and refresh icons const CalendarSyncIcon = () => ( ); interface RoomTableProps { rooms: Room[]; linkCopied: string; onCopyUrl: (roomName: NonEmptyString) => void; onEdit: (roomId: string, roomData: any) => void; onDelete: (roomId: string) => void; loading?: boolean; } const getRoomModeDisplay = (mode: string): string => { switch (mode) { case "normal": return "2-4 people"; case "group": return "2-200 people"; default: return mode; } }; const getRecordingDisplay = (type: string, trigger: string): string => { if (type === "none") return "-"; if (type === "local") return "Local"; if (type === "cloud") { switch (trigger) { case "none": return "Cloud (None)"; case "prompt": return "Cloud (Prompt)"; case "automatic-2nd-participant": return "Cloud (Auto)"; default: return `Cloud (${trigger})`; } } return type; }; const getZulipDisplay = ( autoPost: boolean, stream: string, topic: string, ): string => { if (!autoPost) return "-"; if (stream && topic) return `${stream} > ${topic}`; if (stream) return stream; return "Enabled"; }; function MeetingStatus({ roomName }: { roomName: string }) { const activeMeetingsQuery = useRoomActiveMeetings(roomName); const upcomingMeetingsQuery = useRoomUpcomingMeetings(roomName); const activeMeetings = activeMeetingsQuery.data || []; const upcomingMeetings = upcomingMeetingsQuery.data || []; if (activeMeetingsQuery.isLoading || upcomingMeetingsQuery.isLoading) { return ; } if (activeMeetings.length > 0) { const meeting = activeMeetings[0]; const title = String( meeting.calendar_metadata?.["title"] || "Active Meeting", ); return ( {title} {meeting.num_clients} participants ); } if (upcomingMeetings.length > 0) { const event = upcomingMeetings[0]; const startTime = new Date(event.start_time); const now = new Date(); const diffMinutes = Math.floor( (startTime.getTime() - now.getTime()) / 60000, ); return ( {diffMinutes < MEETING_DEFAULT_TIME_MINUTES ? `In ${diffMinutes}m` : "Upcoming"} {event.title || "Scheduled Meeting"} {startTime.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", month: "short", day: "numeric", })} ); } return ( No meetings ); } export function RoomTable({ rooms, linkCopied, onCopyUrl, onEdit, onDelete, loading, }: RoomTableProps) { const [syncingRooms, setSyncingRooms] = useState>( new Set(), ); const syncMutation = useRoomIcsSync(); const handleForceSync = async (roomName: NonEmptyString) => { setSyncingRooms((prev) => new Set(prev).add(roomName)); try { await syncMutation.mutateAsync({ params: { path: { room_name: roomName }, }, }); } catch (err) { console.error("Failed to sync calendar:", err); } finally { setSyncingRooms((prev) => { const next = new Set(prev); next.delete(roomName); return next; }); } }; return ( {loading && ( )} Room Name Current Meeting Zulip Room Size Recording {rooms.map((room) => ( {room.name} {getZulipDisplay( room.zulip_auto_post, room.zulip_stream, room.zulip_topic, )} {getRoomModeDisplay(room.room_mode)} {getRecordingDisplay( room.recording_type, room.recording_trigger, )} {room.ics_enabled && ( handleForceSync( parseNonEmptyString( room.name, true, "panic! room.name is required", ), ) } size="sm" variant="ghost" disabled={syncingRooms.has( parseNonEmptyString( room.name, true, "panic! room.name is required", ), )} > {syncingRooms.has( parseNonEmptyString( room.name, true, "panic! room.name is required", ), ) ? ( ) : ( )} )} {linkCopied === room.name ? ( Copied! ) : ( onCopyUrl( parseNonEmptyString( room.name, true, "panic! room.name is required", ), ) } size="sm" variant="ghost" > )} ))} ); }