HandRaise kısımları ayrı bir tab üzerinden yönetilmiyor
This commit is contained in:
parent
df415dd04d
commit
823ee98384
4 changed files with 80 additions and 149 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ export interface ClassroomParticipantDto {
|
|||
isObserver?: boolean
|
||||
isAudioMuted?: boolean
|
||||
isVideoMuted?: boolean
|
||||
isHandRaised?: boolean
|
||||
stream?: MediaStream
|
||||
screenStream?: MediaStream
|
||||
isScreenSharing?: boolean
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
async approveHandRaise(sessionId: string, studentId: string): Promise<void> {
|
||||
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<void> {
|
||||
async dismissHandRaise(sessionId: string, studentId: string): Promise<void> {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string>()
|
||||
const [handRaises, setHandRaises] = useState<HandRaiseDto[]>([])
|
||||
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}
|
||||
</span>
|
||||
|
||||
{/* 👋 Parmak kaldırma göstergesi */}
|
||||
{participant.isHandRaised && (
|
||||
<FaHandPaper
|
||||
className="text-yellow-600 ml-2"
|
||||
title="Parmak kaldırdı"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<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 */}
|
||||
{user.role === 'teacher' && !participant.isTeacher && (
|
||||
<button
|
||||
|
|
@ -1195,76 +1222,6 @@ const RoomDetail: React.FC = () => {
|
|||
</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':
|
||||
return (
|
||||
<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>
|
||||
</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
|
||||
onClick={() => {
|
||||
setMobileMenuOpen(false)
|
||||
|
|
@ -1984,24 +1927,6 @@ const RoomDetail: React.FC = () => {
|
|||
<FaFile size={14} />
|
||||
</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 */}
|
||||
<button
|
||||
onClick={handleMuteAll}
|
||||
|
|
|
|||
Loading…
Reference in a new issue