Sınıf planlama komponenti birleştirildi.

This commit is contained in:
Sedat ÖZTÜRK 2025-08-29 16:52:55 +03:00
parent 18b65e6c8a
commit 155bb2f757
21 changed files with 1466 additions and 22 deletions

View file

@ -2835,6 +2835,14 @@
"DisplayName": "App.Classroom.RoomDetail", "DisplayName": "App.Classroom.RoomDetail",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 2 "MultiTenancySide": 2
},
{
"GroupName": "App.Classroom",
"Name": "App.Classroom.Planning",
"ParentName": "App.Classroom",
"DisplayName": "App.Classroom.Planning",
"IsEnabled": true,
"MultiTenancySide": 2
} }
], ],
"Menus": [ "Menus": [
@ -3941,6 +3949,13 @@
"componentPath": "@/views/classroom/RoomDetail", "componentPath": "@/views/classroom/RoomDetail",
"routeType": "protected", "routeType": "protected",
"authority": ["App.Classroom.RoomDetail"] "authority": ["App.Classroom.RoomDetail"]
},
{
"key": "admin.classroom.planning",
"path": "/admin/classroom/planning/:id",
"componentPath": "@/views/classroom/PlanningPage",
"routeType": "protected",
"authority": ["App.Classroom.Planning"]
} }
], ],
"Languages": [ "Languages": [
@ -14184,8 +14199,14 @@
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.Classroom.RoomDetail", "key": "App.Classroom.RoomDetail",
"tr": "Virtul Classroom", "tr": "Sanal Sınıf",
"en": "Sanal Sınıf" "en": "Virtul Classroom"
},
{
"resourceName": "Platform",
"key": "App.Classroom.Planning",
"tr": "Sınıf Planlama",
"en": "Classroom Planning"
} }
], ],
"Settings": [ "Settings": [

View file

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

View file

@ -0,0 +1,64 @@
import { Classroom } from '@/proxy/classroom/planning'
export const classrooms: Classroom[] = [
{
id: '1',
name: 'Theater Sınıfı',
layoutType: 'Theater',
rows: 6,
columns: 8,
capacity: 48,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '2',
name: 'U-Shape Sınıfı',
layoutType: 'UShape',
rows: 5,
columns: 8,
capacity: 40,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '3',
name: 'Bus Sınıfı',
layoutType: 'Bus',
rows: 10,
columns: 5,
capacity: 50,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '4',
name: 'Lab Sınıfı',
layoutType: 'Lab',
rows: 8,
columns: 6,
capacity: 48,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '5',
name: 'Exam Sınıfı',
layoutType: 'Exam',
rows: 10,
columns: 10,
capacity: 100,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '6',
name: 'Grid Sınıfı',
layoutType: 'Grid',
rows: 8,
columns: 8,
capacity: 64,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
]

View file

@ -0,0 +1,44 @@
import {
FaBookOpen,
FaBus,
FaCircle,
FaFlask,
FaLayerGroup,
FaSquare,
FaThLarge,
} from 'react-icons/fa'
export const layouts = [
{
value: 'Theater',
label: 'Theater',
icon: FaThLarge,
description: 'Tam grid düzen',
},
{ value: 'Bus', label: 'Bus', icon: FaBus, description: 'Ortada koridor' },
{
value: 'UShape',
label: 'U-Shape',
icon: FaSquare,
description: 'U şekli düzen',
},
{
value: 'Grid',
label: 'Grid',
icon: FaLayerGroup,
description: 'Basit tablo',
},
{ value: 'Lab', label: 'Lab', icon: FaFlask, description: 'Masa grupları' },
{
value: 'Exam',
label: 'Exam',
icon: FaBookOpen,
description: 'Sınav düzeni',
},
{
value: 'Circle',
label: 'Circle',
icon: FaCircle,
description: 'Yuvarlak düzen',
},
]

View file

@ -0,0 +1,454 @@
import { Student } from '@/proxy/classroom/planning'
export const mockStudents: Student[] = [
{
id: '1',
fullName: 'Ahmet Yılmaz',
photoUrl: 'https://images.pexels.com/photos/1239291/pexels-photo-1239291.jpeg',
tags: ['Matematik', 'Fizik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '2',
fullName: 'Ayşe Demir',
photoUrl: 'https://images.pexels.com/photos/733872/pexels-photo-733872.jpeg',
tags: ['Edebiyat', 'Tarih'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '3',
fullName: 'Mehmet Kaya',
photoUrl: 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg',
tags: ['Kimya', 'Biyoloji'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '4',
fullName: 'Fatma Özkan',
photoUrl: 'https://images.pexels.com/photos/1181519/pexels-photo-1181519.jpeg',
tags: ['Geometri', 'Sanat'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '5',
fullName: 'Ali Çelik',
photoUrl: 'https://images.pexels.com/photos/2379004/pexels-photo-2379004.jpeg',
tags: ['İngilizce', 'Müzik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '6',
fullName: 'Zeynep Arslan',
photoUrl: 'https://images.pexels.com/photos/1181686/pexels-photo-1181686.jpeg',
tags: ['Matematik', 'İngilizce'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '7',
fullName: 'Murat Doğan',
photoUrl: 'https://images.pexels.com/photos/2182970/pexels-photo-2182970.jpeg',
tags: ['Tarih', 'Coğrafya'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '8',
fullName: 'Elif Yıldız',
photoUrl: 'https://images.pexels.com/photos/1858175/pexels-photo-1858175.jpeg',
tags: ['Biyoloji', 'Edebiyat'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '9',
fullName: 'Osman Güler',
photoUrl: 'https://images.pexels.com/photos/2613260/pexels-photo-2613260.jpeg',
tags: ['Fizik', 'Kimya'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '10',
fullName: 'Hatice Aydın',
photoUrl: 'https://images.pexels.com/photos/1587009/pexels-photo-1587009.jpeg',
tags: ['Sanat', 'Müzik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '11',
fullName: 'Emre Şahin',
photoUrl: 'https://images.pexels.com/photos/1819483/pexels-photo-1819483.jpeg',
tags: ['Matematik', 'Geometri'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '12',
fullName: 'Büşra Öztürk',
photoUrl: 'https://images.pexels.com/photos/1674752/pexels-photo-1674752.jpeg',
tags: ['İngilizce', 'Edebiyat'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '13',
fullName: 'Kemal Polat',
photoUrl: 'https://images.pexels.com/photos/2218208/pexels-photo-2218208.jpeg',
tags: ['Coğrafya', 'Tarih'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '14',
fullName: 'Selin Karaca',
photoUrl: 'https://images.pexels.com/photos/1468379/pexels-photo-1468379.jpeg',
tags: ['Biyoloji', 'Kimya'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '15',
fullName: 'Can Yavaş',
photoUrl: 'https://images.pexels.com/photos/2379005/pexels-photo-2379005.jpeg',
tags: ['Fizik', 'Matematik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '16',
fullName: 'Deniz Mutlu',
photoUrl: 'https://images.pexels.com/photos/1040880/pexels-photo-1040880.jpeg',
tags: ['Müzik', 'Sanat'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '17',
fullName: 'Berk Koç',
photoUrl: 'https://images.pexels.com/photos/1043471/pexels-photo-1043471.jpeg',
tags: ['Geometri', 'Fizik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '18',
fullName: 'Naz Aktaş',
photoUrl: 'https://images.pexels.com/photos/1559486/pexels-photo-1559486.jpeg',
tags: ['Edebiyat', 'İngilizce'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '19',
fullName: 'Arda Bulut',
photoUrl: 'https://images.pexels.com/photos/2379004/pexels-photo-2379004.jpeg',
tags: ['Tarih', 'Matematik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '20',
fullName: 'İrem Tosun',
photoUrl: 'https://images.pexels.com/photos/1858175/pexels-photo-1858175.jpeg',
tags: ['Biyoloji', 'Sanat'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '21',
fullName: 'Kaan Erdoğan',
photoUrl: 'https://images.pexels.com/photos/2182970/pexels-photo-2182970.jpeg',
tags: ['Kimya', 'Coğrafya'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '22',
fullName: 'Lale Gündüz',
photoUrl: 'https://images.pexels.com/photos/1587009/pexels-photo-1587009.jpeg',
tags: ['Müzik', 'Edebiyat'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '23',
fullName: 'Rıza Özer',
photoUrl: 'https://images.pexels.com/photos/1239291/pexels-photo-1239291.jpeg',
tags: ['Fizik', 'Geometri'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '24',
fullName: 'Mine Akın',
photoUrl: 'https://images.pexels.com/photos/1181519/pexels-photo-1181519.jpeg',
tags: ['İngilizce', 'Tarih'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '25',
fullName: 'Tolga Şen',
photoUrl: 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg',
tags: ['Matematik', 'Biyoloji'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '26',
fullName: 'Pınar Yıldırım',
photoUrl: 'https://images.pexels.com/photos/733872/pexels-photo-733872.jpeg',
tags: ['Sanat', 'Kimya'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '27',
fullName: 'Serkan Bozkurt',
photoUrl: 'https://images.pexels.com/photos/2613260/pexels-photo-2613260.jpeg',
tags: ['Coğrafya', 'Müzik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '28',
fullName: 'Cansu Güven',
photoUrl: 'https://images.pexels.com/photos/1468379/pexels-photo-1468379.jpeg',
tags: ['Edebiyat', 'Fizik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '29',
fullName: 'Barış Tekin',
photoUrl: 'https://images.pexels.com/photos/1819483/pexels-photo-1819483.jpeg',
tags: ['Matematik', 'Tarih'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '30',
fullName: 'Gizem Aslan',
photoUrl: 'https://images.pexels.com/photos/1674752/pexels-photo-1674752.jpeg',
tags: ['Biyoloji', 'İngilizce'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '31',
fullName: 'Cem Yılmaz',
photoUrl: 'https://images.pexels.com/photos/2218208/pexels-photo-2218208.jpeg',
tags: ['Fizik', 'Matematik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '32',
fullName: 'Seda Kaya',
photoUrl: 'https://images.pexels.com/photos/1040880/pexels-photo-1040880.jpeg',
tags: ['Kimya', 'Biyoloji'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '33',
fullName: 'Burak Özkan',
photoUrl: 'https://images.pexels.com/photos/1043471/pexels-photo-1043471.jpeg',
tags: ['Tarih', 'Coğrafya'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '34',
fullName: 'Esra Demir',
photoUrl: 'https://images.pexels.com/photos/1559486/pexels-photo-1559486.jpeg',
tags: ['Edebiyat', 'Sanat'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '35',
fullName: 'Onur Çelik',
photoUrl: 'https://images.pexels.com/photos/2379004/pexels-photo-2379004.jpeg',
tags: ['İngilizce', 'Müzik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '36',
fullName: 'Merve Arslan',
photoUrl: 'https://images.pexels.com/photos/1181686/pexels-photo-1181686.jpeg',
tags: ['Matematik', 'Geometri'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '37',
fullName: 'Hakan Doğan',
photoUrl: 'https://images.pexels.com/photos/2182970/pexels-photo-2182970.jpeg',
tags: ['Fizik', 'Kimya'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '38',
fullName: 'Aylin Yıldız',
photoUrl: 'https://images.pexels.com/photos/1858175/pexels-photo-1858175.jpeg',
tags: ['Biyoloji', 'Tarih'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '39',
fullName: 'Volkan Güler',
photoUrl: 'https://images.pexels.com/photos/2613260/pexels-photo-2613260.jpeg',
tags: ['Coğrafya', 'Edebiyat'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '40',
fullName: 'Sibel Aydın',
photoUrl: 'https://images.pexels.com/photos/1587009/pexels-photo-1587009.jpeg',
tags: ['Sanat', 'İngilizce'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '41',
fullName: 'Taner Şahin',
photoUrl: 'https://images.pexels.com/photos/1819483/pexels-photo-1819483.jpeg',
tags: ['Matematik', 'Müzik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '42',
fullName: 'Gamze Öztürk',
photoUrl: 'https://images.pexels.com/photos/1674752/pexels-photo-1674752.jpeg',
tags: ['Fizik', 'Sanat'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '43',
fullName: 'Erhan Polat',
photoUrl: 'https://images.pexels.com/photos/2218208/pexels-photo-2218208.jpeg',
tags: ['Kimya', 'Geometri'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '44',
fullName: 'Dilek Karaca',
photoUrl: 'https://images.pexels.com/photos/1468379/pexels-photo-1468379.jpeg',
tags: ['Biyoloji', 'Edebiyat'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '45',
fullName: 'Mert Yavaş',
photoUrl: 'https://images.pexels.com/photos/2379005/pexels-photo-2379005.jpeg',
tags: ['Tarih', 'İngilizce'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '46',
fullName: 'Özge Mutlu',
photoUrl: 'https://images.pexels.com/photos/1040880/pexels-photo-1040880.jpeg',
tags: ['Coğrafya', 'Müzik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '47',
fullName: 'Koray Koç',
photoUrl: 'https://images.pexels.com/photos/1043471/pexels-photo-1043471.jpeg',
tags: ['Matematik', 'Fizik'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '48',
fullName: 'Yeliz Aktaş',
photoUrl: 'https://images.pexels.com/photos/1559486/pexels-photo-1559486.jpeg',
tags: ['Sanat', 'Biyoloji'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '49',
fullName: 'Serdar Bulut',
photoUrl: 'https://images.pexels.com/photos/2379004/pexels-photo-2379004.jpeg',
tags: ['Kimya', 'Tarih'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
{
id: '50',
fullName: 'Nilüfer Tosun',
photoUrl: 'https://images.pexels.com/photos/1858175/pexels-photo-1858175.jpeg',
tags: ['Edebiyat', 'Geometri'],
isActive: true,
creationTime: '2023-01-01T00:00:00Z',
lastModificationTime: '2023-01-01T00:00:00Z',
},
]

View file

@ -0,0 +1,31 @@
import { Classroom } from '@/proxy/classroom/planning'
import React from 'react'
import { classrooms } from '../data/classroom'
interface ClassroomSelectorProps {
selectedClassroom: Classroom | null
onClassroomChange: (classroom: Classroom | null) => void
}
export const ClassroomSelector: React.FC<ClassroomSelectorProps> = ({
selectedClassroom,
onClassroomChange,
}) => {
return (
<div className="w-full">
<select
id="classroom-selector"
value={selectedClassroom?.id || ''}
onChange={(e) => onClassroomChange(classrooms.find((c) => c.id === e.target.value) || null)}
className="w-full px-2 py-1 border border-gray-300 rounded"
>
<option value="">Sınıf seçin...</option>
{classrooms.map((classroom) => (
<option key={classroom.id} value={classroom.id}>
{classroom.name} {classroom.capacity} koltuk
</option>
))}
</select>
</div>
)
}

View file

@ -0,0 +1,145 @@
import React from 'react'
import { Card } from '@/components/ui/Card'
import { Button } from '@/components/ui/Button'
import { Avatar } from '@/components/ui/Avatar'
import { FaPhone, FaEnvelope, FaRegCommentDots, FaUserTimes } from 'react-icons/fa'
import { Seat, Student } from '@/proxy/classroom/planning'
interface QuickActionsProps {
selectedSeats: string[]
seats: Seat[]
students: Student[]
onRemoveSelectedStudents: () => void
onToggleSeatBlock: () => void
}
export const QuickActions: React.FC<QuickActionsProps> = ({
selectedSeats,
seats,
students,
onRemoveSelectedStudents,
}) => {
const selectedStudents = selectedSeats
.map((seatId) => {
const seat = seats.find((s) => s.id === seatId)
return seat?.studentId ? students.find((s) => s.id === seat.studentId) : null
})
.filter(Boolean) as Student[]
return (
<div className="p-4 space-y-4">
{/* Statistics */}
<Card
bodyClass="md:p-3"
header={<h3 className="text-sm">İstatistikler</h3>}
headerClass="p-2"
>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600">Toplam koltuk:</span>
<span className="font-medium">{seats.length}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">Dolu koltuk:</span>
<span className="font-medium text-green-600">
{seats.filter((s) => s.studentId).length}
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">Boş koltuk:</span>
<span className="font-medium text-blue-600">
{seats.filter((s) => !s.studentId && !s.isBlocked).length}
</span>
</div>
<div className="pt-2 border-t">
<div className="flex justify-between text-sm font-medium">
<span>Doluluk oranı:</span>
<span className="text-primary">
{Math.round((seats.filter((s) => s.studentId).length / seats.length) * 100)}%
</span>
</div>
</div>
</div>
</Card>
{/* Selection Info */}
<Card
bodyClass="md:p-3"
header={<h3 className="text-sm">Seçilen Koltuk ({selectedStudents.length})</h3>}
headerClass="p-2"
>
<div className="space-y-2">
{/* Quick Actions */}
{selectedStudents.length > 0 && (
<Card bodyClass="md:p-3">
<div className="flex gap-2 justify-center">
{selectedStudents.length > 0 && (
<>
<Button
title="Toplu Arama"
variant="default"
size="sm"
className="flex flex-row rounded-full justify-center items-center"
>
<FaPhone />
</Button>
<Button
title="E-posta Gönder"
variant="default"
size="sm"
className="flex flex-row rounded-full justify-center items-center"
>
<FaEnvelope />
</Button>
<Button
title="SMS Gönder"
variant="default"
size="sm"
className="flex flex-row rounded-full justify-center items-center"
>
<FaRegCommentDots />
</Button>
</>
)}
{selectedSeats.length > 0 && (
<>
<Button
title="Atamaları Kaldır"
variant="default"
size="sm"
className="flex flex-row rounded-full justify-center items-center"
onClick={onRemoveSelectedStudents}
>
<FaUserTimes />
</Button>
</>
)}
</div>
</Card>
)}
{/* Selected Students */}
{selectedStudents.length > 0 && (
<Card bodyClass="md:p-3">
<body className="space-y-3">
{selectedStudents.map((student) => (
<div key={student.id} className="flex items-center space-x-3">
<Avatar
className="h-8 w-8"
shape="circle"
src={student.photoUrl || undefined}
/>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium truncate">{student.fullName}</div>
</div>
</div>
))}
</body>
</Card>
)}
</div>
</Card>
</div>
)
}

View file

@ -0,0 +1,186 @@
import React from 'react'
import { useDroppable } from '@dnd-kit/core'
import { Avatar } from '@/components/ui/Avatar'
import { FaTimes } from 'react-icons/fa'
import { Seat, SeatGridProps, Student } from '@/proxy/classroom/planning'
const DroppableSeat: React.FC<{
seat: Seat
student?: Student
isSelected: boolean
onSelect: () => void
onRemoveStudent: () => void
}> = ({ seat, student, isSelected, onSelect, onRemoveStudent }) => {
const { isOver, setNodeRef } = useDroppable({
id: seat.id,
})
const isEmpty = !student
const isBlocked = seat.isBlocked
const canDrop = !isBlocked // Bloke olmayan tüm koltuklar drop edilebilir
const canSelect = !isEmpty // Sadece dolu koltuklar seçilebilir
return (
<div className="relative group">
<div
ref={setNodeRef}
onClick={canSelect ? onSelect : undefined}
className={
'relative w-12 h-12 rounded-full border-2 transition-all duration-300 flex items-center justify-center transform ' +
(canSelect ? 'cursor-pointer ' : 'cursor-default ') +
(canDrop && !isOver
? 'bg-gray-100 border-gray-300 hover:border-primary hover:bg-gray-50 '
: '') +
(isBlocked
? 'bg-red-100 border-red-300 text-red-700 cursor-not-allowed opacity-75 '
: '') +
(isSelected && canSelect
? 'ring-2 ring-orange-400 ring-offset-2 bg-orange-50 border-orange-400 '
: '') +
(isOver && canDrop && isEmpty
? 'scale-125 ring-4 ring-green-400 ring-offset-4 bg-green-50 border-green-400 shadow-lg z-10 '
: '') +
(isOver && canDrop && !isEmpty
? 'scale-125 ring-4 ring-yellow-400 ring-offset-4 bg-yellow-50 border-yellow-400 shadow-lg z-10 '
: '') +
(isOver && !canDrop
? 'ring-4 ring-red-400 ring-offset-2 bg-red-50 border-red-400 shake '
: '')
}
style={{
zIndex: isOver ? 10 : 1,
}}
>
{student ? (
<Avatar
className={
'w-full h-full rounded-full object-cover transition-all duration-200 ' +
'group-hover:ring-2 group-hover:ring-blue-500 group-hover:ring-offset-1'
}
src={student.photoUrl || undefined}
>
{student.fullName
.split(' ')
.map((n) => n[0])
.join('')}
</Avatar>
) : (
<span
className={
'text-xs font-medium transition-all duration-300 ' +
(isOver && canDrop ? 'text-green-700 font-bold' : 'text-gray-600')
}
>
{seat.label}
</span>
)}
{/* Drop indicator */}
{isOver && canDrop && isEmpty && (
<div className="absolute inset-0 rounded-lg bg-green-400/20 border-2 border-green-400 border-dashed animate-pulse" />
)}
{/* Drop indicator for occupied seats */}
{isOver && canDrop && !isEmpty && (
<div className="absolute inset-0 rounded-lg bg-yellow-400/20 border-2 border-yellow-400 border-dashed animate-pulse" />
)}
{/* Invalid drop indicator */}
{isOver && !canDrop && (
<div className="absolute inset-0 rounded-lg bg-red-400/20 border-2 border-red-400 border-dashed" />
)}
</div>
{/* Remove button - sadece dolu koltuklar için */}
{student && (
<button
onClick={(e) => {
e.stopPropagation()
onRemoveStudent()
}}
className="absolute -top-2 -right-2 flex items-center justify-center
w-6 h-6 rounded-full bg-red-600 text-white
hover:bg-red-700 shadow-md
opacity-0 group-hover:opacity-100
transition-opacity duration-200 z-20"
>
<FaTimes className="h-3 w-3" />
</button>
)}
{/* Tooltip on hover */}
{student && (
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-10">
{student.fullName}
</div>
)}
</div>
)
}
export const SeatGrid: React.FC<SeatGridProps> = ({
classroom,
seats,
students,
selectedSeats,
onSeatSelect,
onRemoveStudent,
}) => {
const handleSeatSelect = (seatId: string) => {
if (selectedSeats.includes(seatId)) {
onSeatSelect(selectedSeats.filter((id) => id !== seatId))
} else {
onSeatSelect([...selectedSeats, seatId])
}
}
// Create grid layout
const grid = Array.from({ length: classroom.rows }, (_, row) =>
Array.from({ length: classroom.columns }, (_, col) => {
const seat = seats.find((s) => s.row === row && s.col === col)
const student = seat?.studentId ? students.find((s) => s.id === seat.studentId) : undefined
return { seat, student }
}),
)
return (
<div className="flex flex-col items-center space-y-4">
<div className="w-64 mt-4 h-5 items-center bg-gray-800 rounded-sm">
<div className="text-xs text-white justify-center text-center">TAHTA</div>
</div>
{/* Seat Grid */}
<div
className="grid gap-2"
style={{ gridTemplateColumns: `repeat(${classroom.columns}, 1fr)` }}
>
{grid.flat().map(({ seat, student }, index) => {
if (!seat) return <div key={index} className="w-12 h-12" />
return (
<DroppableSeat
key={seat.id}
seat={seat}
student={student}
isSelected={selectedSeats.includes(seat.id)}
onSelect={() => handleSeatSelect(seat.id)}
onRemoveStudent={() => onRemoveStudent(seat.id)}
/>
)
})}
</div>
{/* Legend */}
<div className="flex items-center space-x-6 text-sm text-gray-600">
<div className="flex items-center space-x-2">
<div className="w-4 h-4 bg-gray-100 border-2 border-gray-300 rounded"></div>
<span>Boş</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-4 h-4 bg-primary border-2 border-primary rounded"></div>
<span>Dolu</span>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,60 @@
import React from 'react'
import { useDraggable } from '@dnd-kit/core'
import { Avatar } from '@/components/ui/Avatar'
import { Student, StudentListProps } from '@/proxy/classroom/planning'
const DraggableStudent: React.FC<{ student: Student }> = ({ student }) => {
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
id: student.id,
})
const style = transform
? {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
}
: undefined
return (
<div
ref={setNodeRef}
style={style}
{...listeners}
{...attributes}
className={`transition-all duration-300 cursor-grab active:cursor-grabbing transform hover:scale-105 ${
isDragging ? 'opacity-30 scale-110 rotate-3 shadow-2xl z-50' : ''
}`}
>
<div className="flex flex-col items-center space-y-2 group">
<Avatar className="h-10 w-10" src={student.photoUrl || undefined}>
{student.fullName
.split(' ')
.map((n) => n[0])
.join('')}
</Avatar>
<div className="text-center">
<div className="font-medium text-xs text-gray-900 truncate w-full leading-tight">
{student.fullName}
</div>
</div>
</div>
</div>
)
}
export const StudentList: React.FC<StudentListProps> = ({ students }) => {
return (
<div className="flex-1 overflow-auto p-4">
<div className="grid grid-cols-3 gap-3">
{students.length === 0 ? (
<div className="col-span-3 text-center py-8 text-gray-500">
<div className="text-sm">Öğrenci bulunamadı</div>
<div className="text-xs mt-1">Arama kriterlerinizi değiştirin</div>
</div>
) : (
students.map((student) => <DraggableStudent key={student.id} student={student} />)
)}
</div>
</div>
)
}

View file

@ -0,0 +1,123 @@
export interface Student {
id: string
fullName: string
photoUrl: string | null
tags: string[]
isActive: boolean
creationTime: string
lastModificationTime: string
}
export interface SeatGridProps {
classroom: Classroom
seats: Seat[]
students: Student[]
selectedSeats: string[]
onSeatSelect: (seatIds: string[]) => void
onRemoveStudent: (seatId: string) => void
}
export interface StudentListProps {
students: Student[]
searchQuery: string
selectedTags: string[]
}
export interface Seat {
id: string
row: number
col: number
label: string
isBlocked: boolean
studentId?: string
seatType: SeatType
concurrencyStamp: string
creationTime: string
lastModificationTime: string
}
export interface Classroom {
id: string
name: string
layoutType: string
rows: number
columns: number
capacity: number
creationTime: string
lastModificationTime: string
}
export interface StudentQuery {
q?: string
tags?: string[]
page?: number
size?: number
isActive?: boolean
}
// Classroom types
export type LayoutType = 'Theater' | 'Bus' | 'UShape' | 'Grid' | 'Lab' | 'Exam' | 'Circle'
export type SeatType = 'Standard' | 'Table' | 'Wheelchair'
export type AssignmentStrategy = 'FillByOrder' | 'MatchByIndex'
export interface SeatAssignment {
id: string
classroomId: string
seatId: string
studentId: string
assignedAt: string
assignedBy: string
}
export interface SeatMap {
classroom: Classroom
seats: Seat[]
assignments: SeatAssignment[]
}
// Assignment operations
export interface AssignSingleDto {
seatId: string
studentId: string
}
export interface AssignBulkDto {
seatIds: string[]
studentIds: string[]
strategy: AssignmentStrategy
}
export interface AssignmentResult {
success: boolean
assignedCount: number
errors?: string[]
}
// Real-time events
export interface SseEvent {
id: string
event: string
data: any
timestamp: string
}
export interface SeatAssignedEvent {
classroomId: string
seatId: string
studentId: string
ts: string
id: string
}
export interface SeatUnassignedEvent {
classroomId: string
seatId: string
ts: string
id: string
}
export interface LayoutChangedEvent {
classroomId: string
ts: string
id: string
}

View file

@ -80,6 +80,7 @@ export const ROUTES_ENUM = {
dashboard: '/admin/classroom/dashboard', dashboard: '/admin/classroom/dashboard',
classes: '/admin/classroom/classes', classes: '/admin/classroom/classes',
roomDetail: '/admin/classroom/room/:id', roomDetail: '/admin/classroom/room/:id',
planning: '/admin/classroom/planning/:id',
}, },
}, },
accessDenied: '/admin/access-denied', accessDenied: '/admin/access-denied',

View file

@ -183,6 +183,12 @@ const ClassList: React.FC = () => {
} }
} }
const handlePlanningClass = (classSession: ClassroomDto) => {
if (classSession.id) {
navigate(ROUTES_ENUM.protected.admin.classroom.planning.replace(':id', classSession.id))
}
}
// const canJoinClass = (actualStartTime: string) => { // const canJoinClass = (actualStartTime: string) => {
// const actualed = new Date(actualStartTime) // const actualed = new Date(actualStartTime)
// const now = new Date() // const now = new Date()
@ -260,7 +266,7 @@ const ClassList: React.FC = () => {
<> <>
<Helmet <Helmet
titleTemplate="%s | Kurs Platform" titleTemplate="%s | Kurs Platform"
title={translate('::' + 'App.Classroom')} title={translate('::' + 'App.Classroom.List')}
defaultTitle="Kurs Platform" defaultTitle="Kurs Platform"
></Helmet> ></Helmet>
<Container> <Container>
@ -448,6 +454,18 @@ const ClassList: React.FC = () => {
<div className="flex space-x-2"> <div className="flex space-x-2">
{user.role === 'teacher' && classSession.teacherId === user.id && ( {user.role === 'teacher' && classSession.teacherId === user.id && (
<> <>
<button
onClick={() => handlePlanningClass(classSession)}
disabled={classSession.actualStartTime ? true : false}
className="flex px-3 sm:px-4 py-2 rounded-lg bg-yellow-600 text-white
hover:bg-yellow-700
disabled:bg-gray-400 disabled:cursor-not-allowed disabled:hover:bg-gray-400"
title="Sınıfı Planla"
>
<FaUsers size={14} />
Planlama
</button>
<button <button
onClick={() => openEditModal(classSession)} onClick={() => openEditModal(classSession)}
disabled={classSession.actualStartTime ? true : false} disabled={classSession.actualStartTime ? true : false}

View file

@ -27,7 +27,7 @@ const Dashboard: React.FC = () => {
<> <>
<Helmet <Helmet
titleTemplate="%s | Kurs Platform" titleTemplate="%s | Kurs Platform"
title={translate('::' + 'App.Classroom')} title={translate('::' + 'App.Classroom.Dashboard')}
defaultTitle="Kurs Platform" defaultTitle="Kurs Platform"
></Helmet> ></Helmet>
<div className="flex items-center justify-center p-4"> <div className="flex items-center justify-center p-4">

View file

@ -0,0 +1,306 @@
import React, { useState, useEffect } from 'react'
import { DndContext, DragEndEvent, DragOverlay, DragStartEvent } from '@dnd-kit/core'
import { Button } from '@/components/ui/Button'
import { Avatar } from '@/components/ui/Avatar'
import { FaUsers, FaSearch, FaThLarge, FaUndo, FaSave, FaBolt } from 'react-icons/fa'
import { Classroom, Seat, Student } from '@/proxy/classroom/planning'
import { mockStudents } from '@/components/classroom/data/students'
import { StudentList } from '@/components/classroom/planning/StudentList'
import { SeatGrid } from '@/components/classroom/planning/SeatGrid'
import { ClassroomSelector } from '@/components/classroom/planning/ClassroomSelector'
import { QuickActions } from '@/components/classroom/planning/QuickActions'
import { Container } from '@/components/shared'
import { Helmet } from 'react-helmet'
import { useLocalization } from '@/utils/hooks/useLocalization'
const ClassroomPlannerPage: React.FC = () => {
const [students, setStudents] = useState<Student[]>([])
const [seats, setSeats] = useState<Seat[]>([])
const [selectedClassroom, setSelectedClassroom] = useState<Classroom | null>(null)
const [searchQuery, setSearchQuery] = useState('')
const [selectedTags, setSelectedTags] = useState<string[]>([])
const [draggedStudent, setDraggedStudent] = useState<Student | null>(null)
const [selectedSeats, setSelectedSeats] = useState<string[]>([])
// Mock data - gerçek API'den gelecek
useEffect(() => {
setStudents(mockStudents)
}, [])
// Sınıf değiştiğinde koltukları yeniden oluştur
useEffect(() => {
if (selectedClassroom) {
const newSeats: Seat[] = []
for (let row = 0; row < selectedClassroom.rows; row++) {
for (let col = 0; col < selectedClassroom.columns; col++) {
const label = String.fromCharCode(65 + row) + (col + 1)
newSeats.push({
id: `seat-${row}-${col}`,
row,
col,
label,
isBlocked: false,
seatType: 'Standard',
concurrencyStamp: '',
creationTime: new Date().toISOString(),
lastModificationTime: new Date().toISOString(),
})
}
}
setSeats(newSeats)
setSelectedSeats([]) // Seçili koltukları temizle
}
}, [selectedClassroom])
const handleDragStart = (event: DragStartEvent) => {
const student = students.find((s) => s.id === event.active.id)
setDraggedStudent(student || null)
}
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event
if (over && over.id.toString().startsWith('seat-')) {
const seatId = over.id.toString()
const studentId = active.id.toString()
// Check if seat is blocked
const targetSeat = seats.find((s) => s.id === seatId)
if (!targetSeat || targetSeat.isBlocked) {
setDraggedStudent(null)
return
}
// Update seat assignment - eski öğrenciyi kaldır, yeni öğrenciyi yerleştir
setSeats((prev) =>
prev.map((seat) =>
seat.id === seatId
? { ...seat, studentId }
: seat.studentId === studentId
? { ...seat, studentId: undefined }
: seat,
),
)
}
setDraggedStudent(null)
}
const handleRemoveStudent = (seatId: string) => {
setSeats((prev) =>
prev.map((seat) => (seat.id === seatId ? { ...seat, studentId: undefined } : seat)),
)
}
const handleRemoveSelectedStudents = () => {
setSeats((prev) =>
prev.map((seat) =>
selectedSeats.includes(seat.id) ? { ...seat, studentId: undefined } : seat,
),
)
setSelectedSeats([]) // Seçimi temizle
}
const handleToggleSeatBlock = () => {
setSeats((prev) =>
prev.map((seat) => {
if (selectedSeats.includes(seat.id)) {
return {
...seat,
isBlocked: !seat.isBlocked,
studentId: seat.isBlocked ? seat.studentId : undefined,
}
}
return seat
}),
)
setSelectedSeats([]) // Seçimi temizle
}
const handleSeatSelect = (seatIds: string[]) => {
// Sadece dolu koltukları seçilebilir yap
const validSeatIds = seatIds.filter((seatId) => {
const seat = seats.find((s) => s.id === seatId)
return seat && seat.studentId // Sadece öğrencisi olan koltuklar
})
setSelectedSeats(validSeatIds)
}
const filteredStudents = students.filter((student) => {
const matchesSearch = student.fullName.toLowerCase().includes(searchQuery.toLowerCase())
const matchesTags =
selectedTags.length === 0 || selectedTags.some((tag) => student.tags.includes(tag))
return matchesSearch && matchesTags
})
const assignedStudentIds = seats.filter((seat) => seat.studentId).map((seat) => seat.studentId!)
const unassignedStudents = filteredStudents.filter(
(student) => !assignedStudentIds.includes(student.id),
)
const handleClearAll = () => {
setSeats((prev) => prev.map((seat) => ({ ...seat, studentId: undefined })))
}
const handleAutoAssign = () => {
const availableSeats = seats.filter((seat) => !seat.isBlocked && !seat.studentId)
const studentsToAssign = unassignedStudents.slice(0, availableSeats.length)
const newSeats = [...seats]
studentsToAssign.forEach((student, index) => {
const seatIndex = seats.findIndex((seat) => seat.id === availableSeats[index].id)
if (seatIndex !== -1) {
newSeats[seatIndex] = { ...newSeats[seatIndex], studentId: student.id }
}
})
setSeats(newSeats)
}
const { translate } = useLocalization()
return (
<>
<Helmet
titleTemplate="%s | Kurs Platform"
title={translate('::' + 'App.Classroom.Planning')}
defaultTitle="Kurs Platform"
></Helmet>
<Container>
{/* Header */}
<header className="bg-white border-b border-gray-200 px-4 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-2">
<FaThLarge className="h-8 w-8 text-primary" />
<h1 className="text-2xl font-bold text-gray-900">Sınıf Planlama</h1>
</div>
</div>
<div className="flex items-center space-x-3">
<Button
variant="default"
size="sm"
className="flex flex-row w-full justify-center items-center"
onClick={handleClearAll}
>
<FaUndo className="h-4 w-4 mr-2" />
Temizle
</Button>
<Button
variant="default"
size="sm"
className="flex items-center px-3 whitespace-nowrap"
onClick={handleAutoAssign}
>
<FaBolt className="h-4 w-4 mr-2 text-yellow-400" />
Otomatik Ata
</Button>
<Button size="sm" className="flex flex-row w-full justify-center items-center">
<FaSave className="h-4 w-4 mr-2" />
Kaydet
</Button>
</div>
</div>
</header>
<DndContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<div className="flex h-[calc(100vh-80px)]">
{/* Left Sidebar - Student List */}
<div className="w-80 bg-white border-r border-gray-200 flex flex-col">
<div className="p-2 border-b border-gray-200">
<div className="flex items-center justify-between pb-2">
<h2 className="text-lg font-semibold text-gray-900 flex items-center">
<FaUsers className="h-5 w-5 mr-2" />
Öğrenciler ({unassignedStudents.length})
</h2>
</div>
<div className="space-y-3">
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 h-3 w-3 text-gray-400" />
<input
id="student-search"
placeholder="Öğrenci ara..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full p-1 pl-8 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
/>
</div>
</div>
</div>
<StudentList
students={unassignedStudents}
searchQuery={searchQuery}
selectedTags={selectedTags}
/>
</div>
{/* Main Content - Seat Grid */}
<div className="flex-1 flex flex-col">
<div className="p-2 border-b border-gray-200">
<div className="flex items-center justify-between pb-2">
<h2 className="text-lg font-semibold text-gray-900 flex items-center">
<FaUsers className="h-5 w-5 mr-2" />
Sınıflar
</h2>
</div>
<div className="space-y-3">
<div className="relative">
<ClassroomSelector
selectedClassroom={selectedClassroom}
onClassroomChange={setSelectedClassroom}
/>
</div>
</div>
</div>
{/* Seat Grid */}
<div className="flex-1 overflow-auto">
{selectedClassroom && (
<SeatGrid
classroom={selectedClassroom}
seats={seats}
students={students}
selectedSeats={selectedSeats}
onSeatSelect={handleSeatSelect}
onRemoveStudent={handleRemoveStudent}
/>
)}
</div>
</div>
{/* Right Sidebar - Quick Actions */}
<div className="w-80 bg-white border-l border-gray-200">
<QuickActions
selectedSeats={selectedSeats}
seats={seats}
students={students}
onRemoveSelectedStudents={handleRemoveSelectedStudents}
onToggleSeatBlock={handleToggleSeatBlock}
/>
</div>
</div>
<DragOverlay>
{draggedStudent && (
<div className="transform rotate-3 scale-110">
<Avatar
className="h-12 w-12 border-4 border-primary shadow-2xl"
shape="circle"
src={draggedStudent.photoUrl || undefined}
/>
</div>
)}
</DragOverlay>
</DndContext>
</Container>
</>
)
}
export default ClassroomPlannerPage

View file

@ -19,22 +19,13 @@ import {
FaTimes, FaTimes,
FaCompress, FaCompress,
FaUserFriends, FaUserFriends,
FaClipboardList,
FaLayerGroup, FaLayerGroup,
FaWrench, FaWrench,
FaUserTimes,
FaDownload,
FaTrash,
FaEye,
FaFilePdf, FaFilePdf,
FaFileWord, FaFileWord,
FaFileImage, FaFileImage,
FaFileAlt, FaFileAlt,
FaPaperPlane,
FaBullhorn,
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'
@ -48,8 +39,6 @@ import {
VideoLayoutDto, VideoLayoutDto,
} from '@/proxy/classroom/models' } from '@/proxy/classroom/models'
import { useStoreState } from '@/store/store' import { useStoreState } from '@/store/store'
import { ParticipantGrid } from '@/components/classroom/ParticipantGrid'
import { ScreenSharePanel } from '@/components/classroom/ScreenSharePanel'
import { KickParticipantModal } from '@/components/classroom/KickParticipantModal' import { KickParticipantModal } from '@/components/classroom/KickParticipantModal'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { import {
@ -63,11 +52,13 @@ import { endClassroom } from '@/services/classroom.service'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import ChatPanel from '@/components/classroom/ChatPanel' import ChatPanel from '@/components/classroom/panels/ChatPanel'
import ParticipantsPanel from '@/components/classroom/ParticipantsPanel' import ParticipantsPanel from '@/components/classroom/panels/ParticipantsPanel'
import DocumentsPanel from '@/components/classroom/DocumentsPanel' import DocumentsPanel from '@/components/classroom/panels/DocumentsPanel'
import LayoutPanel from '@/components/classroom/LayoutPanel' import LayoutPanel from '@/components/classroom/panels/LayoutPanel'
import SettingsPanel from '@/components/classroom/SettingsPanel' import SettingsPanel from '@/components/classroom/panels/SettingsPanel'
import { ScreenSharePanel } from '@/components/classroom/panels/ScreenSharePanel'
import { ParticipantGrid } from '@/components/classroom/ParticipantGrid'
type SidePanelType = type SidePanelType =
| 'chat' | 'chat'
@ -740,7 +731,7 @@ const RoomDetail: React.FC = () => {
<> <>
<Helmet <Helmet
titleTemplate="%s | Kurs Platform" titleTemplate="%s | Kurs Platform"
title={translate('::' + 'App.Classroom')} title={translate('::' + 'App.Classroom.RoomDetail')}
defaultTitle="Kurs Platform" defaultTitle="Kurs Platform"
></Helmet> ></Helmet>