HandRaise kısımları ayrı bir tab üzerinden yönetilmiyor

This commit is contained in:
Sedat ÖZTÜRK 2025-08-29 15:53:11 +03:00
parent df415dd04d
commit 823ee98384
4 changed files with 80 additions and 149 deletions

View file

@ -327,7 +327,7 @@ public class ClassroomHub : Hub
} }
[HubMethodName("ApproveHandRaise")] [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 // 🔑 Öğrencinin parmak kaldırma durumunu sıfırla
var participant = await _participantRepository.FirstOrDefaultAsync( var participant = await _participantRepository.FirstOrDefaultAsync(
@ -340,11 +340,11 @@ public class ClassroomHub : Hub
await _participantRepository.UpdateAsync(participant, autoSave: true); 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")] [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 // 🔑 Participant'ı bul ve elini indir
var participant = await _participantRepository.FirstOrDefaultAsync( var participant = await _participantRepository.FirstOrDefaultAsync(
@ -357,7 +357,7 @@ public class ClassroomHub : Hub
await _participantRepository.UpdateAsync(participant, autoSave: true); 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) public override async Task OnDisconnectedAsync(Exception exception)

View file

@ -56,6 +56,7 @@ export interface ClassroomParticipantDto {
isObserver?: boolean isObserver?: boolean
isAudioMuted?: boolean isAudioMuted?: boolean
isVideoMuted?: boolean isVideoMuted?: boolean
isHandRaised?: boolean
stream?: MediaStream stream?: MediaStream
screenStream?: MediaStream screenStream?: MediaStream
isScreenSharing?: boolean isScreenSharing?: boolean

View file

@ -10,8 +10,8 @@ export class SignalRService {
private onParticipantLeft?: (userId: string) => void private onParticipantLeft?: (userId: string) => void
private onChatMessage?: (message: ClassroomChatDto) => void private onChatMessage?: (message: ClassroomChatDto) => void
private onParticipantMuted?: (userId: string, isMuted: boolean) => void private onParticipantMuted?: (userId: string, isMuted: boolean) => void
private onHandRaiseReceived?: (handRaise: HandRaiseDto) => void private onHandRaiseReceived?: (studentId: string) => void
private onHandRaiseDismissed?: (handRaiseId: string) => void private onHandRaiseDismissed?: (studentId: string) => void
constructor() { constructor() {
const { auth } = store.getState() const { auth } = store.getState()
@ -52,12 +52,14 @@ export class SignalRService {
this.onParticipantMuted?.(userId, isMuted) this.onParticipantMuted?.(userId, isMuted)
}) })
this.connection.on('HandRaiseReceived', (handRaise: HandRaiseDto) => { this.connection.on('HandRaiseReceived', (payload: any) => {
this.onHandRaiseReceived?.(handRaise) // payload = { handRaiseId, studentId, studentName, ... }
this.onHandRaiseReceived?.(payload.studentId)
}) })
this.connection.on('HandRaiseDismissed', (handRaiseId: string) => { this.connection.on('HandRaiseDismissed', (payload: any) => {
this.onHandRaiseDismissed?.(handRaiseId) // payload = { handRaiseId, studentId }
this.onHandRaiseDismissed?.(payload.studentId)
}) })
this.connection.onreconnected(() => { this.connection.onreconnected(() => {
@ -135,6 +137,7 @@ export class SignalRService {
console.log('Error starting SignalR connection simulating chat message from', senderName) console.log('Error starting SignalR connection simulating chat message from', senderName)
const chatMessage: ClassroomChatDto = { const chatMessage: ClassroomChatDto = {
id: crypto.randomUUID(), id: crypto.randomUUID(),
sessionId,
senderId, senderId,
senderName, senderName,
message, message,
@ -181,6 +184,7 @@ export class SignalRService {
) )
const chatMessage: ClassroomChatDto = { const chatMessage: ClassroomChatDto = {
id: crypto.randomUUID(), id: crypto.randomUUID(),
sessionId,
senderId, senderId,
senderName, senderName,
message, message,
@ -224,6 +228,7 @@ export class SignalRService {
console.log('Error starting SignalR connection simulating announcement from', senderName) console.log('Error starting SignalR connection simulating announcement from', senderName)
const chatMessage: ClassroomChatDto = { const chatMessage: ClassroomChatDto = {
id: crypto.randomUUID(), id: crypto.randomUUID(),
sessionId,
senderId, senderId,
senderName, senderName,
message, message,
@ -285,7 +290,7 @@ export class SignalRService {
isActive: true, isActive: true,
} }
setTimeout(() => { setTimeout(() => {
this.onHandRaiseReceived?.(handRaise) this.onHandRaiseReceived?.(studentId)
}, 100) }, 100)
return return
} }
@ -313,33 +318,33 @@ export class SignalRService {
} }
} }
async approveHandRaise(sessionId: string, handRaiseId: string): Promise<void> { async approveHandRaise(sessionId: string, studentId: string): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Error starting SignalR connection simulating hand raise approval') console.log('Simulating hand raise approval for student', studentId)
setTimeout(() => { setTimeout(() => {
this.onHandRaiseDismissed?.(handRaiseId) this.onHandRaiseDismissed?.(studentId)
}, 100) }, 100)
return return
} }
try { try {
await this.connection.invoke('ApproveHandRaise', sessionId, handRaiseId) await this.connection.invoke('ApproveHandRaise', sessionId, studentId)
} catch (error) { } catch (error) {
console.error('Error approving hand raise:', error) console.error('Error approving hand raise:', error)
} }
} }
async dismissHandRaise(sessionId: string, handRaiseId: string): Promise<void> { async dismissHandRaise(sessionId: string, studentId: string): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Error starting SignalR connection simulating hand raise dismissal') console.log('Simulating hand raise dismissal for student', studentId)
setTimeout(() => { setTimeout(() => {
this.onHandRaiseDismissed?.(handRaiseId) this.onHandRaiseDismissed?.(studentId)
}, 100) }, 100)
return return
} }
try { try {
await this.connection.invoke('DismissHandRaise', sessionId, handRaiseId) await this.connection.invoke('DismissHandRaise', sessionId, studentId)
} catch (error) { } catch (error) {
console.error('Error dismissing hand raise:', error) console.error('Error dismissing hand raise:', error)
} }
@ -365,11 +370,11 @@ export class SignalRService {
this.onParticipantMuted = callback this.onParticipantMuted = callback
} }
setHandRaiseReceivedHandler(callback: (handRaise: HandRaiseDto) => void) { setHandRaiseReceivedHandler(callback: (studentId: string) => void) {
this.onHandRaiseReceived = callback this.onHandRaiseReceived = callback
} }
setHandRaiseDismissedHandler(callback: (handRaiseId: string) => void) { setHandRaiseDismissedHandler(callback: (studentId: string) => void) {
this.onHandRaiseDismissed = callback this.onHandRaiseDismissed = callback
} }

View file

@ -22,7 +22,6 @@ import {
FaClipboardList, FaClipboardList,
FaLayerGroup, FaLayerGroup,
FaWrench, FaWrench,
FaCheck,
FaUserTimes, FaUserTimes,
FaDownload, FaDownload,
FaTrash, FaTrash,
@ -35,6 +34,7 @@ import {
FaBullhorn, FaBullhorn,
FaUser, FaUser,
FaBars, FaBars,
FaCheck,
} from 'react-icons/fa' } from 'react-icons/fa'
import { SignalRService } from '@/services/classroom/signalr' import { SignalRService } from '@/services/classroom/signalr'
import { WebRTCService } from '@/services/classroom/webrtc' import { WebRTCService } from '@/services/classroom/webrtc'
@ -45,7 +45,6 @@ import {
ClassroomParticipantDto, ClassroomParticipantDto,
ClassroomDto, ClassroomDto,
ClassroomSettingsDto, ClassroomSettingsDto,
HandRaiseDto,
VideoLayoutDto, VideoLayoutDto,
} from '@/proxy/classroom/models' } from '@/proxy/classroom/models'
import { useStoreState } from '@/store/store' 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', description: 'Tüm katılımcılar eşit boyutta görünür',
}) })
const [focusedParticipant, setFocusedParticipant] = useState<string>() const [focusedParticipant, setFocusedParticipant] = useState<string>()
const [handRaises, setHandRaises] = useState<HandRaiseDto[]>([])
const [hasRaisedHand, setHasRaisedHand] = useState(false) const [hasRaisedHand, setHasRaisedHand] = useState(false)
const [isAllMuted, setIsAllMuted] = useState(false) const [isAllMuted, setIsAllMuted] = useState(false)
const [kickingParticipant, setKickingParticipant] = useState<{ id: string; name: string } | null>( const [kickingParticipant, setKickingParticipant] = useState<{ id: string; name: string } | null>(
@ -301,14 +299,21 @@ const RoomDetail: React.FC = () => {
}) })
// Hand raise events // Hand raise events
signalRServiceRef.current.setHandRaiseReceivedHandler?.((handRaise) => { signalRServiceRef.current.setHandRaiseReceivedHandler((studentId) => {
setHandRaises((prev) => [...prev, handRaise]) setParticipants((prev) =>
prev.map((p) => (p.id === studentId ? { ...p, isHandRaised: true } : p)),
)
}) })
signalRServiceRef.current.setHandRaiseDismissedHandler?.((handRaiseId) => { signalRServiceRef.current.setHandRaiseDismissedHandler((studentId) => {
setHandRaises((prev) => setParticipants((prev) =>
prev.map((hr) => (hr.id === handRaiseId ? { ...hr, isActive: false } : hr)), prev.map((p) => (p.id === studentId ? { ...p, isHandRaised: false } : p)),
) )
// 👇 kendi stateini de sıfırla
if (studentId === user.id) {
setHasRaisedHand(false)
}
}) })
// Join the class // 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) => { const handleKickParticipant = async (participantId: string) => {
if (signalRServiceRef.current && user.role === 'teacher') { if (signalRServiceRef.current && user.role === 'teacher') {
await signalRServiceRef.current.kickParticipant(classSession.id, participantId) await signalRServiceRef.current.kickParticipant(classSession.id, participantId)
@ -995,8 +982,48 @@ const RoomDetail: React.FC = () => {
> >
{participant.name} {participant.name}
</span> </span>
{/* 👋 Parmak kaldırma göstergesi */}
{participant.isHandRaised && (
<FaHandPaper
className="text-yellow-600 ml-2"
title="Parmak kaldırdı"
/>
)}
</div> </div>
<div className="flex items-center space-x-1"> <div className="flex items-center space-x-1">
{/* 🎓 Öğretmen için el kaldırma kontrolü */}
{user.role === 'teacher' &&
!participant.isTeacher &&
participant.isHandRaised && (
<>
<button
onClick={() =>
signalRServiceRef.current?.approveHandRaise(
classSession.id,
participant.id,
)
}
className="p-1 text-green-600 hover:bg-green-50 rounded transition-colors"
title="El kaldırmayı onayla"
>
<FaCheck size={12} />
</button>
<button
onClick={() =>
signalRServiceRef.current?.dismissHandRaise(
classSession.id,
participant.id,
)
}
className="p-1 text-red-600 hover:bg-red-50 rounded transition-colors"
title="El kaldırmayı reddet"
>
<FaTimes size={12} />
</button>
</>
)}
{/* Ses aç/kapat butonu */} {/* Ses aç/kapat butonu */}
{user.role === 'teacher' && !participant.isTeacher && ( {user.role === 'teacher' && !participant.isTeacher && (
<button <button
@ -1195,76 +1222,6 @@ const RoomDetail: React.FC = () => {
</div> </div>
) )
case 'handraises': {
const activeHandRaises = handRaises.filter((hr) => hr.isActive)
return (
<div className="h-full bg-white flex flex-col">
<div className="p-4 border-b border-gray-200">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900">
Parmak Kaldıranlar ({activeHandRaises.length})
</h3>
<button onClick={() => setActiveSidePanel(null)}>
<FaTimes className="text-gray-500" />
</button>
</div>
</div>
<div className="flex-1 overflow-y-auto p-4">
{activeHandRaises.length === 0 ? (
<div className="text-center py-8 text-gray-500">
<FaHandPaper size={32} className="mx-auto mb-4 text-gray-300" />
<p className="text-sm text-gray-600">
Şu anda parmak kaldıran öğrenci bulunmamaktadır.
</p>
</div>
) : (
<div className="space-y-3">
{activeHandRaises.map((handRaise) => (
<div
key={handRaise.id}
className="flex items-center justify-between p-3 bg-yellow-50 border border-yellow-200 rounded-lg"
>
<div className="flex items-center space-x-3">
<FaHandPaper className="text-yellow-600" size={16} />
<div>
<h4 className="font-medium text-gray-800 text-sm">
{handRaise.studentName}
</h4>
<p className="text-xs text-gray-600">
{formatTime(handRaise.timestamp)} {' '}
{getTimeSince(handRaise.timestamp)}
</p>
</div>
</div>
{user.role === 'teacher' && (
<div className="flex space-x-1">
<button
onClick={() => handleApproveHandRaise(handRaise.id)}
className="flex items-center space-x-1 px-2 py-1 bg-green-600 text-white rounded text-xs hover:bg-green-700 transition-colors"
>
<FaCheck size={10} />
<span>Onayla</span>
</button>
<button
onClick={() => handleDismissHandRaise(handRaise.id)}
className="flex items-center space-x-1 px-2 py-1 bg-red-600 text-white rounded text-xs hover:bg-red-700 transition-colors"
>
<FaTimes size={10} />
<span>Reddet</span>
</button>
</div>
)}
</div>
))}
</div>
)}
</div>
</div>
)
}
case 'layout': case 'layout':
return ( return (
<div className="h-full bg-white flex flex-col text-gray-900"> <div className="h-full bg-white flex flex-col text-gray-900">
@ -1744,20 +1701,6 @@ const RoomDetail: React.FC = () => {
> >
<FaFile /> <span>Dokümanlar</span> <FaFile /> <span>Dokümanlar</span>
</button> </button>
<button
onClick={() => {
setMobileMenuOpen(false)
setTimeout(() => toggleSidePanel('handraises'), 200)
}}
className={`flex items-center space-x-2 p-3 rounded-lg transition-all text-base ${activeSidePanel === 'handraises' ? 'bg-blue-100 text-blue-700' : 'hover:bg-gray-100 text-gray-700'}`}
>
<FaHandPaper /> <span>Parmak Kaldıranlar</span>
{handRaises.filter((hr) => hr.isActive).length > 0 && (
<span className="ml-auto bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{handRaises.filter((hr) => hr.isActive).length}
</span>
)}
</button>
<button <button
onClick={() => { onClick={() => {
setMobileMenuOpen(false) setMobileMenuOpen(false)
@ -1984,24 +1927,6 @@ const RoomDetail: React.FC = () => {
<FaFile size={14} /> <FaFile size={14} />
</button> </button>
{/* Hand Raises Button */}
<button
onClick={() => toggleSidePanel('handraises')}
className={`p-2 rounded-lg transition-all relative ${
activeSidePanel === 'handraises'
? 'bg-blue-600 text-white'
: 'bg-gray-700 hover:bg-gray-600 text-white'
}`}
title="Parmak Kaldıranlar"
>
<FaHandPaper size={14} />
{handRaises.filter((hr) => hr.isActive).length > 0 && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full w-4 h-4 flex items-center justify-center text-[10px]">
{handRaises.filter((hr) => hr.isActive).length}
</span>
)}
</button>
{/* Mute All Button */} {/* Mute All Button */}
<button <button
onClick={handleMuteAll} onClick={handleMuteAll}