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"
>
)}
))}
);
}