import React, { useState, useEffect, useRef } from 'react' import { motion } from 'framer-motion' import { FaUsers, FaComments, FaUserPlus, FaTh, FaExpand, FaHandPaper, FaVolumeMute, FaVolumeUp, FaFile, FaDesktop, FaMicrophone, FaMicrophoneSlash, FaVideo, FaVideoSlash, FaPhone, FaTimes, FaCompress, FaUserFriends, FaClipboardList, FaLayerGroup, FaWrench, FaCheck, FaUserTimes, FaDownload, FaTrash, FaEye, FaFilePdf, FaFileWord, FaFileImage, FaFileAlt, FaPaperPlane, FaBullhorn, FaUser, FaBars, } from 'react-icons/fa' import { SignalRService } from '@/services/classroom/signalr' import { WebRTCService } from '@/services/classroom/webrtc' import { ClassroomAttendanceDto, ClassroomChatDto, ClassDocumentDto, ClassroomParticipantDto, ClassroomDto, ClassroomSettingsDto, HandRaiseDto, VideoLayoutDto, } from '@/proxy/classroom/models' import { useStoreState } from '@/store/store' import { ParticipantGrid } from '@/components/classroom/ParticipantGrid' import { ScreenSharePanel } from '@/components/classroom/Panels/ScreenSharePanel' import { KickParticipantModal } from '@/components/classroom/KickParticipantModal' import { useParams } from 'react-router-dom' import { getClassroomById } from '@/services/classroom.service' import { showDbDateAsIs } from '@/utils/dateUtils' import { useNavigate } from 'react-router-dom' import { endClassroom } from '@/services/classroom.service' import { ROUTES_ENUM } from '@/routes/route.constant' import { Helmet } from 'react-helmet' import { useLocalization } from '@/utils/hooks/useLocalization' type SidePanelType = | 'chat' | 'participants' | 'documents' | 'handraises' | 'layout' | 'settings' | null const newClassSession: ClassroomDto = { id: '', name: '', teacherId: '', teacherName: '', scheduledStartTime: '', scheduledEndTime: '', actualStartTime: '', actualEndTime: '', participantCount: 0, settingsDto: undefined, } const RoomDetail: React.FC = () => { const params = useParams() const navigate = useNavigate() const { user } = useStoreState((state) => state.auth) const { translate } = useLocalization() const [classSession, setClassSession] = useState(newClassSession) const [mobileMenuOpen, setMobileMenuOpen] = useState(false) const [participants, setParticipants] = useState([]) const [localStream, setLocalStream] = useState() const [isAudioEnabled, setIsAudioEnabled] = useState(true) const [isVideoEnabled, setIsVideoEnabled] = useState(true) const [attendanceRecords, setAttendanceRecords] = useState([]) const [chatMessages, setChatMessages] = useState([]) const [currentLayout, setCurrentLayout] = useState({ id: 'grid', name: 'Izgara Görünümü', type: 'grid', description: 'Tüm katılımcılar eşit boyutta görünür', }) const [focusedParticipant, setFocusedParticipant] = useState() const [handRaises, setHandRaises] = useState([]) const [hasRaisedHand, setHasRaisedHand] = useState(false) const [isAllMuted, setIsAllMuted] = useState(false) const [kickingParticipant, setKickingParticipant] = useState<{ id: string; name: string } | null>( null, ) const [documents, setDocuments] = useState([]) const [isScreenSharing, setIsScreenSharing] = useState(false) const [screenStream, setScreenStream] = useState() const [screenSharer, setScreenSharer] = useState() const [isFullscreen, setIsFullscreen] = useState(false) const [activeSidePanel, setActiveSidePanel] = useState(null) const [newMessage, setNewMessage] = useState('') const [messageMode, setMessageMode] = useState<'public' | 'private' | 'announcement'>('public') const [selectedRecipient, setSelectedRecipient] = useState<{ id: string; name: string } | null>( null, ) const [dragOver, setDragOver] = useState(false) const [participantsActiveTab, setParticipantsActiveTab] = useState<'participants' | 'attendance'>( 'participants', ) const fileInputRef = useRef(null) const messagesEndRef = useRef(null) const [classSettings, setClassSettings] = useState({ allowHandRaise: true, defaultMicrophoneState: 'muted', defaultCameraState: 'on', defaultLayout: 'grid', allowStudentScreenShare: false, allowStudentChat: true, allowPrivateMessages: true, autoMuteNewParticipants: true, }) const signalRServiceRef = useRef() const webRTCServiceRef = useRef() const layouts: VideoLayoutDto[] = [ { id: 'grid', name: 'Izgara Görünümü', type: 'grid', description: 'Tüm katılımcılar eşit boyutta görünür', }, { id: 'sidebar', name: 'Sunum Modu', type: 'sidebar', description: 'Ana konuşmacı büyük, diğerleri yan panelde', }, { id: 'teacher-focus', name: 'Öğretmen Odaklı', type: 'teacher-focus', description: 'Öğretmen tam ekranda görünür, öğrenciler küçük panelde', }, ] const fetchClassDetails = async () => { const classEntity = await getClassroomById(params?.id ?? '') if (classEntity) { classEntity.data.scheduledStartTime = showDbDateAsIs(classEntity.data.scheduledStartTime) setClassSession(classEntity.data) } } useEffect(() => { fetchClassDetails() }, []) useEffect(() => { if (classSession.id) { initializeServices() return () => { cleanup() } } }, [classSession.id]) // Apply class settings useEffect(() => { if (classSession?.settingsDto) { setClassSettings(classSession.settingsDto) const selectedLayout = layouts.find((l) => l.id === classSession.settingsDto!.defaultLayout) || layouts[0] setCurrentLayout(selectedLayout) // Apply default audio/video states for new participants if (user.role === 'student') { setIsAudioEnabled(classSession.settingsDto.defaultMicrophoneState === 'unmuted') setIsVideoEnabled(classSession.settingsDto.defaultCameraState === 'on') } } }, [classSession?.settingsDto, user.role]) useEffect(() => { scrollToBottom() }, [chatMessages]) const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) } const initializeServices = async () => { try { // Initialize SignalR signalRServiceRef.current = new SignalRService() await signalRServiceRef.current.start() // Initialize WebRTC webRTCServiceRef.current = new WebRTCService() const stream = await webRTCServiceRef.current.initializeLocalStream() setLocalStream(stream) // Setup WebRTC remote stream handler webRTCServiceRef.current.onRemoteStreamReceived((userId, stream) => { console.log('Received remote stream from:', userId) setParticipants((prev) => prev.map((p) => (p.id === userId ? { ...p, stream } : p))) }) // Setup SignalR event handlers signalRServiceRef.current.setParticipantJoinHandler((userId, name) => { // 🔑 Eğer gelen participant bizsek, listeye ekleme if (userId === user.id) return console.log(`Participant joined: ${name}`) // Create WebRTC connection for new participant if (webRTCServiceRef.current) { webRTCServiceRef.current.createPeerConnection(userId) } setParticipants((prev) => { const existing = prev.find((p) => p.id === userId) if (existing) return prev return [ ...prev, { id: userId, name, isTeacher: false, isAudioMuted: classSettings.autoMuteNewParticipants, isVideoMuted: classSettings.defaultCameraState === 'off', }, ] }) }) signalRServiceRef.current.setParticipantLeaveHandler((userId) => { console.log(`Participant left: ${userId}`) setParticipants((prev) => prev.filter((p) => p.id !== userId)) webRTCServiceRef.current?.closePeerConnection(userId) }) signalRServiceRef.current.setAttendanceUpdatedHandler((record) => { setAttendanceRecords((prev) => { const existing = prev.find((r) => r.id === record.id) if (existing) { return prev.map((r) => (r.id === record.id ? record : r)) } return [...prev, record] }) }) signalRServiceRef.current.setChatMessageReceivedHandler((message) => { setChatMessages((prev) => [...prev, message]) }) signalRServiceRef.current.setParticipantMutedHandler((userId, isMuted) => { setParticipants((prev) => prev.map((p) => (p.id === userId ? { ...p, isAudioMuted: isMuted } : p)), ) }) // Hand raise events signalRServiceRef.current.setHandRaiseReceivedHandler?.((handRaise) => { setHandRaises((prev) => [...prev, handRaise]) }) signalRServiceRef.current.setHandRaiseDismissedHandler?.((handRaiseId) => { setHandRaises((prev) => prev.map((hr) => (hr.id === handRaiseId ? { ...hr, isActive: false } : hr)), ) }) // Join the class await signalRServiceRef.current.joinClass( classSession.id, user.id, user.name, user.role === 'teacher', ) } catch (error) { console.error('Failed to initialize services:', error) } } const cleanup = async () => { if (signalRServiceRef.current) { await signalRServiceRef.current.leaveClass(classSession.id) await signalRServiceRef.current.disconnect() } webRTCServiceRef.current?.closeAllConnections() } const handleToggleAudio = () => { setIsAudioEnabled(!isAudioEnabled) webRTCServiceRef.current?.toggleAudio(!isAudioEnabled) } const handleToggleVideo = () => { setIsVideoEnabled(!isVideoEnabled) webRTCServiceRef.current?.toggleVideo(!isVideoEnabled) } const handleLeaveCall = async () => { try { // Eğer teacher ise sınıfı kapat if (user.role === 'teacher') { await endClassroom(classSession.id) } // Bağlantıları kapat await cleanup() // Başka sayfaya yönlendir navigate(ROUTES_ENUM.protected.admin.classroom.classes) } catch (err) { console.error('Leave işlemi sırasında hata:', err) navigate(ROUTES_ENUM.protected.admin.classroom.classes) } } const handleSendMessage = async (e: React.FormEvent) => { e.preventDefault() if (newMessage.trim() && signalRServiceRef.current) { if (messageMode === 'private' && selectedRecipient) { await signalRServiceRef.current.sendPrivateMessage( classSession.id, user.id, user.name, newMessage.trim(), selectedRecipient.id, selectedRecipient.name, user.role === 'teacher', ) } else if (messageMode === 'announcement' && user.role === 'teacher') { await signalRServiceRef.current.sendAnnouncement( classSession.id, user.id, user.name, newMessage.trim(), user.role === 'teacher', ) } else { await signalRServiceRef.current.sendChatMessage( classSession.id, user.id, user.name, newMessage.trim(), user.role === 'teacher', ) } setNewMessage('') } } const handleMuteParticipant = async ( participantId: string, isMuted: boolean, isTeacher: boolean, ) => { if (signalRServiceRef.current && user.role === 'teacher') { await signalRServiceRef.current.muteParticipant( classSession.id, participantId, isMuted, isTeacher, ) } } const handleMuteAll = async () => { if (signalRServiceRef.current && user.role === 'teacher') { const newMuteState = !isAllMuted setIsAllMuted(newMuteState) // Mute all participants except teacher for (const participant of participants) { if (!participant.isTeacher) { await signalRServiceRef.current.muteParticipant( classSession.id, participant.id, newMuteState, user.role === 'teacher', ) } } } } const handleRaiseHand = async () => { if ( signalRServiceRef.current && user.role === 'student' && !hasRaisedHand && classSettings.allowHandRaise ) { await signalRServiceRef.current.raiseHand(classSession.id, user.id, user.name) setHasRaisedHand(true) } } const handleApproveHandRaise = async (handRaiseId: string) => { if (signalRServiceRef.current && user.role === 'teacher') { await signalRServiceRef.current.approveHandRaise(classSession.id, handRaiseId) setHandRaises((prev) => prev.map((hr) => (hr.id === handRaiseId ? { ...hr, isActive: false } : hr)), ) } } const handleDismissHandRaise = async (handRaiseId: string) => { if (signalRServiceRef.current && user.role === 'teacher') { await signalRServiceRef.current.dismissHandRaise(classSession.id, handRaiseId) setHandRaises((prev) => prev.map((hr) => (hr.id === handRaiseId ? { ...hr, isActive: false } : hr)), ) } } const handleKickParticipant = async (participantId: string) => { if (signalRServiceRef.current && user.role === 'teacher') { await signalRServiceRef.current.kickParticipant(classSession.id, participantId) setParticipants((prev) => prev.filter((p) => p.id !== participantId)) // Update attendance record for kicked participant setAttendanceRecords((prev) => prev.map((r) => { if (r.studentId === participantId && !r.leaveTime) { const leaveTime = new Date().toISOString() const join = new Date(r.joinTime) const leave = new Date(leaveTime) const totalDurationMinutes = Math.max( 1, Math.round((leave.getTime() - join.getTime()) / 60000), ) return { ...r, leaveTime, totalDurationMinutes } } return r }), ) } } const handleUploadDocument = async (file: File) => { // In a real app, this would upload to a server const newDoc: ClassDocumentDto = { id: crypto.randomUUID(), name: file.name, url: URL.createObjectURL(file), type: file.type, size: file.size, uploadedAt: new Date().toISOString(), uploadedBy: user.name, } setDocuments((prev) => [...prev, newDoc]) } const handleDeleteDocument = (documentId: string) => { setDocuments((prev) => prev.filter((d) => d.id !== documentId)) } const handleViewDocument = (document: ClassDocumentDto) => { window.open(document.url, '_blank') } const handleStartScreenShare = async () => { try { const stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true, }) setScreenStream(stream) setIsScreenSharing(true) setScreenSharer(user.name) // Handle stream end stream.getVideoTracks()[0].onended = () => { handleStopScreenShare() } } catch (error) { console.error('Error starting screen share:', error) } } const handleStopScreenShare = () => { if (screenStream) { screenStream.getTracks().forEach((track) => track.stop()) setScreenStream(undefined) } setIsScreenSharing(false) setScreenSharer(undefined) } const handleLayoutChange = (layout: VideoLayoutDto) => { setCurrentLayout(layout) if (layout.type === 'grid') { setFocusedParticipant(undefined) } } const handleParticipantFocus = (participantId: string | undefined) => { setFocusedParticipant(participantId) } const toggleFullscreen = () => { if (!document.fullscreenElement) { document.documentElement.requestFullscreen() setIsFullscreen(true) } else { document.exitFullscreen() setIsFullscreen(false) } } const toggleSidePanel = (panelType: SidePanelType) => { setActiveSidePanel(activeSidePanel === panelType ? null : panelType) } // Demo: Simulate student joining const simulateStudentJoin = async () => { const studentNames = ['Ahmet Yılmaz', 'Fatma Demir', 'Mehmet Kaya', 'Ayşe Özkan', 'Ali Çelik'] const availableNames = studentNames.filter((name) => !participants.some((p) => p.name === name)) if (availableNames.length === 0) { alert('Tüm demo öğrenciler zaten sınıfta!') return } const randomName = availableNames[Math.floor(Math.random() * availableNames.length)] const studentId = crypto.randomUUID() // Guid formatında id üretiliyor // SignalR üzerinden joinClass çağrılıyor await signalRServiceRef.current?.joinClass( classSession.id, studentId, randomName, false, // öğrenci ) } // // Demo: Simulate student joining // const simulateStudentJoin = () => { // const studentNames = ['Ahmet Yılmaz', 'Fatma Demir', 'Mehmet Kaya', 'Ayşe Özkan', 'Ali Çelik'] // const availableNames = studentNames.filter((name) => !participants.some((p) => p.name === name)) // if (availableNames.length === 0) { // alert('Tüm demo öğrenciler zaten sınıfta!') // return // } // const randomName = availableNames[Math.floor(Math.random() * availableNames.length)] // const studentId = crypto.randomUUID() // setParticipants((prev) => { // return [ // ...prev, // { // id: studentId, // name: randomName, // isTeacher: false, // isAudioMuted: classSettings.autoMuteNewParticipants, // isVideoMuted: classSettings.defaultCameraState === 'off', // }, // ] // }) // // Add attendance record // setAttendanceRecords((prev: any) => { // // Check if student already has an active attendance record // const existingRecord = prev.find((r: any) => r.studentId === studentId && !r.leaveTime) // if (existingRecord) return prev // return [ // ...prev, // { // id: crypto.randomUUID(), // sessionId: classSession.id, // studentId, // studentName: randomName, // joinTime: new Date().toISOString(), // totalDurationMinutes: 0, // }, // ] // }) // } const handleSettingsChange = (newSettings: Partial) => { setClassSettings((prev) => ({ ...prev, ...newSettings })) } const formatTime = (timestamp: string) => { return new Date(timestamp).toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit', }) } const formatDuration = (minutes: number) => { const hours = Math.floor(minutes / 60) const mins = minutes % 60 if (hours > 0) { return `${hours}h ${mins}m` } return `${mins}m` } const getTimeSince = (timestamp: string) => { const now = new Date() const time = new Date(timestamp) const diffMinutes = Math.floor((now.getTime() - time.getTime()) / 60000) if (diffMinutes < 1) return 'Az önce' if (diffMinutes < 60) return `${diffMinutes} dakika önce` const hours = Math.floor(diffMinutes / 60) return `${hours} saat önce` } const getFileIcon = (type: string) => { if (type.includes('pdf')) return if ( type.includes('word') || type.includes('doc') || type.includes('presentation') || type.includes('powerpoint') ) return if (type.includes('image')) return return } const formatFileSize = (bytes: number) => { if (bytes === 0) return '0 Bytes' const k = 1024 const sizes = ['Bytes', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] } const handleDrop = (e: React.DragEvent) => { e.preventDefault() setDragOver(false) if (user.role !== 'teacher' || !handleUploadDocument) return const files = Array.from(e.dataTransfer.files) files.forEach((file) => handleUploadDocument(file)) } const handleFileSelect = (e: React.ChangeEvent) => { if (user.role !== 'teacher' || !handleUploadDocument) return const files = Array.from(e.target.files || []) files.forEach((file) => handleUploadDocument(file)) // Reset input if (fileInputRef.current) { fileInputRef.current.value = '' } } const getLayoutIcon = (type: string) => { switch (type) { case 'grid': return case 'speaker': return case 'presentation': return case 'sidebar': return default: return } } const renderSidePanel = () => { if (!activeSidePanel) return null const panelContent = () => { switch (activeSidePanel) { case 'chat': { const availableRecipients = participants.filter((p) => p.id !== user.id) return (

Sınıf Sohbeti

{/* Message Mode Selector */}
{user.role === 'teacher' && ( )}
{messageMode === 'private' && ( )}
{/* Messages */}
{chatMessages.length === 0 ? (
Henüz mesaj bulunmamaktadır.
) : ( chatMessages.map((message) => (
{message.senderId !== user.id && (
{message.senderName} {message.isTeacher && ' (Öğretmen)'} {message.messageType === 'private' && message.recipientId === user.id && ' (Size özel)'}
)} {message.messageType === 'private' && message.senderId === user.id && (
→ {message.recipientName}
)} {message.messageType === 'announcement' && (
📢 DUYURU - {message.senderName}
)}
{message.message}
{formatTime(message.timestamp)}
)) )}
{/* Message Input */}
{messageMode === 'public' && 'Herkese mesaj gönderiyorsunuz'} {messageMode === 'private' && selectedRecipient && `${selectedRecipient.name} kişisine özel mesaj`} {messageMode === 'private' && !selectedRecipient && 'Önce bir kişi seçin'} {messageMode === 'announcement' && 'Sınıfa duyuru gönderiyorsunuz'}
setNewMessage(e.target.value)} placeholder={ messageMode === 'private' && !selectedRecipient ? 'Önce kişi seçin...' : messageMode === 'announcement' ? 'Duyuru mesajınız...' : 'Mesajınızı yazın...' } className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm" maxLength={500} disabled={messageMode === 'private' && !selectedRecipient} />
) } case 'participants': { return (

Katılımcılar ({participants.length + 1})

{/* Tab Navigation */}
{participantsActiveTab === 'participants' && ( <> {user.role === 'teacher' && participants.length > 0 && (
)}
{/* Current User */}
{user.name.charAt(0)}
{user.name} (Siz)
{user.role === 'teacher' && ( Öğretmen )}
{/* Other Participants */} {participants.map((participant) => (
{participant.name.charAt(0)}
{ setMessageMode('private') setSelectedRecipient({ id: participant.id, name: participant.name }) setActiveSidePanel('chat') }} > {participant.name}
{/* Ses aç/kapat butonu */} {user.role === 'teacher' && !participant.isTeacher && ( )} {/* Video durumu gösterimi */} {participant.isVideoMuted && ( )} {participant.isTeacher && ( Öğretmen )} {user.role === 'teacher' && !participant.isTeacher && ( )}
))}
)} {participantsActiveTab === 'attendance' && (
{attendanceRecords.length === 0 ? (

Henüz katılım kaydı bulunmamaktadır.

) : (
{attendanceRecords.map((record) => (

{record.studentName}

{formatDuration(record.totalDurationMinutes)}
Giriş: {formatTime(record.joinTime)}
Çıkış:{' '} {record.leaveTime ? formatTime(record.leaveTime) : 'Devam ediyor'}
))}
)}
)}
) } case 'documents': return (

Sınıf Dokümanları

{/* Upload Area (Teacher Only) */} {user.role === 'teacher' && (
{ e.preventDefault() setDragOver(true) }} onDragLeave={() => setDragOver(false)} >

Doküman Yükle

Dosyaları buraya sürükleyin veya seçin

)} {/* Documents List */} {documents.length === 0 ? (

Henüz doküman yüklenmemiş.

) : (
{documents.map((doc) => (
{getFileIcon(doc.type)}

{doc.name}

{formatFileSize(doc.size)} •{' '} {new Date(doc.uploadedAt).toLocaleDateString('tr-TR')}

{doc.uploadedBy}

{user.role === 'teacher' && ( )}
))}
)}
) case 'handraises': { const activeHandRaises = handRaises.filter((hr) => hr.isActive) return (

Parmak Kaldıranlar ({activeHandRaises.length})

{activeHandRaises.length === 0 ? (

Şu anda parmak kaldıran öğrenci bulunmamaktadır.

) : (
{activeHandRaises.map((handRaise) => (

{handRaise.studentName}

{formatTime(handRaise.timestamp)} •{' '} {getTimeSince(handRaise.timestamp)}

{user.role === 'teacher' && (
)}
))}
)}
) } case 'layout': return (

Video Layout Seçin

{layouts.map((layout) => ( ))}
) case 'settings': return (

Sınıf Ayarları

{/* Katılımcı Davranışları */}

Katılımcı İzinleri

{/* Varsayılan Ayarlar */}

Varsayılan Ayarlar

{/* Otomatik Ayarlar */}

Otomatik Ayarlar

{user.role !== 'teacher' && (

⚠️ Ayarları sadece öğretmen değiştirebilir.

)}
) default: return null } } return (
{panelContent()} {/* Kapat butonu kaldırıldı, ana panelde gösterilecek */}
) } return ( <>
{/* Main Content Area */}
{/* Left Content Area - Video and Screen Share */}
{/* Video Container - Panel kapalıyken ortalanmış */}
{/* Screen Share Panel */} {(isScreenSharing || screenStream) && (
)} {/* Video Grid */}
{} : handleToggleAudio} onToggleVideo={user.role === 'observer' ? () => {} : handleToggleVideo} onLeaveCall={handleLeaveCall} onMuteParticipant={handleMuteParticipant} layout={currentLayout} focusedParticipant={focusedParticipant} onParticipantFocus={handleParticipantFocus} hasSidePanel={!!activeSidePanel} onKickParticipant={ user.role === 'teacher' ? (participantId) => { const participant = participants.find((p) => p.id === participantId) if (participant) { setKickingParticipant({ id: participant.id, name: participant.name }) } } : undefined } />
{/* Side Panel */} {activeSidePanel && (
{renderSidePanel()}
)}
{/* Bottom Control Bar - Google Meet Style */}
{/* Mobile Layout */}
{/* Left Side - Main Controls */}
{/* Audio Control */} {user.role !== 'observer' && ( )} {/* Video Control */} {user.role !== 'observer' && ( )} {/* Screen Share */} {(user.role === 'teacher' || classSettings.allowStudentScreenShare) && ( )} {/* Hand Raise (Students) */} {user.role === 'student' && classSettings.allowHandRaise && ( )} {/* Leave Call */}
{/* Right Side - Panel Controls */}
{/* Hamburger Menu Modal */} {mobileMenuOpen && ( <> {/* Overlay */}
setMobileMenuOpen(false)} /> {/* Drawer */}
Menü
{user.role === 'teacher' && ( <> )}
)}
{/* Desktop Layout */}
{/* Left Side - Meeting Info */}
{classSession?.name}
{new Date().toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' })}
{/* Center - Main Controls */}
{/* Audio Control */} {user.role !== 'observer' && ( )} {/* Video Control */} {user.role !== 'observer' && ( )} {/* Screen Share */} {(user.role === 'teacher' || classSettings.allowStudentScreenShare) && ( )} {/* Hand Raise (Students) */} {user.role === 'student' && classSettings.allowHandRaise && ( )} {/* Leave Call */}
{/* Right Side - Panel Controls & Participant Count */}
{/* Participant Count */}
{participants.length + 1}
{/* Fullscreen Toggle */} {/* Chat */} {((user.role !== 'observer' && classSettings.allowStudentChat) || user.role === 'teacher') && ( )} {/* Participants */} {/* Teacher Only Options */} {user.role === 'teacher' && ( <> {/* Documents Button */} {/* Hand Raises Button */} {/* Mute All Button */} {/* Add Student Demo Button */} )} {/* Layout Button */} {/* Settings Button */}
{/* Kick Participant Modal */} setKickingParticipant(null)} onConfirm={handleKickParticipant} />
) } export default RoomDetail