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[]
|
||||
onMuteParticipant: (participantId: string, isMuted: boolean, isTeacher: boolean) => void
|
||||
onKickParticipant: (participantId: string) => void
|
||||
onApproveHandRaise: (participantId: string) => void
|
||||
onDismissHandRaise: (participantId: string) => void
|
||||
onClose: () => void
|
||||
formatTime: (timestamp: string) => string
|
||||
formatDuration: (minutes: number) => string
|
||||
|
|
@ -30,11 +32,16 @@ const ParticipantsPanel: React.FC<ParticipantsPanelProps> = ({
|
|||
onMuteParticipant,
|
||||
onKickParticipant,
|
||||
onClose,
|
||||
onApproveHandRaise,
|
||||
onDismissHandRaise,
|
||||
formatTime,
|
||||
formatDuration,
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState<'participants' | 'attendance'>('participants')
|
||||
|
||||
// El kaldıranları bul
|
||||
const handRaised = participants.filter((p) => p.isHandRaised)
|
||||
|
||||
return (
|
||||
<div className="h-full bg-white flex flex-col text-gray-900">
|
||||
{/* Header */}
|
||||
|
|
@ -48,6 +55,19 @@ const ParticipantsPanel: React.FC<ParticipantsPanelProps> = ({
|
|||
</button>
|
||||
</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 */}
|
||||
<div className="flex space-x-1 bg-gray-100 rounded-lg p-1">
|
||||
<button
|
||||
|
|
@ -109,34 +129,24 @@ const ParticipantsPanel: React.FC<ParticipantsPanelProps> = ({
|
|||
</div>
|
||||
<span className="text-gray-900">{participant.name}</span>
|
||||
|
||||
{/* Hand Raise Indicator */}
|
||||
{participant.isHandRaised && (
|
||||
<FaHandPaper className="text-yellow-600 ml-2" title="Parmak kaldırdı" />
|
||||
)}
|
||||
{/* Hand Raise Indicator & Teacher Control */}
|
||||
{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ı" />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-1">
|
||||
{/* Hand Raise Controls (Teacher Only) */}
|
||||
{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>
|
||||
</>
|
||||
)}
|
||||
{/* Hand Raise Controls kaldırıldı, kontrol yukarıya taşındı */}
|
||||
|
||||
{/* Mute / Unmute Button */}
|
||||
{user.role === 'teacher' && !participant.isTeacher && (
|
||||
|
|
|
|||
|
|
@ -83,6 +83,20 @@ const newClassSession: ClassroomDto = {
|
|||
}
|
||||
|
||||
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 navigate = useNavigate()
|
||||
const { user } = useStoreState((state) => state.auth)
|
||||
|
|
@ -119,7 +133,7 @@ const RoomDetail: React.FC = () => {
|
|||
const [selectedRecipient, setSelectedRecipient] = useState<{ id: string; name: string } | null>(
|
||||
null,
|
||||
)
|
||||
const [dragOver, setDragOver] = useState(false)
|
||||
const raisedHandsCount = participants.filter((p) => p.isHandRaised).length
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||
const [classSettings, setClassSettings] = useState<ClassroomSettingsDto>({
|
||||
|
|
@ -298,7 +312,7 @@ const RoomDetail: React.FC = () => {
|
|||
}
|
||||
|
||||
// ✅ öğ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)
|
||||
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]
|
||||
}
|
||||
|
||||
// ...existing code...
|
||||
// ...existing code...
|
||||
// --- yan panel fonksiyonu ---
|
||||
const renderSidePanel = () => {
|
||||
if (!activeSidePanel) return null
|
||||
|
||||
|
|
@ -754,12 +771,15 @@ const RoomDetail: React.FC = () => {
|
|||
attendanceRecords={attendanceRecords}
|
||||
onMuteParticipant={handleMuteParticipant}
|
||||
onKickParticipant={handleKickParticipant}
|
||||
onApproveHandRaise={handleApproveHandRaise}
|
||||
onDismissHandRaise={handleDismissHandRaise}
|
||||
onClose={() => setActiveSidePanel(null)}
|
||||
formatTime={formatTime}
|
||||
formatDuration={formatDuration}
|
||||
/>
|
||||
)
|
||||
|
||||
// ...existing code...
|
||||
case 'documents':
|
||||
return (
|
||||
<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'}`}
|
||||
>
|
||||
<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
|
||||
onClick={() => {
|
||||
|
|
@ -1241,6 +1270,14 @@ const RoomDetail: React.FC = () => {
|
|||
title="Katılımcılar"
|
||||
>
|
||||
<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>
|
||||
|
||||
{/* Teacher Only Options */}
|
||||
|
|
|
|||
Loading…
Reference in a new issue