RoomDetail komponentlere ayrıldı

This commit is contained in:
Sedat ÖZTÜRK 2025-08-29 16:20:51 +03:00
parent 823ee98384
commit 18b65e6c8a
7 changed files with 905 additions and 811 deletions

View file

@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "index.html",
"revision": "0.48gll4p3s3o"
"revision": "0.d148klvmpj8"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

View file

@ -0,0 +1,188 @@
import React, { useRef, useEffect } from 'react'
import { FaTimes, FaUsers, FaUser, FaBullhorn, FaPaperPlane } from 'react-icons/fa'
import { ClassroomChatDto, ClassroomParticipantDto } from '@/proxy/classroom/models'
interface ChatPanelProps {
user: { id: string; name: string; role: string }
participants: ClassroomParticipantDto[]
chatMessages: ClassroomChatDto[]
newMessage: string
setNewMessage: (msg: string) => void
messageMode: 'public' | 'private' | 'announcement'
setMessageMode: (mode: 'public' | 'private' | 'announcement') => void
selectedRecipient: { id: string; name: string } | null
setSelectedRecipient: (recipient: { id: string; name: string } | null) => void
onSendMessage: (e: React.FormEvent) => void
onClose: () => void
formatTime: (timestamp: string) => string
}
const ChatPanel: React.FC<ChatPanelProps> = ({
user,
participants,
chatMessages,
newMessage,
setNewMessage,
messageMode,
setMessageMode,
selectedRecipient,
setSelectedRecipient,
onSendMessage,
onClose,
formatTime,
}) => {
const messagesEndRef = useRef<HTMLDivElement>(null)
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [chatMessages])
const availableRecipients = participants.filter((p) => p.id !== user.id)
return (
<div className="h-full bg-white flex flex-col text-gray-900">
{/* Header */}
<div className="p-3 sm:p-4 border-b border-gray-200 flex justify-between items-center">
<h3 className="text-base sm:text-lg font-semibold">Sohbet</h3>
<button onClick={onClose} className="p-1 hover:bg-gray-100 rounded lg:hidden">
<FaTimes className="text-gray-500" size={16} />
</button>
</div>
{/* Mesaj Modu */}
<div className="p-3 border-b border-gray-200 bg-gray-50">
<div className="flex space-x-2 mb-2">
<button
onClick={() => {
setMessageMode('public')
setSelectedRecipient(null)
}}
className={`flex items-center space-x-1 px-3 py-1 rounded-full text-xs ${
messageMode === 'public'
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
<FaUsers size={12} />
<span>Herkese</span>
</button>
<button
onClick={() => setMessageMode('private')}
className={`flex items-center space-x-1 px-3 py-1 rounded-full text-xs ${
messageMode === 'private'
? 'bg-green-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
<FaUser size={12} />
<span>Özel</span>
</button>
{user.role === 'teacher' && (
<button
onClick={() => {
setMessageMode('announcement')
setSelectedRecipient(null)
}}
className={`flex items-center space-x-1 px-3 py-1 rounded-full text-xs ${
messageMode === 'announcement'
? 'bg-red-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
<FaBullhorn size={12} />
<span>Duyuru</span>
</button>
)}
</div>
{messageMode === 'private' && (
<select
value={selectedRecipient?.id || ''}
onChange={(e) => {
const recipient = availableRecipients.find((p) => p.id === e.target.value)
setSelectedRecipient(recipient ? { id: recipient.id, name: recipient.name } : null)
}}
className="w-full px-2 py-1 text-xs border border-gray-300 rounded"
>
<option value="">Kişi seçin...</option>
{availableRecipients.map((p) => (
<option key={p.id} value={p.id}>
{p.name} {p.isTeacher ? '(Öğretmen)' : ''}
</option>
))}
</select>
)}
</div>
{/* Mesaj Listesi */}
<div className="flex-1 overflow-y-auto p-4 space-y-3">
{chatMessages.length === 0 ? (
<div className="text-center text-gray-500 text-sm">Henüz mesaj yok.</div>
) : (
chatMessages.map((m) => (
<div
key={m.id}
className={`${
m.messageType === 'announcement'
? 'w-full'
: m.senderId === user.id
? 'flex justify-end'
: 'flex justify-start'
}`}
>
<div
className={`max-w-xs px-3 py-2 rounded-lg ${
m.messageType === 'announcement'
? 'bg-red-100 text-red-800 border border-red-200 w-full text-center'
: m.messageType === 'private'
? m.senderId === user.id
? 'bg-green-600 text-white'
: 'bg-green-100 text-green-800 border'
: m.senderId === user.id
? 'bg-blue-600 text-white'
: m.isTeacher
? 'bg-yellow-100 text-yellow-800 border'
: 'bg-gray-100 text-gray-800'
}`}
>
{m.senderId !== user.id && (
<div className="text-xs font-semibold mb-1">
{m.senderName}
{m.isTeacher && ' (Öğretmen)'}
</div>
)}
<div className="text-sm">{m.message}</div>
<div className="text-xs mt-1 opacity-75">{formatTime(m.timestamp)}</div>
</div>
</div>
))
)}
<div ref={messagesEndRef} />
</div>
{/* Mesaj Gönderme */}
<form onSubmit={onSendMessage} className="p-4 border-t border-gray-200">
<div className="flex space-x-2">
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder="Mesaj yaz..."
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm"
/>
<button
type="submit"
disabled={!newMessage.trim()}
className="px-3 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400"
>
<FaPaperPlane size={16} />
</button>
</div>
</form>
</div>
)
}
export default ChatPanel

View file

@ -0,0 +1,153 @@
import React, { useRef, useState } from 'react'
import { FaTimes, FaFile, FaEye, FaDownload, FaTrash } from 'react-icons/fa'
import { ClassDocumentDto } from '@/proxy/classroom/models'
interface DocumentsPanelProps {
user: { role: string; name: string }
documents: ClassDocumentDto[]
onUpload: (file: File) => void
onDelete: (id: string) => void
onView: (doc: ClassDocumentDto) => void
onClose: () => void
formatFileSize: (bytes: number) => string
getFileIcon: (type: string) => JSX.Element
}
const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
user,
documents,
onUpload,
onDelete,
onView,
onClose,
formatFileSize,
getFileIcon,
}) => {
const fileInputRef = useRef<HTMLInputElement>(null)
const [dragOver, setDragOver] = useState(false)
const handleDrop = (e: React.DragEvent) => {
e.preventDefault()
setDragOver(false)
if (user.role !== 'teacher') return
const files = Array.from(e.dataTransfer.files)
files.forEach((file) => onUpload(file))
}
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
if (user.role !== 'teacher') return
const files = Array.from(e.target.files || [])
files.forEach((file) => onUpload(file))
if (fileInputRef.current) fileInputRef.current.value = ''
}
return (
<div className="h-full bg-white flex flex-col text-gray-900">
{/* Header */}
<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">Sınıf Dokümanları</h3>
<button onClick={onClose}>
<FaTimes className="text-gray-500" />
</button>
</div>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-4">
{/* Upload Area (Teacher Only) */}
{user.role === 'teacher' && (
<div
className={`border-2 border-dashed rounded-lg p-6 mb-4 text-center transition-colors ${
dragOver ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-gray-400'
}`}
onDrop={handleDrop}
onDragOver={(e) => {
e.preventDefault()
setDragOver(true)
}}
onDragLeave={() => setDragOver(false)}
>
<FaFile size={32} className="mx-auto text-gray-400 mb-2" />
<p className="text-sm font-medium text-gray-700 mb-2">Doküman Yükle</p>
<p className="text-xs text-gray-500 mb-3">Dosyaları buraya sürükleyin veya seçin</p>
<button
onClick={() => fileInputRef.current?.click()}
className="px-3 py-1 bg-blue-600 text-white rounded text-sm hover:bg-blue-700 transition-colors"
>
Dosya Seç
</button>
<input
ref={fileInputRef}
type="file"
multiple
onChange={handleFileSelect}
className="hidden"
accept=".pdf,.doc,.docx,.ppt,.pptx,.jpg,.jpeg,.png,.gif,.odp"
/>
</div>
)}
{/* Documents List */}
{documents.length === 0 ? (
<div className="text-center py-8 text-gray-500">
<FaFile size={32} className="mx-auto mb-4 text-gray-300" />
<p className="text-sm">Henüz doküman yüklenmemiş.</p>
</div>
) : (
<div className="space-y-3">
{documents.map((doc) => (
<div
key={doc.id}
className="flex items-center justify-between p-3 border border-gray-200 rounded-lg hover:shadow-sm transition-shadow"
>
<div className="flex items-center space-x-3 min-w-0 flex-1">
<div className="text-lg flex-shrink-0">{getFileIcon(doc.type)}</div>
<div className="min-w-0 flex-1">
<h4 className="font-medium text-gray-800 text-sm truncate">{doc.name}</h4>
<p className="text-xs text-gray-600">
{formatFileSize(doc.size)} {' '}
{new Date(doc.uploadedAt).toLocaleDateString('tr-TR')}
</p>
<p className="text-xs text-gray-500">{doc.uploadedBy}</p>
</div>
</div>
<div className="flex items-center space-x-1 flex-shrink-0">
<button
onClick={() => onView(doc)}
className="p-1 text-blue-600 hover:bg-blue-50 rounded transition-colors"
title="Görüntüle"
>
<FaEye size={12} />
</button>
<a
href={doc.url}
download={doc.name}
className="p-1 text-green-600 hover:bg-green-50 rounded transition-colors"
title="İndir"
>
<FaDownload size={12} />
</a>
{user.role === 'teacher' && (
<button
onClick={() => onDelete(doc.id)}
className="p-1 text-red-600 hover:bg-red-50 rounded transition-colors"
title="Sil"
>
<FaTrash size={12} />
</button>
)}
</div>
</div>
))}
</div>
)}
</div>
</div>
)
}
export default DocumentsPanel

View file

@ -0,0 +1,112 @@
import React from 'react'
import { FaTimes, FaTh, FaExpand, FaDesktop, FaUsers } from 'react-icons/fa'
import { VideoLayoutDto } from '@/proxy/classroom/models'
interface LayoutPanelProps {
layouts: VideoLayoutDto[]
currentLayout: VideoLayoutDto
onChangeLayout: (layout: VideoLayoutDto) => void
onClose: () => void
}
const getLayoutIcon = (type: string) => {
switch (type) {
case 'grid':
return <FaTh size={24} />
case 'speaker':
return <FaExpand size={24} />
case 'presentation':
return <FaDesktop size={24} />
case 'sidebar':
return <FaUsers size={24} />
case 'teacher-focus':
return <FaDesktop size={24} />
default:
return <FaTh size={24} />
}
}
const LayoutPanel: React.FC<LayoutPanelProps> = ({
layouts,
currentLayout,
onChangeLayout,
onClose,
}) => {
return (
<div className="h-full bg-white flex flex-col text-gray-900">
<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">Video Layout Seçin</h3>
<button onClick={onClose}>
<FaTimes className="text-gray-500" />
</button>
</div>
</div>
<div className="flex-1 overflow-y-auto p-2">
<div className="space-y-3">
{layouts.map((layout) => (
<button
key={layout.id}
onClick={() => onChangeLayout(layout)}
className={`w-full p-4 rounded-lg border-2 transition-all text-left ${
currentLayout.id === layout.id
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-blue-300 hover:bg-gray-50'
}`}
>
<div className="flex items-center space-x-3 mb-2">
<div
className={`p-2 rounded-full ${
currentLayout.id === layout.id
? 'bg-blue-100 text-blue-600'
: 'bg-gray-100 text-gray-600'
}`}
>
{getLayoutIcon(layout.type)}
</div>
<div>
<h4 className="font-medium text-gray-900 text-sm">{layout.name}</h4>
<p className="text-xs text-gray-600">{layout.description}</p>
</div>
</div>
{/* Layout Preview */}
<div className="bg-gray-100 rounded p-3 h-16 flex items-center justify-center">
{layout.type === 'grid' && (
<div className="grid grid-cols-2 gap-1">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="w-4 h-3 bg-blue-300 rounded"></div>
))}
</div>
)}
{layout.type === 'sidebar' && (
<div className="flex items-center space-x-2">
<div className="w-8 h-6 bg-blue-500 rounded"></div>
<div className="grid grid-cols-3 gap-1">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="w-1 h-1 bg-blue-300 rounded"></div>
))}
</div>
</div>
)}
{layout.type === 'teacher-focus' && (
<div className="space-y-1">
<div className="w-12 h-4 bg-green-500 rounded"></div>
<div className="flex space-x-1">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="w-1 h-1 bg-blue-300 rounded"></div>
))}
</div>
</div>
)}
</div>
</button>
))}
</div>
</div>
</div>
)
}
export default LayoutPanel

View file

@ -0,0 +1,218 @@
import React, { useState } from 'react'
import {
FaTimes,
FaUsers,
FaClipboardList,
FaHandPaper,
FaCheck,
FaMicrophone,
FaMicrophoneSlash,
FaVideoSlash,
FaUserTimes,
} from 'react-icons/fa'
import { ClassroomParticipantDto, ClassroomAttendanceDto } from '@/proxy/classroom/models'
interface ParticipantsPanelProps {
user: { id: string; name: string; role: string }
participants: ClassroomParticipantDto[]
attendanceRecords: ClassroomAttendanceDto[]
onMuteParticipant: (participantId: string, isMuted: boolean, isTeacher: boolean) => void
onKickParticipant: (participantId: string) => void
onClose: () => void
formatTime: (timestamp: string) => string
formatDuration: (minutes: number) => string
}
const ParticipantsPanel: React.FC<ParticipantsPanelProps> = ({
user,
participants,
attendanceRecords,
onMuteParticipant,
onKickParticipant,
onClose,
formatTime,
formatDuration,
}) => {
const [activeTab, setActiveTab] = useState<'participants' | 'attendance'>('participants')
return (
<div className="h-full bg-white flex flex-col text-gray-900">
{/* Header */}
<div className="p-4 border-b border-gray-200">
<div className="flex items-center justify-between mb-3">
<h3 className="text-lg font-semibold text-gray-900">
Katılımcılar ({participants.length + 1})
</h3>
<button onClick={onClose}>
<FaTimes className="text-gray-500" />
</button>
</div>
{/* Tab Navigation */}
<div className="flex space-x-1 bg-gray-100 rounded-lg p-1">
<button
onClick={() => setActiveTab('participants')}
className={`flex-1 px-3 py-2 text-sm font-medium rounded-md transition-all ${
activeTab === 'participants'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<FaUsers className="inline mr-1" size={14} />
Katılımcılar
</button>
{user.role === 'teacher' && (
<button
onClick={() => setActiveTab('attendance')}
className={`flex-1 px-3 py-2 text-sm font-medium rounded-md transition-all ${
activeTab === 'attendance'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<FaClipboardList className="inline mr-1" size={14} />
Katılım Raporu
</button>
)}
</div>
</div>
{/* Participants Tab */}
{activeTab === 'participants' && (
<div className="flex-1 overflow-y-auto p-2">
<div className="space-y-2">
{/* Current User */}
<div className="flex items-center justify-between p-2 rounded-lg bg-blue-50">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center text-white text-sm font-semibold">
{user.name.charAt(0)}
</div>
<span className="text-gray-900">{user.name} (Siz)</span>
</div>
{user.role === 'teacher' && (
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">
Öğretmen
</span>
)}
</div>
{/* Other Participants */}
{participants.map((participant) => (
<div
key={participant.id}
className="flex items-center justify-between p-2 rounded-lg hover:bg-gray-50"
>
<div className="flex items-center space-x-3">
<div className="w-8 h-8 bg-gray-500 rounded-full flex items-center justify-center text-white text-sm font-semibold">
{participant.name.charAt(0)}
</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ı" />
)}
</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>
</>
)}
{/* Mute / Unmute Button */}
{user.role === 'teacher' && !participant.isTeacher && (
<button
onClick={() =>
onMuteParticipant(participant.id, !participant.isAudioMuted, true)
}
className={`p-1 rounded transition-colors ${
participant.isAudioMuted
? 'text-green-600 hover:bg-green-50'
: 'text-yellow-600 hover:bg-yellow-50'
}`}
title={participant.isAudioMuted ? 'Sesi Aç' : 'Sesi Kapat'}
>
{participant.isAudioMuted ? <FaMicrophone /> : <FaMicrophoneSlash />}
</button>
)}
{/* Video muted indicator */}
{participant.isVideoMuted && <FaVideoSlash className="text-red-500 text-sm" />}
{participant.isTeacher && (
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">
Öğretmen
</span>
)}
{/* Kick Button (Teacher Only) */}
{user.role === 'teacher' && !participant.isTeacher && (
<button
onClick={() => onKickParticipant(participant.id)}
className="p-1 text-red-600 hover:bg-red-50 rounded transition-colors"
title="Sınıftan Çıkar"
>
<FaUserTimes size={12} />
</button>
)}
</div>
</div>
))}
</div>
</div>
)}
{/* Attendance Tab */}
{activeTab === 'attendance' && (
<div className="flex-1 overflow-y-auto p-4">
{attendanceRecords.length === 0 ? (
<div className="text-center py-8 text-gray-500">
<FaClipboardList size={32} className="mx-auto mb-4 text-gray-300" />
<p className="text-sm text-gray-600">Henüz katılım kaydı bulunmamaktadır.</p>
</div>
) : (
<div className="space-y-3">
{attendanceRecords.map((record) => (
<div key={record.id} className="p-3 border border-gray-200 rounded-lg">
<div className="flex items-center justify-between mb-2">
<h4 className="font-medium text-gray-800">{record.studentName}</h4>
<span className="text-sm font-semibold text-blue-600">
{formatDuration(record.totalDurationMinutes)}
</span>
</div>
<div className="text-xs text-gray-600 space-y-1">
<div>Giriş: {formatTime(record.joinTime)}</div>
<div>
Çıkış: {record.leaveTime ? formatTime(record.leaveTime) : 'Devam ediyor'}
</div>
</div>
</div>
))}
</div>
)}
</div>
)}
</div>
)
}
export default ParticipantsPanel

View file

@ -0,0 +1,164 @@
import React from 'react'
import { FaTimes } from 'react-icons/fa'
import { ClassroomSettingsDto } from '@/proxy/classroom/models'
interface SettingsPanelProps {
user: { role: string }
classSettings: ClassroomSettingsDto
onSettingsChange: (newSettings: Partial<ClassroomSettingsDto>) => void
onClose: () => void
}
const SettingsPanel: React.FC<SettingsPanelProps> = ({
user,
classSettings,
onSettingsChange,
onClose,
}) => {
return (
<div className="h-full bg-white flex flex-col text-gray-900">
<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">Sınıf Ayarları</h3>
<button onClick={onClose}>
<FaTimes className="text-gray-500" />
</button>
</div>
</div>
<div className="flex-1 overflow-y-auto p-4">
<div className="space-y-6">
{/* Katılımcı Davranışları */}
<div>
<h4 className="font-semibold text-gray-800 mb-3">Katılımcı İzinleri</h4>
<div className="space-y-3">
<label className="flex items-center justify-between">
<span className="text-sm text-gray-700">Parmak kaldırma izni</span>
<input
type="checkbox"
checked={classSettings.allowHandRaise}
onChange={(e) => onSettingsChange({ allowHandRaise: e.target.checked })}
className="rounded"
disabled={user.role !== 'teacher'}
/>
</label>
<label className="flex items-center justify-between">
<span className="text-sm text-gray-700">Öğrenci sohbet izni</span>
<input
type="checkbox"
checked={classSettings.allowStudentChat}
onChange={(e) => onSettingsChange({ allowStudentChat: e.target.checked })}
className="rounded"
disabled={user.role !== 'teacher'}
/>
</label>
<label className="flex items-center justify-between">
<span className="text-sm">Özel mesaj izni</span>
<input
type="checkbox"
checked={classSettings.allowPrivateMessages}
onChange={(e) => onSettingsChange({ allowPrivateMessages: e.target.checked })}
className="rounded"
disabled={user.role !== 'teacher'}
/>
</label>
<label className="flex items-center justify-between">
<span className="text-sm">Öğrenci ekran paylaşımı</span>
<input
type="checkbox"
checked={classSettings.allowStudentScreenShare}
onChange={(e) => onSettingsChange({ allowStudentScreenShare: e.target.checked })}
className="rounded"
disabled={user.role !== 'teacher'}
/>
</label>
</div>
</div>
{/* Varsayılan Ayarlar */}
<div>
<h4 className="font-semibold text-gray-800 mb-3">Varsayılan Ayarlar</h4>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Varsayılan mikrofon durumu
</label>
<select
value={classSettings.defaultMicrophoneState}
onChange={(e) =>
onSettingsChange({
defaultMicrophoneState: e.target.value as 'muted' | 'unmuted',
})
}
className="w-full p-2 border border-gray-300 rounded-md text-sm"
disabled={user.role !== 'teacher'}
>
<option value="muted">Kapalı</option>
<option value="unmuted">ık</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Varsayılan kamera durumu
</label>
<select
value={classSettings.defaultCameraState}
onChange={(e) =>
onSettingsChange({
defaultCameraState: e.target.value as 'on' | 'off',
})
}
className="w-full p-2 border border-gray-300 rounded-md text-sm"
disabled={user.role !== 'teacher'}
>
<option value="on">ık</option>
<option value="off">Kapalı</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Varsayılan layout
</label>
<select
value={classSettings.defaultLayout}
onChange={(e) => onSettingsChange({ defaultLayout: e.target.value })}
className="w-full p-2 border border-gray-300 rounded-md text-sm"
disabled={user.role !== 'teacher'}
>
<option value="grid">Izgara Görünümü</option>
<option value="sidebar">Sunum Modu</option>
<option value="teacher-focus">Öğretmen Odaklı</option>
<option value="interview">Karşılıklı Görüşme</option>
</select>
</div>
</div>
</div>
{/* Otomatik Ayarlar */}
<div>
<h4 className="font-semibold text-gray-800 mb-3">Otomatik Ayarlar</h4>
<div className="space-y-3">
<label className="flex items-center justify-between">
<span className="text-sm text-gray-700">Yeni katılımcıları otomatik sustur</span>
<input
type="checkbox"
checked={classSettings.autoMuteNewParticipants}
onChange={(e) => onSettingsChange({ autoMuteNewParticipants: e.target.checked })}
className="rounded"
disabled={user.role !== 'teacher'}
/>
</label>
</div>
</div>
{user.role !== 'teacher' && (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
<p className="text-sm text-yellow-800"> Ayarları sadece öğretmen değiştirebilir.</p>
</div>
)}
</div>
</div>
</div>
)
}
export default SettingsPanel

View file

@ -63,6 +63,11 @@ import { endClassroom } from '@/services/classroom.service'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { Helmet } from 'react-helmet'
import { useLocalization } from '@/utils/hooks/useLocalization'
import ChatPanel from '@/components/classroom/ChatPanel'
import ParticipantsPanel from '@/components/classroom/ParticipantsPanel'
import DocumentsPanel from '@/components/classroom/DocumentsPanel'
import LayoutPanel from '@/components/classroom/LayoutPanel'
import SettingsPanel from '@/components/classroom/SettingsPanel'
type SidePanelType =
| 'chat'
@ -659,822 +664,76 @@ const RoomDetail: React.FC = () => {
const renderSidePanel = () => {
if (!activeSidePanel) return null
const panelContent = () => {
switch (activeSidePanel) {
case 'chat': {
const availableRecipients = participants.filter((p) => p.id !== user.id)
return (
<div className="h-full bg-white flex flex-col text-gray-900">
<div className="p-3 sm:p-4 border-b border-gray-200">
<div className="flex items-center justify-between">
<h3 className="text-base sm:text-lg font-semibold text-gray-900">
Sınıf Sohbeti
</h3>
<button
onClick={() => setActiveSidePanel(null)}
className="p-1 hover:bg-gray-100 rounded lg:hidden"
>
<FaTimes className="text-gray-500" size={16} />
</button>
</div>
</div>
switch (activeSidePanel) {
case 'chat':
return (
<ChatPanel
user={user}
participants={participants}
chatMessages={chatMessages}
newMessage={newMessage}
setNewMessage={setNewMessage}
messageMode={messageMode}
setMessageMode={setMessageMode}
selectedRecipient={selectedRecipient}
setSelectedRecipient={setSelectedRecipient}
onSendMessage={handleSendMessage}
onClose={() => setActiveSidePanel(null)}
formatTime={formatTime}
/>
)
{/* Message Mode Selector */}
<div className="p-3 border-b border-gray-200 bg-gray-50">
<div className="flex space-x-2 mb-2">
<button
onClick={() => {
setMessageMode('public')
setSelectedRecipient(null)
}}
className={`flex items-center space-x-1 px-3 py-1 rounded-full text-xs ${
messageMode === 'public'
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
<FaUsers size={12} />
<span>Herkese</span>
</button>
case 'participants':
return (
<ParticipantsPanel
user={user}
participants={participants}
attendanceRecords={attendanceRecords}
onMuteParticipant={handleMuteParticipant}
onKickParticipant={handleKickParticipant}
onClose={() => setActiveSidePanel(null)}
formatTime={formatTime}
formatDuration={formatDuration}
/>
)
<button
onClick={() => setMessageMode('private')}
className={`flex items-center space-x-1 px-3 py-1 rounded-full text-xs ${
messageMode === 'private'
? 'bg-green-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
<FaUser size={12} />
<span>Özel</span>
</button>
case 'documents':
return (
<DocumentsPanel
user={user}
documents={documents}
onUpload={handleUploadDocument}
onDelete={handleDeleteDocument}
onView={handleViewDocument}
onClose={() => setActiveSidePanel(null)}
formatFileSize={formatFileSize}
getFileIcon={getFileIcon}
/>
)
{user.role === 'teacher' && (
<button
onClick={() => {
setMessageMode('announcement')
setSelectedRecipient(null)
}}
className={`flex items-center space-x-1 px-3 py-1 rounded-full text-xs ${
messageMode === 'announcement'
? 'bg-red-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
<FaBullhorn size={12} />
<span>Duyuru</span>
</button>
)}
</div>
case 'layout':
return (
<LayoutPanel
layouts={layouts}
currentLayout={currentLayout}
onChangeLayout={handleLayoutChange}
onClose={() => setActiveSidePanel(null)}
/>
)
{messageMode === 'private' && (
<select
value={selectedRecipient?.id || ''}
onChange={(e) => {
const recipient = availableRecipients.find((p) => p.id === e.target.value)
setSelectedRecipient(
recipient ? { id: recipient.id, name: recipient.name } : null,
)
}}
className="w-full px-2 py-1 text-xs border border-gray-300 rounded"
>
<option value="">Kişi seçin...</option>
{availableRecipients.map((participant) => (
<option key={participant.id} value={participant.id}>
{participant.name} {participant.isTeacher ? '(Öğretmen)' : ''}
</option>
))}
</select>
)}
</div>
case 'settings':
return (
<SettingsPanel
user={user}
classSettings={classSettings}
onSettingsChange={handleSettingsChange}
onClose={() => setActiveSidePanel(null)}
/>
)
{/* Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-3">
{chatMessages.length === 0 ? (
<div className="text-center text-gray-500 text-sm">
Henüz mesaj bulunmamaktadır.
</div>
) : (
chatMessages.map((message) => (
<div
key={message.id}
className={`${
message.messageType === 'announcement'
? 'w-full'
: message.senderId === user.id
? 'flex justify-end'
: 'flex justify-start'
}`}
>
<div
className={`max-w-xs px-3 py-2 rounded-lg ${
message.messageType === 'announcement'
? 'bg-red-100 text-red-800 border border-red-200 w-full text-center'
: message.messageType === 'private'
? message.senderId === user.id
? 'bg-green-600 text-white'
: 'bg-green-100 text-green-800 border border-green-200'
: message.senderId === user.id
? 'bg-blue-600 text-white'
: message.isTeacher
? 'bg-yellow-100 text-yellow-800 border border-yellow-200'
: 'bg-gray-100 text-gray-800'
}`}
>
{message.senderId !== user.id && (
<div className="text-xs font-semibold mb-1">
{message.senderName}
{message.isTeacher && ' (Öğretmen)'}
{message.messageType === 'private' &&
message.recipientId === user.id &&
' (Size özel)'}
</div>
)}
{message.messageType === 'private' && message.senderId === user.id && (
<div className="text-xs mb-1 opacity-75"> {message.recipientName}</div>
)}
{message.messageType === 'announcement' && (
<div className="text-xs font-semibold mb-1">
📢 DUYURU - {message.senderName}
</div>
)}
<div className="text-sm">{message.message}</div>
<div
className={`text-xs mt-1 opacity-75 ${
message.messageType === 'announcement'
? 'text-red-600'
: message.senderId === user.id
? 'text-white'
: 'text-gray-500'
}`}
>
{formatTime(message.timestamp)}
</div>
</div>
</div>
))
)}
<div ref={messagesEndRef} />
</div>
{/* Message Input */}
<form onSubmit={handleSendMessage} className="p-4 border-t border-gray-200">
<div className="text-xs text-gray-500 mb-2">
{messageMode === 'public' && 'Herkese mesaj gönderiyorsunuz'}
{messageMode === 'private' &&
selectedRecipient &&
`${selectedRecipient.name} kişisine özel mesaj`}
{messageMode === 'private' && !selectedRecipient && 'Önce bir kişi seçin'}
{messageMode === 'announcement' && 'Sınıfa duyuru gönderiyorsunuz'}
</div>
<div className="flex space-x-2">
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder={
messageMode === 'private' && !selectedRecipient
? 'Önce kişi seçin...'
: messageMode === 'announcement'
? 'Duyuru mesajınız...'
: 'Mesajınızı yazın...'
}
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
maxLength={500}
disabled={messageMode === 'private' && !selectedRecipient}
/>
<button
type="submit"
disabled={
!newMessage.trim() || (messageMode === 'private' && !selectedRecipient)
}
className="px-3 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
<FaPaperPlane size={16} />
</button>
</div>
</form>
</div>
)
}
case 'participants': {
return (
<div className="h-full bg-white flex flex-col text-gray-900">
<div className="p-4 border-b border-gray-200">
<div className="flex items-center justify-between mb-3">
<h3 className="text-lg font-semibold text-gray-900">
Katılımcılar ({participants.length + 1})
</h3>
<button onClick={() => setActiveSidePanel(null)}>
<FaTimes className="text-gray-500" />
</button>
</div>
{/* Tab Navigation */}
<div className="flex space-x-1 bg-gray-100 rounded-lg p-1">
<button
onClick={() => setParticipantsActiveTab('participants')}
className={`flex-1 px-3 py-2 text-sm font-medium rounded-md transition-all ${
participantsActiveTab === 'participants'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<FaUsers className="inline mr-1" size={14} />
Katılımcılar
</button>
{user.role === 'teacher' && (
<button
onClick={() => setParticipantsActiveTab('attendance')}
className={`flex-1 px-3 py-2 text-sm font-medium rounded-md transition-all ${
participantsActiveTab === 'attendance'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<FaClipboardList className="inline mr-1" size={14} />
Katılım Raporu
</button>
)}
</div>
</div>
{participantsActiveTab === 'participants' && (
<>
{user.role === 'teacher' && participants.length > 0 && (
<div className="flex">
<button
onClick={async () => {
if (signalRServiceRef.current && user.role === 'teacher') {
const now = new Date()
// Remove all non-teacher participants
const nonTeacherIds = participants
.filter((p) => !p.isTeacher)
.map((p) => p.id)
for (const participantId of nonTeacherIds) {
await signalRServiceRef.current.kickParticipant(
classSession.id,
participantId,
)
}
setParticipants((prev) => prev.filter((p) => p.isTeacher))
setAttendanceRecords((prev) =>
prev.map((r) => {
if (nonTeacherIds.includes(r.studentId) && !r.leaveTime) {
const leaveTime = now.toISOString()
const join = new Date(r.joinTime)
const leave = now
const totalDurationMinutes = Math.max(
1,
Math.round((leave.getTime() - join.getTime()) / 60000),
)
return { ...r, leaveTime, totalDurationMinutes }
}
return r
}),
)
}
}}
className="w-full bg-red-600 hover:bg-red-700 text-white p-2 shadow text-sm transition-all"
>
Tüm Katılımcıları Çıkar
</button>
</div>
)}
<div className="flex-1 overflow-y-auto p-2">
<div className="space-y-2">
{/* Current User */}
<div className="flex items-center justify-between p-2 rounded-lg bg-blue-50">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center text-white text-sm font-semibold">
{user.name.charAt(0)}
</div>
<span className="text-gray-900">{user.name} (Siz)</span>
</div>
<div className="flex items-center space-x-1">
{user.role === 'teacher' && (
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">
Öğretmen
</span>
)}
</div>
</div>
{/* Other Participants */}
{participants.map((participant) => (
<div
key={participant.id}
className="flex items-center justify-between p-2 rounded-lg hover:bg-gray-50"
>
<div className="flex items-center space-x-3">
<div className="w-8 h-8 bg-gray-500 rounded-full flex items-center justify-center text-white text-sm font-semibold">
{participant.name.charAt(0)}
</div>
<span
className="text-gray-900 cursor-pointer hover:underline"
onClick={() => {
setMessageMode('private')
setSelectedRecipient({ id: participant.id, name: participant.name })
setActiveSidePanel('chat')
}}
>
{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
onClick={async () => {
await handleMuteParticipant(
participant.id,
!participant.isAudioMuted,
user.role === 'teacher',
)
}}
className={`p-1 rounded transition-colors ${participant.isAudioMuted ? 'text-green-600 hover:bg-green-50' : 'text-yellow-600 hover:bg-yellow-50'}`}
title={participant.isAudioMuted ? 'Sesi Aç' : 'Sesi Kapat'}
>
{participant.isAudioMuted ? (
<FaMicrophone />
) : (
<FaMicrophoneSlash />
)}
</button>
)}
{/* Video durumu gösterimi */}
{participant.isVideoMuted && (
<FaVideoSlash className="text-red-500 text-sm" />
)}
{participant.isTeacher && (
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">
Öğretmen
</span>
)}
{user.role === 'teacher' && !participant.isTeacher && (
<button
onClick={() =>
setKickingParticipant({
id: participant.id,
name: participant.name,
})
}
className="p-1 text-red-600 hover:bg-red-50 rounded transition-colors"
title="Sınıftan Çıkar"
>
<FaUserTimes size={12} />
</button>
)}
</div>
</div>
))}
</div>
</div>
</>
)}
{participantsActiveTab === 'attendance' && (
<div className="flex-1 overflow-y-auto p-4">
{attendanceRecords.length === 0 ? (
<div className="text-center py-8 text-gray-500">
<FaClipboardList size={32} className="mx-auto mb-4 text-gray-300" />
<p className="text-sm text-gray-600">Henüz katılım kaydı bulunmamaktadır.</p>
</div>
) : (
<div className="space-y-3">
{attendanceRecords.map((record) => (
<div key={record.id} className="p-3 border border-gray-200 rounded-lg">
<div className="flex items-center justify-between mb-2">
<h4 className="font-medium text-gray-800">{record.studentName}</h4>
<span className="text-sm font-semibold text-blue-600">
{formatDuration(record.totalDurationMinutes)}
</span>
</div>
<div className="text-xs text-gray-600 space-y-1">
<div>Giriş: {formatTime(record.joinTime)}</div>
<div>
Çıkış:{' '}
{record.leaveTime ? formatTime(record.leaveTime) : 'Devam ediyor'}
</div>
</div>
</div>
))}
</div>
)}
</div>
)}
</div>
)
}
case 'documents':
return (
<div className="h-full bg-white flex flex-col text-gray-900">
<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">Sınıf Dokümanları</h3>
<button onClick={() => setActiveSidePanel(null)}>
<FaTimes className="text-gray-500" />
</button>
</div>
</div>
<div className="flex-1 overflow-y-auto p-4">
{/* Upload Area (Teacher Only) */}
{user.role === 'teacher' && (
<div
className={`border-2 border-dashed rounded-lg p-6 mb-4 text-center transition-colors ${
dragOver
? 'border-blue-500 bg-blue-50'
: 'border-gray-300 hover:border-gray-400'
}`}
onDrop={handleDrop}
onDragOver={(e) => {
e.preventDefault()
setDragOver(true)
}}
onDragLeave={() => setDragOver(false)}
>
<FaFile size={32} className="mx-auto text-gray-400 mb-2" />
<p className="text-sm font-medium text-gray-700 mb-2">Doküman Yükle</p>
<p className="text-xs text-gray-500 mb-3">
Dosyaları buraya sürükleyin veya seçin
</p>
<button
onClick={() => fileInputRef.current?.click()}
className="px-3 py-1 bg-blue-600 text-white rounded text-sm hover:bg-blue-700 transition-colors"
>
Dosya Seç
</button>
<input
ref={fileInputRef}
type="file"
multiple
onChange={handleFileSelect}
className="hidden"
accept=".pdf,.doc,.docx,.ppt,.pptx,.jpg,.jpeg,.png,.gif,.odp"
/>
</div>
)}
{/* Documents List */}
{documents.length === 0 ? (
<div className="text-center py-8 text-gray-500">
<FaFile size={32} className="mx-auto mb-4 text-gray-300" />
<p className="text-sm">Henüz doküman yüklenmemiş.</p>
</div>
) : (
<div className="space-y-3">
{documents.map((doc) => (
<div
key={doc.id}
className="flex items-center justify-between p-3 border border-gray-200 rounded-lg hover:shadow-sm transition-shadow"
>
<div className="flex items-center space-x-3 min-w-0 flex-1">
<div className="text-lg flex-shrink-0">{getFileIcon(doc.type)}</div>
<div className="min-w-0 flex-1">
<h4 className="font-medium text-gray-800 text-sm truncate">
{doc.name}
</h4>
<p className="text-xs text-gray-600">
{formatFileSize(doc.size)} {' '}
{new Date(doc.uploadedAt).toLocaleDateString('tr-TR')}
</p>
<p className="text-xs text-gray-500">{doc.uploadedBy}</p>
</div>
</div>
<div className="flex items-center space-x-1 flex-shrink-0">
<button
onClick={() => handleViewDocument(doc)}
className="p-1 text-blue-600 hover:bg-blue-50 rounded transition-colors"
title="Görüntüle"
>
<FaEye size={12} />
</button>
<a
href={doc.url}
download={doc.name}
className="p-1 text-green-600 hover:bg-green-50 rounded transition-colors"
title="İndir"
>
<FaDownload size={12} />
</a>
{user.role === 'teacher' && (
<button
onClick={() => handleDeleteDocument(doc.id)}
className="p-1 text-red-600 hover:bg-red-50 rounded transition-colors"
title="Sil"
>
<FaTrash size={12} />
</button>
)}
</div>
</div>
))}
</div>
)}
</div>
</div>
)
case 'layout':
return (
<div className="h-full bg-white flex flex-col text-gray-900">
<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">Video Layout Seçin</h3>
<button onClick={() => setActiveSidePanel(null)}>
<FaTimes className="text-gray-500" />
</button>
</div>
</div>
<div className="flex-1 overflow-y-auto p-2">
<div className="space-y-3">
{layouts.map((layout) => (
<button
key={layout.id}
onClick={() => {
handleLayoutChange(layout)
}}
className={`w-full p-4 rounded-lg border-2 transition-all text-left ${
currentLayout.id === layout.id
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-blue-300 hover:bg-gray-50'
}`}
>
<div className="flex items-center space-x-3 mb-2">
<div
className={`p-2 rounded-full ${
currentLayout.id === layout.id
? 'bg-blue-100 text-blue-600'
: 'bg-gray-100 text-gray-600'
}`}
>
{getLayoutIcon(layout.type)}
</div>
<div>
<h4 className="font-medium text-gray-900 text-sm">{layout.name}</h4>
<p className="text-xs text-gray-600">{layout.description}</p>
</div>
</div>
{/* Layout Preview */}
<div className="bg-gray-100 rounded p-3 h-16 flex items-center justify-center">
{layout.type === 'grid' && (
<div className="grid grid-cols-2 gap-1">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="w-4 h-3 bg-blue-300 rounded"></div>
))}
</div>
)}
{layout.type === 'sidebar' && (
<div className="flex items-center space-x-2">
<div className="w-8 h-6 bg-blue-500 rounded"></div>
<div className="grid grid-cols-3 gap-1">
<div className="w-1 h-1 bg-blue-300 rounded"></div>
<div className="w-1 h-1 bg-blue-300 rounded"></div>
<div className="w-1 h-1 bg-blue-300 rounded"></div>
<div className="w-1 h-1 bg-blue-300 rounded"></div>
<div className="w-1 h-1 bg-blue-300 rounded"></div>
<div className="w-1 h-1 bg-blue-300 rounded"></div>
</div>
</div>
)}
{layout.type === 'teacher-focus' && (
<div className="space-y-1">
<div className="w-12 h-4 bg-green-500 rounded"></div>
<div className="flex space-x-1">
<div className="w-1 h-1 bg-blue-300 rounded"></div>
<div className="w-1 h-1 bg-blue-300 rounded"></div>
<div className="w-1 h-1 bg-blue-300 rounded"></div>
<div className="w-1 h-1 bg-blue-300 rounded"></div>
</div>
</div>
)}
</div>
</button>
))}
</div>
</div>
</div>
)
case 'settings':
return (
<div className="h-full bg-white flex flex-col text-gray-900">
<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">Sınıf Ayarları</h3>
<button onClick={() => setActiveSidePanel(null)}>
<FaTimes className="text-gray-500" />
</button>
</div>
</div>
<div className="flex-1 overflow-y-auto p-4">
<div className="space-y-6">
{/* Katılımcı Davranışları */}
<div>
<h4 className="font-semibold text-gray-800 mb-3">Katılımcı İzinleri</h4>
<div className="space-y-3">
<label className="flex items-center justify-between">
<span className="text-sm text-gray-700">Parmak kaldırma izni</span>
<input
type="checkbox"
checked={classSettings.allowHandRaise}
onChange={(e) =>
handleSettingsChange({ allowHandRaise: e.target.checked })
}
className="rounded"
disabled={user.role !== 'teacher'}
/>
</label>
<label className="flex items-center justify-between">
<span className="text-sm text-gray-700">Öğrenci sohbet izni</span>
<input
type="checkbox"
checked={classSettings.allowStudentChat}
onChange={(e) =>
handleSettingsChange({ allowStudentChat: e.target.checked })
}
className="rounded"
disabled={user.role !== 'teacher'}
/>
</label>
<label className="flex items-center justify-between">
<span className="text-sm">Özel mesaj izni</span>
<input
type="checkbox"
checked={classSettings.allowPrivateMessages}
onChange={(e) =>
handleSettingsChange({ allowPrivateMessages: e.target.checked })
}
className="rounded"
disabled={user.role !== 'teacher'}
/>
</label>
<label className="flex items-center justify-between">
<span className="text-sm">Öğrenci ekran paylaşımı</span>
<input
type="checkbox"
checked={classSettings.allowStudentScreenShare}
onChange={(e) =>
handleSettingsChange({ allowStudentScreenShare: e.target.checked })
}
className="rounded"
disabled={user.role !== 'teacher'}
/>
</label>
</div>
</div>
{/* Varsayılan Ayarlar */}
<div>
<h4 className="font-semibold text-gray-800 mb-3">Varsayılan Ayarlar</h4>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Varsayılan mikrofon durumu
</label>
<select
value={classSettings.defaultMicrophoneState}
onChange={(e) =>
handleSettingsChange({
defaultMicrophoneState: e.target.value as 'muted' | 'unmuted',
})
}
className="w-full p-2 border border-gray-300 rounded-md text-sm"
disabled={user.role !== 'teacher'}
>
<option value="muted">Kapalı</option>
<option value="unmuted">ık</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Varsayılan kamera durumu
</label>
<select
value={classSettings.defaultCameraState}
onChange={(e) =>
handleSettingsChange({
defaultCameraState: e.target.value as 'on' | 'off',
})
}
className="w-full p-2 border border-gray-300 rounded-md text-sm"
disabled={user.role !== 'teacher'}
>
<option value="on">ık</option>
<option value="off">Kapalı</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Varsayılan layout
</label>
<select
value={classSettings.defaultLayout}
onChange={(e) => handleSettingsChange({ defaultLayout: e.target.value })}
className="w-full p-2 border border-gray-300 rounded-md text-sm"
disabled={user.role !== 'teacher'}
>
<option value="grid">Izgara Görünümü</option>
<option value="sidebar">Sunum Modu</option>
<option value="teacher-focus">Öğretmen Odaklı</option>
<option value="interview">Karşılıklı Görüşme</option>
</select>
</div>
</div>
</div>
{/* Otomatik Ayarlar */}
<div>
<h4 className="font-semibold text-gray-800 mb-3">Otomatik Ayarlar</h4>
<div className="space-y-3">
<label className="flex items-center justify-between">
<span className="text-sm text-gray-700">
Yeni katılımcıları otomatik sustur
</span>
<input
type="checkbox"
checked={classSettings.autoMuteNewParticipants}
onChange={(e) =>
handleSettingsChange({ autoMuteNewParticipants: e.target.checked })
}
className="rounded"
disabled={user.role !== 'teacher'}
/>
</label>
</div>
</div>
{user.role !== 'teacher' && (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
<p className="text-sm text-yellow-800">
Ayarları sadece öğretmen değiştirebilir.
</p>
</div>
)}
</div>
</div>
</div>
)
default:
return null
}
default:
return null
}
return (
<div className="w-full lg:w-80 bg-white shadow-xl border-l border-gray-200 flex-shrink-0 h-full">
{panelContent()}
{/* Kapat butonu kaldırıldı, ana panelde gösterilecek */}
</div>
)
}
return (