Sınıf planlama komponenti birleştirildi.
This commit is contained in:
parent
18b65e6c8a
commit
155bb2f757
21 changed files with 1466 additions and 22 deletions
|
|
@ -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": [
|
||||||
|
|
|
||||||
|
|
@ -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"), {
|
||||||
|
|
|
||||||
64
ui/src/components/classroom/data/classroom.ts
Normal file
64
ui/src/components/classroom/data/classroom.ts
Normal 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',
|
||||||
|
},
|
||||||
|
]
|
||||||
44
ui/src/components/classroom/data/layouts.ts
Normal file
44
ui/src/components/classroom/data/layouts.ts
Normal 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',
|
||||||
|
},
|
||||||
|
]
|
||||||
454
ui/src/components/classroom/data/students.ts
Normal file
454
ui/src/components/classroom/data/students.ts
Normal 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',
|
||||||
|
},
|
||||||
|
]
|
||||||
31
ui/src/components/classroom/planning/ClassroomSelector.tsx
Normal file
31
ui/src/components/classroom/planning/ClassroomSelector.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
145
ui/src/components/classroom/planning/QuickActions.tsx
Normal file
145
ui/src/components/classroom/planning/QuickActions.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
186
ui/src/components/classroom/planning/SeatGrid.tsx
Normal file
186
ui/src/components/classroom/planning/SeatGrid.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
60
ui/src/components/classroom/planning/StudentList.tsx
Normal file
60
ui/src/components/classroom/planning/StudentList.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
123
ui/src/proxy/classroom/planning.ts
Normal file
123
ui/src/proxy/classroom/planning.ts
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
306
ui/src/views/classroom/PlanningPage.tsx
Normal file
306
ui/src/views/classroom/PlanningPage.tsx
Normal 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
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue