Classroom SignalR ve WebRtc güvenlik düzenlemesi
This commit is contained in:
parent
a09f65f53d
commit
d8c4f39bd3
2 changed files with 75 additions and 28 deletions
|
|
@ -18,6 +18,8 @@ interface ParticipantsPanelProps {
|
||||||
attendanceRecords: ClassroomAttendanceDto[]
|
attendanceRecords: ClassroomAttendanceDto[]
|
||||||
onMuteParticipant: (participantId: string, isMuted: boolean, isTeacher: boolean) => void
|
onMuteParticipant: (participantId: string, isMuted: boolean, isTeacher: boolean) => void
|
||||||
onKickParticipant: (participantId: string) => void
|
onKickParticipant: (participantId: string) => void
|
||||||
|
onApproveHandRaise: (participantId: string) => void
|
||||||
|
onDismissHandRaise: (participantId: string) => void
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
formatTime: (timestamp: string) => string
|
formatTime: (timestamp: string) => string
|
||||||
formatDuration: (minutes: number) => string
|
formatDuration: (minutes: number) => string
|
||||||
|
|
@ -30,11 +32,16 @@ const ParticipantsPanel: React.FC<ParticipantsPanelProps> = ({
|
||||||
onMuteParticipant,
|
onMuteParticipant,
|
||||||
onKickParticipant,
|
onKickParticipant,
|
||||||
onClose,
|
onClose,
|
||||||
|
onApproveHandRaise,
|
||||||
|
onDismissHandRaise,
|
||||||
formatTime,
|
formatTime,
|
||||||
formatDuration,
|
formatDuration,
|
||||||
}) => {
|
}) => {
|
||||||
const [activeTab, setActiveTab] = useState<'participants' | 'attendance'>('participants')
|
const [activeTab, setActiveTab] = useState<'participants' | 'attendance'>('participants')
|
||||||
|
|
||||||
|
// El kaldıranları bul
|
||||||
|
const handRaised = participants.filter((p) => p.isHandRaised)
|
||||||
|
|
||||||
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">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
|
|
@ -48,6 +55,19 @@ const ParticipantsPanel: React.FC<ParticipantsPanelProps> = ({
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* El kaldıranlar göstergesi */}
|
||||||
|
{user.role === 'teacher' && handRaised.length > 0 && (
|
||||||
|
<div className="mb-2 flex items-center space-x-2 p-2 bg-yellow-50 rounded">
|
||||||
|
<FaHandPaper className="text-yellow-600" />
|
||||||
|
<span className="font-medium text-yellow-800">
|
||||||
|
{handRaised.length} kişi el kaldırdı:
|
||||||
|
</span>
|
||||||
|
<span className="text-yellow-900 text-sm truncate">
|
||||||
|
{handRaised.map((p) => p.name).join(', ')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Tab Navigation */}
|
{/* Tab Navigation */}
|
||||||
<div className="flex space-x-1 bg-gray-100 rounded-lg p-1">
|
<div className="flex space-x-1 bg-gray-100 rounded-lg p-1">
|
||||||
<button
|
<button
|
||||||
|
|
@ -109,34 +129,24 @@ const ParticipantsPanel: React.FC<ParticipantsPanelProps> = ({
|
||||||
</div>
|
</div>
|
||||||
<span className="text-gray-900">{participant.name}</span>
|
<span className="text-gray-900">{participant.name}</span>
|
||||||
|
|
||||||
{/* Hand Raise Indicator */}
|
{/* Hand Raise Indicator & Teacher Control */}
|
||||||
{participant.isHandRaised && (
|
{participant.isHandRaised && (
|
||||||
|
user.role === 'teacher' && !participant.isTeacher ? (
|
||||||
|
<button
|
||||||
|
onClick={() => onDismissHandRaise(participant.id)}
|
||||||
|
className="ml-2 p-1 rounded bg-yellow-100 hover:bg-yellow-200"
|
||||||
|
title="El kaldırmayı kaldır"
|
||||||
|
>
|
||||||
|
<FaHandPaper className="text-yellow-600" />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
<FaHandPaper className="text-yellow-600 ml-2" title="Parmak kaldırdı" />
|
<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">
|
||||||
{/* Hand Raise Controls (Teacher Only) */}
|
{/* Hand Raise Controls kaldırıldı, kontrol yukarıya taşındı */}
|
||||||
{user.role === 'teacher' &&
|
|
||||||
!participant.isTeacher &&
|
|
||||||
participant.isHandRaised && (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
onClick={() => console.log('approveHandRaise', 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={() => console.log('dismissHandRaise', participant.id)}
|
|
||||||
className="p-1 text-red-600 hover:bg-red-50 rounded transition-colors"
|
|
||||||
title="El kaldırmayı reddet"
|
|
||||||
>
|
|
||||||
<FaTimes size={12} />
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Mute / Unmute Button */}
|
{/* Mute / Unmute Button */}
|
||||||
{user.role === 'teacher' && !participant.isTeacher && (
|
{user.role === 'teacher' && !participant.isTeacher && (
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,20 @@ const newClassSession: ClassroomDto = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomDetail: React.FC = () => {
|
const RoomDetail: React.FC = () => {
|
||||||
|
// El kaldırma onayla/iptal fonksiyonları en başta tanımlanmalı
|
||||||
|
// (props olarak kullanılmadan önce)
|
||||||
|
const handleApproveHandRaise = async (participantId: string) => {
|
||||||
|
if (signalRServiceRef.current && user.role === 'teacher') {
|
||||||
|
await signalRServiceRef.current.approveHandRaise(classSession.id, participantId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDismissHandRaise = async (participantId: string) => {
|
||||||
|
if (signalRServiceRef.current && user.role === 'teacher') {
|
||||||
|
await signalRServiceRef.current.dismissHandRaise(classSession.id, participantId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { user } = useStoreState((state) => state.auth)
|
const { user } = useStoreState((state) => state.auth)
|
||||||
|
|
@ -119,7 +133,7 @@ const RoomDetail: React.FC = () => {
|
||||||
const [selectedRecipient, setSelectedRecipient] = useState<{ id: string; name: string } | null>(
|
const [selectedRecipient, setSelectedRecipient] = useState<{ id: string; name: string } | null>(
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
const [dragOver, setDragOver] = useState(false)
|
const raisedHandsCount = participants.filter((p) => p.isHandRaised).length
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||||
const [classSettings, setClassSettings] = useState<ClassroomSettingsDto>({
|
const [classSettings, setClassSettings] = useState<ClassroomSettingsDto>({
|
||||||
|
|
@ -298,7 +312,7 @@ const RoomDetail: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ öğretmen ise her zaman offer başlatır
|
// ✅ öğretmen ise her zaman offer başlatır
|
||||||
if (isTeacher || (isActive && user.id < userId)) {
|
if (isActive && user.id < userId) {
|
||||||
const offer = await webRTCServiceRef.current!.createOffer(userId)
|
const offer = await webRTCServiceRef.current!.createOffer(userId)
|
||||||
await signalRServiceRef.current?.sendOffer(classSession.id, userId, offer)
|
await signalRServiceRef.current?.sendOffer(classSession.id, userId, offer)
|
||||||
}
|
}
|
||||||
|
|
@ -724,6 +738,9 @@ const RoomDetail: React.FC = () => {
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ...existing code...
|
||||||
|
// ...existing code...
|
||||||
|
// --- yan panel fonksiyonu ---
|
||||||
const renderSidePanel = () => {
|
const renderSidePanel = () => {
|
||||||
if (!activeSidePanel) return null
|
if (!activeSidePanel) return null
|
||||||
|
|
||||||
|
|
@ -754,12 +771,15 @@ const RoomDetail: React.FC = () => {
|
||||||
attendanceRecords={attendanceRecords}
|
attendanceRecords={attendanceRecords}
|
||||||
onMuteParticipant={handleMuteParticipant}
|
onMuteParticipant={handleMuteParticipant}
|
||||||
onKickParticipant={handleKickParticipant}
|
onKickParticipant={handleKickParticipant}
|
||||||
|
onApproveHandRaise={handleApproveHandRaise}
|
||||||
|
onDismissHandRaise={handleDismissHandRaise}
|
||||||
onClose={() => setActiveSidePanel(null)}
|
onClose={() => setActiveSidePanel(null)}
|
||||||
formatTime={formatTime}
|
formatTime={formatTime}
|
||||||
formatDuration={formatDuration}
|
formatDuration={formatDuration}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ...existing code...
|
||||||
case 'documents':
|
case 'documents':
|
||||||
return (
|
return (
|
||||||
<DocumentsPanel
|
<DocumentsPanel
|
||||||
|
|
@ -1022,7 +1042,16 @@ const RoomDetail: React.FC = () => {
|
||||||
}}
|
}}
|
||||||
className={`flex items-center space-x-2 p-3 rounded-lg transition-all text-base ${activeSidePanel === 'participants' ? 'bg-blue-100 text-blue-700' : 'hover:bg-gray-100 text-gray-700'}`}
|
className={`flex items-center space-x-2 p-3 rounded-lg transition-all text-base ${activeSidePanel === 'participants' ? 'bg-blue-100 text-blue-700' : 'hover:bg-gray-100 text-gray-700'}`}
|
||||||
>
|
>
|
||||||
<FaUserFriends /> <span>Katılımcılar</span>
|
<FaUserFriends />
|
||||||
|
<span>Katılımcılar</span>
|
||||||
|
<span className="absolute -top-1 -right-1 bg-blue-500 text-white text-xs rounded-full w-4 h-4 flex items-center justify-center text-[10px]">
|
||||||
|
{participants.length}
|
||||||
|
</span>
|
||||||
|
{raisedHandsCount > 0 && (
|
||||||
|
<span className="absolute -top-1 -right-6 bg-yellow-500 text-white text-xs rounded-full w-4 h-4 flex items-center justify-center text-[10px]">
|
||||||
|
{raisedHandsCount > 9 ? '9+' : raisedHandsCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -1241,6 +1270,14 @@ const RoomDetail: React.FC = () => {
|
||||||
title="Katılımcılar"
|
title="Katılımcılar"
|
||||||
>
|
>
|
||||||
<FaUserFriends size={14} />
|
<FaUserFriends size={14} />
|
||||||
|
<span className="absolute -top-1 -right-1 bg-blue-500 text-white text-xs rounded-full w-4 h-4 flex items-center justify-center text-[10px]">
|
||||||
|
{participants.length}
|
||||||
|
</span>
|
||||||
|
{raisedHandsCount > 0 && (
|
||||||
|
<span className="absolute -top-1 -right-6 bg-yellow-500 text-white text-xs rounded-full w-4 h-4 flex items-center justify-center text-[10px]">
|
||||||
|
{raisedHandsCount > 9 ? '9+' : raisedHandsCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Teacher Only Options */}
|
{/* Teacher Only Options */}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue