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",
|
||||
"IsEnabled": true,
|
||||
"MultiTenancySide": 2
|
||||
},
|
||||
{
|
||||
"GroupName": "App.Classroom",
|
||||
"Name": "App.Classroom.Planning",
|
||||
"ParentName": "App.Classroom",
|
||||
"DisplayName": "App.Classroom.Planning",
|
||||
"IsEnabled": true,
|
||||
"MultiTenancySide": 2
|
||||
}
|
||||
],
|
||||
"Menus": [
|
||||
|
|
@ -3941,6 +3949,13 @@
|
|||
"componentPath": "@/views/classroom/RoomDetail",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.Classroom.RoomDetail"]
|
||||
},
|
||||
{
|
||||
"key": "admin.classroom.planning",
|
||||
"path": "/admin/classroom/planning/:id",
|
||||
"componentPath": "@/views/classroom/PlanningPage",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.Classroom.Planning"]
|
||||
}
|
||||
],
|
||||
"Languages": [
|
||||
|
|
@ -14184,8 +14199,14 @@
|
|||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Classroom.RoomDetail",
|
||||
"tr": "Virtul Classroom",
|
||||
"en": "Sanal Sınıf"
|
||||
"tr": "Sanal Sınıf",
|
||||
"en": "Virtul Classroom"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Classroom.Planning",
|
||||
"tr": "Sınıf Planlama",
|
||||
"en": "Classroom Planning"
|
||||
}
|
||||
],
|
||||
"Settings": [
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
|||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||
}, {
|
||||
"url": "index.html",
|
||||
"revision": "0.d148klvmpj8"
|
||||
"revision": "0.7r1n6s5iulg"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
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',
|
||||
classes: '/admin/classroom/classes',
|
||||
roomDetail: '/admin/classroom/room/:id',
|
||||
planning: '/admin/classroom/planning/:id',
|
||||
},
|
||||
},
|
||||
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 actualed = new Date(actualStartTime)
|
||||
// const now = new Date()
|
||||
|
|
@ -260,7 +266,7 @@ const ClassList: React.FC = () => {
|
|||
<>
|
||||
<Helmet
|
||||
titleTemplate="%s | Kurs Platform"
|
||||
title={translate('::' + 'App.Classroom')}
|
||||
title={translate('::' + 'App.Classroom.List')}
|
||||
defaultTitle="Kurs Platform"
|
||||
></Helmet>
|
||||
<Container>
|
||||
|
|
@ -448,6 +454,18 @@ const ClassList: React.FC = () => {
|
|||
<div className="flex space-x-2">
|
||||
{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
|
||||
onClick={() => openEditModal(classSession)}
|
||||
disabled={classSession.actualStartTime ? true : false}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const Dashboard: React.FC = () => {
|
|||
<>
|
||||
<Helmet
|
||||
titleTemplate="%s | Kurs Platform"
|
||||
title={translate('::' + 'App.Classroom')}
|
||||
title={translate('::' + 'App.Classroom.Dashboard')}
|
||||
defaultTitle="Kurs Platform"
|
||||
></Helmet>
|
||||
<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,
|
||||
FaCompress,
|
||||
FaUserFriends,
|
||||
FaClipboardList,
|
||||
FaLayerGroup,
|
||||
FaWrench,
|
||||
FaUserTimes,
|
||||
FaDownload,
|
||||
FaTrash,
|
||||
FaEye,
|
||||
FaFilePdf,
|
||||
FaFileWord,
|
||||
FaFileImage,
|
||||
FaFileAlt,
|
||||
FaPaperPlane,
|
||||
FaBullhorn,
|
||||
FaUser,
|
||||
FaBars,
|
||||
FaCheck,
|
||||
} from 'react-icons/fa'
|
||||
import { SignalRService } from '@/services/classroom/signalr'
|
||||
import { WebRTCService } from '@/services/classroom/webrtc'
|
||||
|
|
@ -48,8 +39,6 @@ import {
|
|||
VideoLayoutDto,
|
||||
} from '@/proxy/classroom/models'
|
||||
import { useStoreState } from '@/store/store'
|
||||
import { ParticipantGrid } from '@/components/classroom/ParticipantGrid'
|
||||
import { ScreenSharePanel } from '@/components/classroom/ScreenSharePanel'
|
||||
import { KickParticipantModal } from '@/components/classroom/KickParticipantModal'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import {
|
||||
|
|
@ -63,11 +52,13 @@ import { endClassroom } from '@/services/classroom.service'
|
|||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import ChatPanel from '@/components/classroom/ChatPanel'
|
||||
import ParticipantsPanel from '@/components/classroom/ParticipantsPanel'
|
||||
import DocumentsPanel from '@/components/classroom/DocumentsPanel'
|
||||
import LayoutPanel from '@/components/classroom/LayoutPanel'
|
||||
import SettingsPanel from '@/components/classroom/SettingsPanel'
|
||||
import ChatPanel from '@/components/classroom/panels/ChatPanel'
|
||||
import ParticipantsPanel from '@/components/classroom/panels/ParticipantsPanel'
|
||||
import DocumentsPanel from '@/components/classroom/panels/DocumentsPanel'
|
||||
import LayoutPanel from '@/components/classroom/panels/LayoutPanel'
|
||||
import SettingsPanel from '@/components/classroom/panels/SettingsPanel'
|
||||
import { ScreenSharePanel } from '@/components/classroom/panels/ScreenSharePanel'
|
||||
import { ParticipantGrid } from '@/components/classroom/ParticipantGrid'
|
||||
|
||||
type SidePanelType =
|
||||
| 'chat'
|
||||
|
|
@ -740,7 +731,7 @@ const RoomDetail: React.FC = () => {
|
|||
<>
|
||||
<Helmet
|
||||
titleTemplate="%s | Kurs Platform"
|
||||
title={translate('::' + 'App.Classroom')}
|
||||
title={translate('::' + 'App.Classroom.RoomDetail')}
|
||||
defaultTitle="Kurs Platform"
|
||||
></Helmet>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue