diff --git a/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs b/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs index b2205f35..01e50287 100644 --- a/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs +++ b/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs @@ -327,7 +327,7 @@ public class ClassroomHub : Hub } [HubMethodName("ApproveHandRaise")] - public async Task ApproveHandRaiseAsync(Guid sessionId, Guid handRaiseId, Guid studentId) + public async Task ApproveHandRaiseAsync(Guid sessionId, Guid studentId) { // 🔑 Öğrencinin parmak kaldırma durumunu sıfırla var participant = await _participantRepository.FirstOrDefaultAsync( @@ -340,11 +340,11 @@ public class ClassroomHub : Hub await _participantRepository.UpdateAsync(participant, autoSave: true); } - await Clients.Group(sessionId.ToString()).SendAsync("HandRaiseDismissed", handRaiseId); + await Clients.Group(sessionId.ToString()).SendAsync("HandRaiseDismissed", new { studentId }); } [HubMethodName("DismissHandRaise")] - public async Task DismissHandRaiseAsync(Guid sessionId, Guid handRaiseId, Guid studentId) + public async Task DismissHandRaiseAsync(Guid sessionId, Guid studentId) { // 🔑 Participant'ı bul ve elini indir var participant = await _participantRepository.FirstOrDefaultAsync( @@ -357,7 +357,7 @@ public class ClassroomHub : Hub await _participantRepository.UpdateAsync(participant, autoSave: true); } - await Clients.Group(sessionId.ToString()).SendAsync("HandRaiseDismissed", handRaiseId); + await Clients.Group(sessionId.ToString()).SendAsync("HandRaiseDismissed", new { studentId }); } public override async Task OnDisconnectedAsync(Exception exception) diff --git a/ui/src/proxy/classroom/models.ts b/ui/src/proxy/classroom/models.ts index c33fe7c7..ea563b22 100644 --- a/ui/src/proxy/classroom/models.ts +++ b/ui/src/proxy/classroom/models.ts @@ -56,6 +56,7 @@ export interface ClassroomParticipantDto { isObserver?: boolean isAudioMuted?: boolean isVideoMuted?: boolean + isHandRaised?: boolean stream?: MediaStream screenStream?: MediaStream isScreenSharing?: boolean diff --git a/ui/src/services/classroom/signalr.ts b/ui/src/services/classroom/signalr.ts index 2075c427..777886fd 100644 --- a/ui/src/services/classroom/signalr.ts +++ b/ui/src/services/classroom/signalr.ts @@ -10,8 +10,8 @@ export class SignalRService { private onParticipantLeft?: (userId: string) => void private onChatMessage?: (message: ClassroomChatDto) => void private onParticipantMuted?: (userId: string, isMuted: boolean) => void - private onHandRaiseReceived?: (handRaise: HandRaiseDto) => void - private onHandRaiseDismissed?: (handRaiseId: string) => void + private onHandRaiseReceived?: (studentId: string) => void + private onHandRaiseDismissed?: (studentId: string) => void constructor() { const { auth } = store.getState() @@ -52,12 +52,14 @@ export class SignalRService { this.onParticipantMuted?.(userId, isMuted) }) - this.connection.on('HandRaiseReceived', (handRaise: HandRaiseDto) => { - this.onHandRaiseReceived?.(handRaise) + this.connection.on('HandRaiseReceived', (payload: any) => { + // payload = { handRaiseId, studentId, studentName, ... } + this.onHandRaiseReceived?.(payload.studentId) }) - this.connection.on('HandRaiseDismissed', (handRaiseId: string) => { - this.onHandRaiseDismissed?.(handRaiseId) + this.connection.on('HandRaiseDismissed', (payload: any) => { + // payload = { handRaiseId, studentId } + this.onHandRaiseDismissed?.(payload.studentId) }) this.connection.onreconnected(() => { @@ -135,6 +137,7 @@ export class SignalRService { console.log('Error starting SignalR connection simulating chat message from', senderName) const chatMessage: ClassroomChatDto = { id: crypto.randomUUID(), + sessionId, senderId, senderName, message, @@ -181,6 +184,7 @@ export class SignalRService { ) const chatMessage: ClassroomChatDto = { id: crypto.randomUUID(), + sessionId, senderId, senderName, message, @@ -224,6 +228,7 @@ export class SignalRService { console.log('Error starting SignalR connection simulating announcement from', senderName) const chatMessage: ClassroomChatDto = { id: crypto.randomUUID(), + sessionId, senderId, senderName, message, @@ -285,7 +290,7 @@ export class SignalRService { isActive: true, } setTimeout(() => { - this.onHandRaiseReceived?.(handRaise) + this.onHandRaiseReceived?.(studentId) }, 100) return } @@ -313,33 +318,33 @@ export class SignalRService { } } - async approveHandRaise(sessionId: string, handRaiseId: string): Promise { + async approveHandRaise(sessionId: string, studentId: string): Promise { if (!this.isConnected) { - console.log('Error starting SignalR connection simulating hand raise approval') + console.log('Simulating hand raise approval for student', studentId) setTimeout(() => { - this.onHandRaiseDismissed?.(handRaiseId) + this.onHandRaiseDismissed?.(studentId) }, 100) return } try { - await this.connection.invoke('ApproveHandRaise', sessionId, handRaiseId) + await this.connection.invoke('ApproveHandRaise', sessionId, studentId) } catch (error) { console.error('Error approving hand raise:', error) } } - async dismissHandRaise(sessionId: string, handRaiseId: string): Promise { + async dismissHandRaise(sessionId: string, studentId: string): Promise { if (!this.isConnected) { - console.log('Error starting SignalR connection simulating hand raise dismissal') + console.log('Simulating hand raise dismissal for student', studentId) setTimeout(() => { - this.onHandRaiseDismissed?.(handRaiseId) + this.onHandRaiseDismissed?.(studentId) }, 100) return } try { - await this.connection.invoke('DismissHandRaise', sessionId, handRaiseId) + await this.connection.invoke('DismissHandRaise', sessionId, studentId) } catch (error) { console.error('Error dismissing hand raise:', error) } @@ -365,11 +370,11 @@ export class SignalRService { this.onParticipantMuted = callback } - setHandRaiseReceivedHandler(callback: (handRaise: HandRaiseDto) => void) { + setHandRaiseReceivedHandler(callback: (studentId: string) => void) { this.onHandRaiseReceived = callback } - setHandRaiseDismissedHandler(callback: (handRaiseId: string) => void) { + setHandRaiseDismissedHandler(callback: (studentId: string) => void) { this.onHandRaiseDismissed = callback } diff --git a/ui/src/views/classroom/RoomDetail.tsx b/ui/src/views/classroom/RoomDetail.tsx index 431bd04e..ecb376f4 100644 --- a/ui/src/views/classroom/RoomDetail.tsx +++ b/ui/src/views/classroom/RoomDetail.tsx @@ -22,7 +22,6 @@ import { FaClipboardList, FaLayerGroup, FaWrench, - FaCheck, FaUserTimes, FaDownload, FaTrash, @@ -35,6 +34,7 @@ import { FaBullhorn, FaUser, FaBars, + FaCheck, } from 'react-icons/fa' import { SignalRService } from '@/services/classroom/signalr' import { WebRTCService } from '@/services/classroom/webrtc' @@ -45,7 +45,6 @@ import { ClassroomParticipantDto, ClassroomDto, ClassroomSettingsDto, - HandRaiseDto, VideoLayoutDto, } from '@/proxy/classroom/models' import { useStoreState } from '@/store/store' @@ -108,7 +107,6 @@ const RoomDetail: React.FC = () => { 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>( @@ -301,14 +299,21 @@ const RoomDetail: React.FC = () => { }) // Hand raise events - signalRServiceRef.current.setHandRaiseReceivedHandler?.((handRaise) => { - setHandRaises((prev) => [...prev, handRaise]) + signalRServiceRef.current.setHandRaiseReceivedHandler((studentId) => { + setParticipants((prev) => + prev.map((p) => (p.id === studentId ? { ...p, isHandRaised: true } : p)), + ) }) - signalRServiceRef.current.setHandRaiseDismissedHandler?.((handRaiseId) => { - setHandRaises((prev) => - prev.map((hr) => (hr.id === handRaiseId ? { ...hr, isActive: false } : hr)), + signalRServiceRef.current.setHandRaiseDismissedHandler((studentId) => { + setParticipants((prev) => + prev.map((p) => (p.id === studentId ? { ...p, isHandRaised: false } : p)), ) + + // 👇 kendi state’ini de sıfırla + if (studentId === user.id) { + setHasRaisedHand(false) + } }) // Join the class @@ -440,24 +445,6 @@ const RoomDetail: React.FC = () => { } } - 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) @@ -995,8 +982,48 @@ const RoomDetail: React.FC = () => { > {participant.name} + + {/* 👋 Parmak kaldırma göstergesi */} + {participant.isHandRaised && ( + + )}
+ {/* 🎓 Öğretmen için el kaldırma kontrolü */} + {user.role === 'teacher' && + !participant.isTeacher && + participant.isHandRaised && ( + <> + + + + )} + {/* Ses aç/kapat butonu */} {user.role === 'teacher' && !participant.isTeacher && (
) - 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 (
@@ -1744,20 +1701,6 @@ const RoomDetail: React.FC = () => { > Dokümanlar - - {/* Hand Raises Button */} - - {/* Mute All Button */}