Classroom canlı sisteme alınması
Classroom Listesi, NewClassroom, EditClassroom, DeleteClassroom
This commit is contained in:
parent
6365a0672f
commit
87f04ca653
20 changed files with 624 additions and 1072 deletions
|
|
@ -1,4 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
|
|
||||||
namespace Kurs.Platform.Classrooms;
|
namespace Kurs.Platform.Classrooms;
|
||||||
|
|
@ -19,26 +21,32 @@ public class ClassroomDto : FullAuditedEntityDto<Guid>
|
||||||
public bool IsScheduled { get; set; }
|
public bool IsScheduled { get; set; }
|
||||||
public int ParticipantCount { get; set; }
|
public int ParticipantCount { get; set; }
|
||||||
public bool CanJoin { get; set; }
|
public bool CanJoin { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string SettingsJson { get; set; }
|
||||||
|
public ClassroomSettingsDto SettingsDto
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(SettingsJson))
|
||||||
|
return JsonSerializer.Deserialize<ClassroomSettingsDto>(SettingsJson);
|
||||||
|
|
||||||
|
return new ClassroomSettingsDto();
|
||||||
|
}
|
||||||
|
set { SettingsJson = JsonSerializer.Serialize(value); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreateClassroomDto
|
public class ClassroomSettingsDto
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public bool AllowHandRaise { get; set; }
|
||||||
public string Description { get; set; }
|
public bool AllowStudentChat { get; set; }
|
||||||
public string Subject { get; set; }
|
public bool AllowPrivateMessages { get; set; }
|
||||||
public DateTime ScheduledStartTime { get; set; }
|
public bool AllowStudentScreenShare { get; set; }
|
||||||
public int Duration { get; set; } = 60;
|
public string DefaultMicrophoneState { get; set; } = "muted"; // 'muted' | 'unmuted'
|
||||||
public int MaxParticipants { get; set; } = 30;
|
public string DefaultCameraState { get; set; } = "off"; // 'on' | 'off'
|
||||||
}
|
public string DefaultLayout { get; set; } = "grid";
|
||||||
|
public bool AutoMuteNewParticipants { get; set; }
|
||||||
public class UpdateClassroomDto
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Description { get; set; }
|
|
||||||
public string Subject { get; set; }
|
|
||||||
public DateTime ScheduledStartTime { get; set; }
|
|
||||||
public int Duration { get; set; }
|
|
||||||
public int MaxParticipants { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GetClassroomListDto : PagedAndSortedResultRequestDto
|
public class GetClassroomListDto : PagedAndSortedResultRequestDto
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ namespace Kurs.Platform.Classrooms;
|
||||||
|
|
||||||
public interface IClassroomAppService : IApplicationService
|
public interface IClassroomAppService : IApplicationService
|
||||||
{
|
{
|
||||||
Task<ClassroomDto> CreateAsync(CreateClassroomDto input);
|
Task<ClassroomDto> CreateAsync(ClassroomDto input);
|
||||||
Task<PagedResultDto<ClassroomDto>> GetListAsync(GetClassroomListDto input);
|
Task<PagedResultDto<ClassroomDto>> GetListAsync(PagedAndSortedResultRequestDto input);
|
||||||
Task<ClassroomDto> GetAsync(Guid id);
|
Task<ClassroomDto> GetAsync(Guid id);
|
||||||
Task<ClassroomDto> UpdateAsync(Guid id, UpdateClassroomDto input);
|
Task<ClassroomDto> UpdateAsync(Guid id, ClassroomDto input);
|
||||||
Task DeleteAsync(Guid id);
|
Task DeleteAsync(Guid id);
|
||||||
Task<ClassroomDto> StartClassAsync(Guid id);
|
Task<ClassroomDto> StartClassAsync(Guid id);
|
||||||
Task EndClassAsync(Guid id);
|
Task EndClassAsync(Guid id);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kurs.Platform.Entities;
|
using Kurs.Platform.Entities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
@ -26,40 +27,15 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
_attendanceRepository = attendanceRepository;
|
_attendanceRepository = attendanceRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ClassroomDto> CreateAsync(CreateClassroomDto input)
|
public async Task<ClassroomDto> GetAsync(Guid id)
|
||||||
{
|
{
|
||||||
var classSession = new Classroom(
|
var classSession = await _classSessionRepository.GetAsync(id);
|
||||||
GuidGenerator.Create(),
|
|
||||||
input.Name,
|
|
||||||
input.Description,
|
|
||||||
input.Subject,
|
|
||||||
CurrentUser.Id,
|
|
||||||
CurrentUser.Name,
|
|
||||||
input.ScheduledStartTime,
|
|
||||||
input.Duration,
|
|
||||||
input.MaxParticipants
|
|
||||||
);
|
|
||||||
|
|
||||||
await _classSessionRepository.InsertAsync(classSession);
|
|
||||||
await CurrentUnitOfWork.SaveChangesAsync();
|
|
||||||
|
|
||||||
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PagedResultDto<ClassroomDto>> GetListAsync(GetClassroomListDto input)
|
public async Task<PagedResultDto<ClassroomDto>> GetListAsync(PagedAndSortedResultRequestDto input)
|
||||||
{
|
{
|
||||||
var query = await _classSessionRepository.GetQueryableAsync();
|
var query = await _classSessionRepository.GetQueryableAsync();
|
||||||
|
|
||||||
if (input.IsActive.HasValue)
|
|
||||||
{
|
|
||||||
query = query.Where(x => x.IsActive == input.IsActive.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.TeacherId.HasValue)
|
|
||||||
{
|
|
||||||
query = query.Where(x => x.TeacherId == input.TeacherId.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
var totalCount = query.Count();
|
var totalCount = query.Count();
|
||||||
var items = query
|
var items = query
|
||||||
.OrderBy(x => x.ScheduledStartTime)
|
.OrderBy(x => x.ScheduledStartTime)
|
||||||
|
|
@ -73,13 +49,29 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ClassroomDto> GetAsync(Guid id)
|
public async Task<ClassroomDto> CreateAsync(ClassroomDto input)
|
||||||
{
|
{
|
||||||
var classSession = await _classSessionRepository.GetAsync(id);
|
var classSession = new Classroom(
|
||||||
|
GuidGenerator.Create(),
|
||||||
|
input.Name,
|
||||||
|
input.Description,
|
||||||
|
input.Subject,
|
||||||
|
CurrentUser.Id,
|
||||||
|
CurrentUser.Name,
|
||||||
|
input.ScheduledStartTime,
|
||||||
|
input.Duration,
|
||||||
|
input.MaxParticipants,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
input.SettingsJson = JsonSerializer.Serialize(input.SettingsDto)
|
||||||
|
);
|
||||||
|
|
||||||
|
await _classSessionRepository.InsertAsync(classSession);
|
||||||
|
|
||||||
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ClassroomDto> UpdateAsync(Guid id, UpdateClassroomDto input)
|
public async Task<ClassroomDto> UpdateAsync(Guid id, ClassroomDto input)
|
||||||
{
|
{
|
||||||
var classSession = await _classSessionRepository.GetAsync(id);
|
var classSession = await _classSessionRepository.GetAsync(id);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,6 @@ public class ClassroomAutoMapperProfile : Profile
|
||||||
CreateMap<Classroom, ClassroomDto>()
|
CreateMap<Classroom, ClassroomDto>()
|
||||||
.ForMember(dest => dest.CanJoin, opt => opt.MapFrom(src => src.CanJoin()));
|
.ForMember(dest => dest.CanJoin, opt => opt.MapFrom(src => src.CanJoin()));
|
||||||
|
|
||||||
CreateMap<CreateClassroomDto, Classroom>();
|
|
||||||
CreateMap<UpdateClassroomDto, Classroom>();
|
|
||||||
|
|
||||||
CreateMap<ClassAttandance, ClassAttendanceDto>();
|
CreateMap<ClassAttandance, ClassAttendanceDto>();
|
||||||
CreateMap<ClassParticipant, ClassParticipantDto>();
|
CreateMap<ClassParticipant, ClassParticipantDto>();
|
||||||
CreateMap<ClassChat, ClassChatDto>();
|
CreateMap<ClassChat, ClassChatDto>();
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,12 @@ public class Classroom : FullAuditedEntity<Guid>
|
||||||
public DateTime ScheduledStartTime { get; set; }
|
public DateTime ScheduledStartTime { get; set; }
|
||||||
public DateTime? ActualStartTime { get; set; }
|
public DateTime? ActualStartTime { get; set; }
|
||||||
public DateTime? EndTime { get; set; }
|
public DateTime? EndTime { get; set; }
|
||||||
public int Duration { get; set; } // minutes
|
public int Duration { get; set; }
|
||||||
public int MaxParticipants { get; set; }
|
public int MaxParticipants { get; set; }
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
public bool IsScheduled { get; set; }
|
public bool IsScheduled { get; set; }
|
||||||
public int ParticipantCount { get; set; }
|
public int ParticipantCount { get; set; }
|
||||||
|
public string SettingsJson { get; set; }
|
||||||
|
|
||||||
public virtual ICollection<ClassParticipant> Participants { get; set; }
|
public virtual ICollection<ClassParticipant> Participants { get; set; }
|
||||||
public virtual ICollection<ClassAttandance> AttendanceRecords { get; set; }
|
public virtual ICollection<ClassAttandance> AttendanceRecords { get; set; }
|
||||||
|
|
@ -40,7 +41,10 @@ public class Classroom : FullAuditedEntity<Guid>
|
||||||
string teacherName,
|
string teacherName,
|
||||||
DateTime scheduledStartTime,
|
DateTime scheduledStartTime,
|
||||||
int duration,
|
int duration,
|
||||||
int maxParticipants
|
int maxParticipants,
|
||||||
|
bool isActive,
|
||||||
|
bool isScheduled,
|
||||||
|
string settingsJson
|
||||||
) : base(id)
|
) : base(id)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|
@ -51,9 +55,9 @@ public class Classroom : FullAuditedEntity<Guid>
|
||||||
ScheduledStartTime = scheduledStartTime;
|
ScheduledStartTime = scheduledStartTime;
|
||||||
Duration = duration;
|
Duration = duration;
|
||||||
MaxParticipants = maxParticipants;
|
MaxParticipants = maxParticipants;
|
||||||
IsActive = false;
|
IsActive = isActive;
|
||||||
IsScheduled = true;
|
IsScheduled = isScheduled;
|
||||||
ParticipantCount = 0;
|
SettingsJson = settingsJson;
|
||||||
|
|
||||||
Participants = new HashSet<ClassParticipant>();
|
Participants = new HashSet<ClassParticipant>();
|
||||||
AttendanceRecords = new HashSet<ClassAttandance>();
|
AttendanceRecords = new HashSet<ClassAttandance>();
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Kurs.Platform.Migrations
|
namespace Kurs.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20250826055306_Initial")]
|
[Migration("20250826203853_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -1840,6 +1840,9 @@ namespace Kurs.Platform.Migrations
|
||||||
b.Property<DateTime>("ScheduledStartTime")
|
b.Property<DateTime>("ScheduledStartTime")
|
||||||
.HasColumnType("datetime2");
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("SettingsJson")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("Subject")
|
b.Property<string>("Subject")
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("nvarchar(100)");
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
@ -804,6 +804,7 @@ namespace Kurs.Platform.Migrations
|
||||||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||||
IsScheduled = table.Column<bool>(type: "bit", nullable: false),
|
IsScheduled = table.Column<bool>(type: "bit", nullable: false),
|
||||||
ParticipantCount = table.Column<int>(type: "int", nullable: false),
|
ParticipantCount = table.Column<int>(type: "int", nullable: false),
|
||||||
|
SettingsJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
|
@ -1837,6 +1837,9 @@ namespace Kurs.Platform.Migrations
|
||||||
b.Property<DateTime>("ScheduledStartTime")
|
b.Property<DateTime>("ScheduledStartTime")
|
||||||
.HasColumnType("datetime2");
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("SettingsJson")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("Subject")
|
b.Property<string>("Subject")
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("nvarchar(100)");
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
||||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||||
}, {
|
}, {
|
||||||
"url": "index.html",
|
"url": "index.html",
|
||||||
"revision": "0.lcv915aoevo"
|
"revision": "0.8cs01edqoio"
|
||||||
}], {});
|
}], {});
|
||||||
workbox.cleanupOutdatedCaches();
|
workbox.cleanupOutdatedCaches();
|
||||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||||
|
|
|
||||||
711
ui/package-lock.json
generated
711
ui/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -15,7 +15,6 @@
|
||||||
"format": "npm run prettier:fix && npm run lint:fix"
|
"format": "npm run prettier:fix && npm run lint:fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@microsoft/signalr": "^9.0.6",
|
|
||||||
"@babel/generator": "^7.28.3",
|
"@babel/generator": "^7.28.3",
|
||||||
"@babel/parser": "^7.28.0",
|
"@babel/parser": "^7.28.0",
|
||||||
"@babel/standalone": "^7.28.0",
|
"@babel/standalone": "^7.28.0",
|
||||||
|
|
@ -30,6 +29,7 @@
|
||||||
"@fullcalendar/react": "^6.1.8",
|
"@fullcalendar/react": "^6.1.8",
|
||||||
"@fullcalendar/timegrid": "^6.1.8",
|
"@fullcalendar/timegrid": "^6.1.8",
|
||||||
"@marsidev/react-turnstile": "^0.2.1",
|
"@marsidev/react-turnstile": "^0.2.1",
|
||||||
|
"@microsoft/signalr": "^9.0.6",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@tanstack/react-query": "^4.29.19",
|
"@tanstack/react-query": "^4.29.19",
|
||||||
"@tanstack/react-table": "^8.8.5",
|
"@tanstack/react-table": "^8.8.5",
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import { motion } from 'framer-motion'
|
|
||||||
import { FaGraduationCap, FaUserCheck, FaEye } from 'react-icons/fa'
|
|
||||||
import { Role } from '@/proxy/classroom/models'
|
|
||||||
|
|
||||||
interface RoleSelectorProps {
|
|
||||||
onRoleSelect: (role: Role) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RoleSelector: React.FC<RoleSelectorProps> = ({ onRoleSelect }) => {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-center p-4">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
className="text-center w-full max-w-4xl"
|
|
||||||
>
|
|
||||||
<p className="text-lg sm:text-xl text-gray-600 mb-8 sm:mb-12">Lütfen rolünüzü seçin</p>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8">
|
|
||||||
<motion.button
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
whileTap={{ scale: 0.95 }}
|
|
||||||
onClick={() => onRoleSelect('teacher')}
|
|
||||||
className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-blue-500"
|
|
||||||
>
|
|
||||||
<FaGraduationCap size={48} className="mx-auto text-blue-600 mb-4 sm:mb-4" />
|
|
||||||
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">Öğretmen</h2>
|
|
||||||
<p className="text-gray-600 text-sm sm:text-base">
|
|
||||||
Ders başlatın, öğrencilerle iletişim kurun ve katılım raporlarını görün
|
|
||||||
</p>
|
|
||||||
</motion.button>
|
|
||||||
|
|
||||||
<motion.button
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
whileTap={{ scale: 0.95 }}
|
|
||||||
onClick={() => onRoleSelect('student')}
|
|
||||||
className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-green-500"
|
|
||||||
>
|
|
||||||
<FaUserCheck size={48} className="mx-auto text-green-600 mb-4 sm:mb-4" />
|
|
||||||
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">Öğrenci</h2>
|
|
||||||
<p className="text-gray-600 text-sm sm:text-base">
|
|
||||||
Aktif derslere katılın, öğretmeniniz ve diğer öğrencilerle etkileşim kurun
|
|
||||||
</p>
|
|
||||||
</motion.button>
|
|
||||||
|
|
||||||
<motion.button
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
whileTap={{ scale: 0.95 }}
|
|
||||||
onClick={() => onRoleSelect('observer')}
|
|
||||||
className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-purple-500 md:col-span-2 lg:col-span-1"
|
|
||||||
>
|
|
||||||
<FaEye size={48} className="mx-auto text-purple-600 mb-4 sm:mb-4" />
|
|
||||||
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">Gözlemci</h2>
|
|
||||||
<p className="text-gray-600 text-sm sm:text-base">
|
|
||||||
Sınıfı gözlemleyin, eğitim sürecini takip edin (ses/video paylaşımı yok)
|
|
||||||
</p>
|
|
||||||
</motion.button>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
export const initialScheduledClasses = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
name: 'Matematik 101 - Diferansiyel Denklemler',
|
|
||||||
description: 'İleri matematik konuları ve uygulamaları',
|
|
||||||
teacherId: '0cc64443-fe3b-5f2c-bb22-3a1bf7acd82a',
|
|
||||||
teacherName: 'Prof. Dr. Mehmet Özkan',
|
|
||||||
scheduledStartTime: new Date(Date.now() - 300000).toISOString(), // 5 minutes ago (can join)
|
|
||||||
startTime: '',
|
|
||||||
isActive: false,
|
|
||||||
isScheduled: true,
|
|
||||||
participantCount: 0,
|
|
||||||
maxParticipants: 30,
|
|
||||||
subject: 'Matematik',
|
|
||||||
duration: 90,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
name: 'Fizik 201 - Kuantum Mekaniği',
|
|
||||||
description: 'Modern fizik ve kuantum teorisi temelleri',
|
|
||||||
teacherId: 'teacher2',
|
|
||||||
teacherName: 'Dr. Ayşe Kaya',
|
|
||||||
scheduledStartTime: new Date(Date.now() + 1800000).toISOString(), // 30 minutes from now
|
|
||||||
startTime: '',
|
|
||||||
isActive: false,
|
|
||||||
isScheduled: true,
|
|
||||||
participantCount: 0,
|
|
||||||
maxParticipants: 25,
|
|
||||||
subject: 'Fizik',
|
|
||||||
duration: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
name: 'Kimya 301 - Organik Kimya',
|
|
||||||
description: 'Organik bileşikler ve reaksiyon mekanizmaları',
|
|
||||||
teacherId: 'current-teacher',
|
|
||||||
teacherName: 'Dr. Ali Veli',
|
|
||||||
scheduledStartTime: new Date(Date.now() - 120000).toISOString(), // 2 minutes ago (can join)
|
|
||||||
startTime: '',
|
|
||||||
isActive: false,
|
|
||||||
isScheduled: true,
|
|
||||||
participantCount: 0,
|
|
||||||
maxParticipants: 20,
|
|
||||||
subject: 'Kimya',
|
|
||||||
duration: 75,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
@ -13,31 +13,30 @@ export interface ClassroomDto {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
description?: string
|
description?: string
|
||||||
|
subject?: string
|
||||||
teacherId: string
|
teacherId: string
|
||||||
teacherName: string
|
teacherName: string
|
||||||
startTime: string
|
|
||||||
scheduledStartTime: string
|
scheduledStartTime: string
|
||||||
|
actualStartTime?: string
|
||||||
endTime?: string
|
endTime?: string
|
||||||
|
duration?: number
|
||||||
|
maxParticipants?: number
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
isScheduled: boolean
|
isScheduled: boolean
|
||||||
participantCount: number
|
participantCount: number
|
||||||
maxParticipants?: number
|
canJoin: boolean
|
||||||
subject?: string
|
|
||||||
duration?: number // in minutes
|
|
||||||
settings?: ClassroomSettingsDto
|
settings?: ClassroomSettingsDto
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClassroomSettingsDto {
|
export interface ClassroomSettingsDto {
|
||||||
allowHandRaise: boolean
|
allowHandRaise: boolean
|
||||||
|
allowStudentChat: boolean
|
||||||
|
allowPrivateMessages: boolean
|
||||||
|
allowStudentScreenShare: boolean
|
||||||
defaultMicrophoneState: 'muted' | 'unmuted'
|
defaultMicrophoneState: 'muted' | 'unmuted'
|
||||||
defaultCameraState: 'on' | 'off'
|
defaultCameraState: 'on' | 'off'
|
||||||
defaultLayout: string
|
defaultLayout: string
|
||||||
allowStudentScreenShare: boolean
|
|
||||||
allowStudentChat: boolean
|
|
||||||
allowPrivateMessages: boolean
|
|
||||||
autoMuteNewParticipants: boolean
|
autoMuteNewParticipants: boolean
|
||||||
recordSession: boolean
|
|
||||||
waitingRoomEnabled: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClassAttendanceDto {
|
export interface ClassAttendanceDto {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { ClassroomDto } from '@/proxy/classroom/models'
|
import { ClassroomDto } from '@/proxy/classroom/models'
|
||||||
import apiService from './api.service'
|
import apiService from './api.service'
|
||||||
|
import { PagedAndSortedResultRequestDto, PagedResultDto } from '@/proxy'
|
||||||
|
|
||||||
export const getClassroomById = (id: string) =>
|
export const getClassroomById = (id: string) =>
|
||||||
apiService.fetchData<ClassroomDto>({
|
apiService.fetchData<ClassroomDto>({
|
||||||
|
|
@ -8,47 +9,29 @@ export const getClassroomById = (id: string) =>
|
||||||
params: { id },
|
params: { id },
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getClassrooms = () =>
|
export const getClassrooms = (input: PagedAndSortedResultRequestDto) =>
|
||||||
apiService.fetchData<ClassroomDto[]>({
|
apiService.fetchData<PagedResultDto<ClassroomDto>>({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: `/api/app/classroom`,
|
url: `/api/app/classroom`,
|
||||||
|
params: input,
|
||||||
})
|
})
|
||||||
|
|
||||||
// export const getChartOptions = (chartCode: string) =>
|
export const createClassroom = (input: ClassroomDto) =>
|
||||||
// apiService.fetchData<ChartDto>({
|
apiService.fetchData<ClassroomDto>({
|
||||||
// method: 'GET',
|
method: 'POST',
|
||||||
// url: `/api/app/charts/chart-options`,
|
url: `/api/app/classroom`,
|
||||||
// params: { chartCode },
|
data: input as any,
|
||||||
// })
|
})
|
||||||
|
|
||||||
// export const getChartSelect = (chartCode: string, filter?: string) =>
|
export const updateClassroom = (input: ClassroomDto) =>
|
||||||
// apiService.fetchData({
|
apiService.fetchData({
|
||||||
// method: 'GET',
|
method: 'PUT',
|
||||||
// url: `/api/app/chart-select/select`,
|
url: `/api/app/classroom/${input.id}`,
|
||||||
// params: { chartCode, filter },
|
data: input,
|
||||||
// })
|
})
|
||||||
|
|
||||||
// export const putCharts = (input: ChartEditDto) =>
|
export const deleteClassroom = (id: string) =>
|
||||||
// apiService.fetchData({
|
apiService.fetchData({
|
||||||
// method: 'PUT',
|
method: 'DELETE',
|
||||||
// url: `/api/app/charts/${input.id}`,
|
url: `/api/app/classroom/${id}`,
|
||||||
// data: input,
|
})
|
||||||
// })
|
|
||||||
|
|
||||||
// export const deleteChartJsonItem = (
|
|
||||||
// id: string,
|
|
||||||
// chartCode: string,
|
|
||||||
// index: number,
|
|
||||||
// fieldName: string,
|
|
||||||
// ) =>
|
|
||||||
// apiService.fetchData({
|
|
||||||
// method: 'DELETE',
|
|
||||||
// url: `/api/app/charts/chart-json-item?id=${id}&chartCode=${chartCode}&index=${index}&fieldName=${fieldName}`,
|
|
||||||
// })
|
|
||||||
|
|
||||||
// export const putChartJsonItem = (input: ChartJsonItemRowDto) =>
|
|
||||||
// apiService.fetchData({
|
|
||||||
// method: 'PUT',
|
|
||||||
// url: `/api/app/charts/chart-json-item`,
|
|
||||||
// data: input,
|
|
||||||
// })
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import * as signalR from '@microsoft/signalr'
|
||||||
export class SignalRService {
|
export class SignalRService {
|
||||||
private connection!: signalR.HubConnection
|
private connection!: signalR.HubConnection
|
||||||
private isConnected: boolean = false
|
private isConnected: boolean = false
|
||||||
private demoMode: boolean = true // Start in demo mode by default
|
private demoMode: boolean = false // Start in demo mode by default
|
||||||
private onSignalingMessage?: (message: SignalingMessageDto) => void
|
private onSignalingMessage?: (message: SignalingMessageDto) => void
|
||||||
private onAttendanceUpdate?: (record: ClassAttendanceDto) => void
|
private onAttendanceUpdate?: (record: ClassAttendanceDto) => void
|
||||||
private onParticipantJoined?: (userId: string, name: string) => void
|
private onParticipantJoined?: (userId: string, name: string) => void
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,6 @@ export function useClassroomLogic() {
|
||||||
setRoleState('dashboard')
|
setRoleState('dashboard')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleJoinClass = (classSession: ClassroomDto, userName?: string) => {
|
|
||||||
setCurrentClass(classSession)
|
|
||||||
setRoleState('classroom')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleLeaveClass = () => {
|
|
||||||
setCurrentClass(null)
|
|
||||||
setRoleState('dashboard')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCreateClass = (classData: Partial<ClassroomDto>) => {
|
const handleCreateClass = (classData: Partial<ClassroomDto>) => {
|
||||||
const newClass = {
|
const newClass = {
|
||||||
...classData,
|
...classData,
|
||||||
|
|
@ -57,8 +47,6 @@ export function useClassroomLogic() {
|
||||||
allClasses,
|
allClasses,
|
||||||
setAllClasses,
|
setAllClasses,
|
||||||
handleRoleSelect,
|
handleRoleSelect,
|
||||||
handleJoinClass,
|
|
||||||
handleLeaveClass,
|
|
||||||
handleCreateClass,
|
handleCreateClass,
|
||||||
handleEditClass,
|
handleEditClass,
|
||||||
handleDeleteClass,
|
handleDeleteClass,
|
||||||
|
|
|
||||||
|
|
@ -13,24 +13,21 @@ import {
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
|
|
||||||
import { ClassroomDto } from '@/proxy/classroom/models'
|
import { ClassroomDto } from '@/proxy/classroom/models'
|
||||||
import { initialScheduledClasses } from '@/proxy/classroom/data'
|
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { Container } from '@/components/shared'
|
import { Container } from '@/components/shared'
|
||||||
import { getClassrooms } from '@/services/classroom.service'
|
import {
|
||||||
|
createClassroom,
|
||||||
|
deleteClassroom,
|
||||||
|
getClassrooms,
|
||||||
|
updateClassroom,
|
||||||
|
} from '@/services/classroom.service'
|
||||||
|
|
||||||
const ClassList: React.FC = () => {
|
const ClassList: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { user } = useStoreState((state) => state.auth)
|
const { user } = useStoreState((state) => state.auth)
|
||||||
|
|
||||||
const [showCreateModal, setShowCreateModal] = useState(false)
|
const newClassEntity = {
|
||||||
const [showEditModal, setShowEditModal] = useState(false)
|
|
||||||
const [editingClass, setEditingClass] = useState<ClassroomDto | null>(null)
|
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
|
||||||
const [deletingClass, setDeletingClass] = useState<ClassroomDto | null>(null)
|
|
||||||
const [scheduledClasses, setScheduledClasses] = useState<ClassroomDto[]>(initialScheduledClasses)
|
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
subject: '',
|
subject: '',
|
||||||
|
|
@ -39,17 +36,133 @@ const ClassList: React.FC = () => {
|
||||||
maxParticipants: 30,
|
maxParticipants: 30,
|
||||||
settings: {
|
settings: {
|
||||||
allowHandRaise: true,
|
allowHandRaise: true,
|
||||||
defaultMicrophoneState: 'muted' as 'muted' | 'unmuted',
|
|
||||||
defaultCameraState: 'on' as 'on' | 'off',
|
|
||||||
defaultLayout: 'grid',
|
|
||||||
allowStudentScreenShare: false,
|
|
||||||
allowStudentChat: true,
|
allowStudentChat: true,
|
||||||
allowPrivateMessages: true,
|
allowPrivateMessages: true,
|
||||||
|
allowStudentScreenShare: false,
|
||||||
|
defaultMicrophoneState: 'muted',
|
||||||
|
defaultCameraState: 'on',
|
||||||
|
defaultLayout: 'grid',
|
||||||
autoMuteNewParticipants: true,
|
autoMuteNewParticipants: true,
|
||||||
recordSession: false,
|
|
||||||
waitingRoomEnabled: false,
|
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
const [classList, setClassList] = useState<ClassroomDto[]>([])
|
||||||
|
|
||||||
|
const [newClassroom, setNewClassroom] = useState(newClassEntity)
|
||||||
|
const [editingClass, setEditingClass] = useState<ClassroomDto | null>(null)
|
||||||
|
const [deletingClass, setDeletingClass] = useState<ClassroomDto | null>(null)
|
||||||
|
|
||||||
|
const [showCreateModal, setShowCreateModal] = useState(false)
|
||||||
|
const [showEditModal, setShowEditModal] = useState(false)
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||||
|
|
||||||
|
const getClassroomList = async (skipCount = 0, maxResultCount = 1000, sorting = '') => {
|
||||||
|
try {
|
||||||
|
const result = await getClassrooms({
|
||||||
|
sorting,
|
||||||
|
skipCount,
|
||||||
|
maxResultCount,
|
||||||
|
})
|
||||||
|
|
||||||
|
setClassList(result.data.items || [])
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching classrooms:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getClassroomList()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleCreateClass = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
const newClass: ClassroomDto = {
|
||||||
|
...newClassroom,
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
teacherId: user.id,
|
||||||
|
teacherName: user.name,
|
||||||
|
isActive: false,
|
||||||
|
isScheduled: true,
|
||||||
|
participantCount: 0,
|
||||||
|
actualStartTime: '',
|
||||||
|
canJoin: false,
|
||||||
|
settings: {
|
||||||
|
...newClassroom.settings,
|
||||||
|
defaultMicrophoneState: newClassroom.settings.defaultMicrophoneState as 'muted' | 'unmuted',
|
||||||
|
defaultCameraState: newClassroom.settings.defaultCameraState as 'on' | 'off',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createClassroom(newClass)
|
||||||
|
|
||||||
|
getClassroomList()
|
||||||
|
setShowCreateModal(false)
|
||||||
|
setNewClassroom(newClassEntity)
|
||||||
|
|
||||||
|
if (newClass.id) {
|
||||||
|
navigate(ROUTES_ENUM.protected.admin.classroom.classroom.replace(':id', newClass.id))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Sınıf oluştururken hata oluştu:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEditClass = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (!editingClass) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateClassroom(editingClass)
|
||||||
|
|
||||||
|
getClassroomList()
|
||||||
|
setShowEditModal(false)
|
||||||
|
setEditingClass(null)
|
||||||
|
resetForm()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Sınıf oluştururken hata oluştu:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openEditModal = (classSession: ClassroomDto) => {
|
||||||
|
setEditingClass(classSession)
|
||||||
|
setShowEditModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openDeleteModal = (classSession: ClassroomDto) => {
|
||||||
|
setDeletingClass(classSession)
|
||||||
|
setShowDeleteModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteClass = async () => {
|
||||||
|
if (!deletingClass) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteClassroom(deletingClass.id)
|
||||||
|
getClassroomList()
|
||||||
|
setShowDeleteModal(false)
|
||||||
|
setDeletingClass(null)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Sınıf silinirken hata oluştu:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setNewClassroom(newClassEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStartClass = (classSession: ClassroomDto) => {
|
||||||
|
const updatedClass = {
|
||||||
|
...classSession,
|
||||||
|
isActive: true,
|
||||||
|
actualStartTime: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
getClassroomList()
|
||||||
|
|
||||||
|
if (updatedClass.id) {
|
||||||
|
navigate(ROUTES_ENUM.protected.admin.classroom.classroom.replace(':id', updatedClass.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const canJoinClass = (scheduledTime: string) => {
|
const canJoinClass = (scheduledTime: string) => {
|
||||||
const scheduled = new Date(scheduledTime)
|
const scheduled = new Date(scheduledTime)
|
||||||
|
|
@ -85,136 +198,6 @@ const ClassList: React.FC = () => {
|
||||||
return `${minutes}d kaldı`
|
return `${minutes}d kaldı`
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCreateClass = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
const newClass: Partial<ClassroomDto> = {
|
|
||||||
...formData,
|
|
||||||
id: `class-${Date.now()}`,
|
|
||||||
teacherId: user.id,
|
|
||||||
teacherName: user.name,
|
|
||||||
isActive: false,
|
|
||||||
isScheduled: true,
|
|
||||||
participantCount: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
setScheduledClasses((prev) => [...prev, newClass as ClassroomDto])
|
|
||||||
setShowCreateModal(false)
|
|
||||||
setFormData({
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
subject: '',
|
|
||||||
scheduledStartTime: '',
|
|
||||||
duration: 60,
|
|
||||||
maxParticipants: 30,
|
|
||||||
settings: {
|
|
||||||
allowHandRaise: true,
|
|
||||||
defaultMicrophoneState: 'muted',
|
|
||||||
defaultCameraState: 'on',
|
|
||||||
defaultLayout: 'grid',
|
|
||||||
allowStudentScreenShare: false,
|
|
||||||
allowStudentChat: true,
|
|
||||||
allowPrivateMessages: true,
|
|
||||||
autoMuteNewParticipants: true,
|
|
||||||
recordSession: false,
|
|
||||||
waitingRoomEnabled: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
// Yeni oluşturulan sınıfa yönlendir
|
|
||||||
if (newClass.id) {
|
|
||||||
navigate(ROUTES_ENUM.protected.admin.classroom.classroom.replace(':id', newClass.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleEditClass = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
if (!editingClass) return
|
|
||||||
|
|
||||||
const updatedClass = {
|
|
||||||
...editingClass,
|
|
||||||
...formData,
|
|
||||||
}
|
|
||||||
|
|
||||||
setScheduledClasses((prev) => prev.map((c) => (c.id === editingClass.id ? updatedClass : c)))
|
|
||||||
setShowEditModal(false)
|
|
||||||
setEditingClass(null)
|
|
||||||
resetForm()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDeleteClass = () => {
|
|
||||||
if (!deletingClass) return
|
|
||||||
|
|
||||||
setScheduledClasses((prev) => prev.filter((c) => c.id !== deletingClass.id))
|
|
||||||
setShowDeleteModal(false)
|
|
||||||
setDeletingClass(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const openEditModal = (classSession: ClassroomDto) => {
|
|
||||||
setEditingClass(classSession)
|
|
||||||
setFormData({
|
|
||||||
name: classSession.name,
|
|
||||||
description: classSession.description || '',
|
|
||||||
subject: classSession.subject || '',
|
|
||||||
scheduledStartTime: new Date(classSession.scheduledStartTime).toISOString().slice(0, 16),
|
|
||||||
duration: classSession.duration || 60,
|
|
||||||
maxParticipants: classSession.maxParticipants || 30,
|
|
||||||
settings: classSession.settings || {
|
|
||||||
allowHandRaise: true,
|
|
||||||
defaultMicrophoneState: 'muted',
|
|
||||||
defaultCameraState: 'on',
|
|
||||||
defaultLayout: 'grid',
|
|
||||||
allowStudentScreenShare: false,
|
|
||||||
allowStudentChat: true,
|
|
||||||
allowPrivateMessages: true,
|
|
||||||
autoMuteNewParticipants: true,
|
|
||||||
recordSession: false,
|
|
||||||
waitingRoomEnabled: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
setShowEditModal(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const openDeleteModal = (classSession: ClassroomDto) => {
|
|
||||||
setDeletingClass(classSession)
|
|
||||||
setShowDeleteModal(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
setFormData({
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
subject: '',
|
|
||||||
scheduledStartTime: '',
|
|
||||||
duration: 60,
|
|
||||||
maxParticipants: 30,
|
|
||||||
settings: {
|
|
||||||
allowHandRaise: true,
|
|
||||||
defaultMicrophoneState: 'muted',
|
|
||||||
defaultCameraState: 'on',
|
|
||||||
defaultLayout: 'grid',
|
|
||||||
allowStudentScreenShare: false,
|
|
||||||
allowStudentChat: true,
|
|
||||||
allowPrivateMessages: true,
|
|
||||||
autoMuteNewParticipants: true,
|
|
||||||
recordSession: false,
|
|
||||||
waitingRoomEnabled: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleStartClass = (classSession: ClassroomDto) => {
|
|
||||||
const updatedClass = {
|
|
||||||
...classSession,
|
|
||||||
isActive: true,
|
|
||||||
startTime: new Date().toISOString(),
|
|
||||||
}
|
|
||||||
|
|
||||||
setScheduledClasses((prev) => prev.map((c) => (c.id === classSession.id ? updatedClass : c)))
|
|
||||||
// Sınıf başlatıldığında classroom ekranına yönlendir
|
|
||||||
if (updatedClass.id) {
|
|
||||||
navigate(ROUTES_ENUM.protected.admin.classroom.classroom.replace(':id', updatedClass.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatDateTime = (dateString: string) => {
|
const formatDateTime = (dateString: string) => {
|
||||||
return new Date(dateString).toLocaleString('tr-TR', {
|
return new Date(dateString).toLocaleString('tr-TR', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
|
|
@ -242,9 +225,7 @@ const ClassList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 sm:ml-4">
|
<div className="ml-3 sm:ml-4">
|
||||||
<p className="text-xs sm:text-sm font-medium text-gray-600">Toplam Sınıf</p>
|
<p className="text-xs sm:text-sm font-medium text-gray-600">Toplam Sınıf</p>
|
||||||
<p className="text-xl sm:text-2xl font-bold text-gray-900">
|
<p className="text-xl sm:text-2xl font-bold text-gray-900">{classList.length}</p>
|
||||||
{scheduledClasses.length}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
@ -262,7 +243,7 @@ const ClassList: React.FC = () => {
|
||||||
<div className="ml-3 sm:ml-4">
|
<div className="ml-3 sm:ml-4">
|
||||||
<p className="text-xs sm:text-sm font-medium text-gray-600">Aktif Sınıf</p>
|
<p className="text-xs sm:text-sm font-medium text-gray-600">Aktif Sınıf</p>
|
||||||
<p className="text-xl sm:text-2xl font-bold text-gray-900">
|
<p className="text-xl sm:text-2xl font-bold text-gray-900">
|
||||||
{scheduledClasses.filter((c) => c.isActive).length}
|
{classList.filter((c) => c.isActive).length}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -281,7 +262,7 @@ const ClassList: React.FC = () => {
|
||||||
<div className="ml-3 sm:ml-4">
|
<div className="ml-3 sm:ml-4">
|
||||||
<p className="text-xs sm:text-sm font-medium text-gray-600">Toplam Katılımcı</p>
|
<p className="text-xs sm:text-sm font-medium text-gray-600">Toplam Katılımcı</p>
|
||||||
<p className="text-xl sm:text-2xl font-bold text-gray-900">
|
<p className="text-xl sm:text-2xl font-bold text-gray-900">
|
||||||
{scheduledClasses.reduce((sum, c) => sum + c.participantCount, 0)}
|
{classList.reduce((sum, c) => sum + c.participantCount, 0)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -304,14 +285,14 @@ const ClassList: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 sm:p-6">
|
<div className="p-4 sm:p-6">
|
||||||
{scheduledClasses.length === 0 ? (
|
{classList.length === 0 ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<FaCalendarAlt size={48} className="mx-auto text-gray-400 mb-4" />
|
<FaCalendarAlt size={48} className="mx-auto text-gray-400 mb-4" />
|
||||||
<p className="text-gray-500">Henüz programlanmış sınıf bulunmamaktadır.</p>
|
<p className="text-gray-500">Henüz programlanmış sınıf bulunmamaktadır.</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-4 sm:gap-6">
|
<div className="grid gap-4 sm:gap-6">
|
||||||
{scheduledClasses.map((classSession, index) => (
|
{classList.map((classSession, index) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={classSession.id}
|
key={classSession.id}
|
||||||
initial={{ opacity: 0, x: -20 }}
|
initial={{ opacity: 0, x: -20 }}
|
||||||
|
|
@ -440,8 +421,8 @@ const ClassList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Create Class Modal */}
|
{/* Class Modal (Create/Edit) */}
|
||||||
{showCreateModal && (
|
{(showCreateModal || (showEditModal && editingClass)) && (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, scale: 0.95 }}
|
initial={{ opacity: 0, scale: 0.95 }}
|
||||||
|
|
@ -449,18 +430,23 @@ const ClassList: React.FC = () => {
|
||||||
className="bg-white rounded-lg max-w-2xl w-full max-h-[95vh] overflow-y-auto"
|
className="bg-white rounded-lg max-w-2xl w-full max-h-[95vh] overflow-y-auto"
|
||||||
>
|
>
|
||||||
<div className="p-3 sm:p-3 border-b border-gray-200">
|
<div className="p-3 sm:p-3 border-b border-gray-200">
|
||||||
<h2 className="text-xl sm:text-2xl font-bold text-gray-900">Yeni Sınıf Oluştur</h2>
|
<h2 className="text-xl sm:text-2xl font-bold text-gray-900">
|
||||||
|
{showCreateModal ? 'Yeni Sınıf Oluştur' : 'Sınıfı Düzenle'}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleCreateClass} className="p-4 sm:p-6 space-y-4 sm:space-y-6">
|
<form
|
||||||
|
onSubmit={showCreateModal ? handleCreateClass : handleEditClass}
|
||||||
|
className="p-4 sm:p-6 space-y-4 sm:space-y-6"
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">Sınıf Adı *</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">Sınıf Adı *</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus={showCreateModal}
|
||||||
value={formData.name}
|
value={newClassroom.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setNewClassroom({ ...newClassroom, name: e.target.value })}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder="Örn: Matematik 101 - Diferansiyel Denklemler"
|
placeholder="Örn: Matematik 101 - Diferansiyel Denklemler"
|
||||||
/>
|
/>
|
||||||
|
|
@ -469,8 +455,10 @@ const ClassList: React.FC = () => {
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">Açıklama</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">Açıklama</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={formData.description}
|
value={newClassroom.description}
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setNewClassroom({ ...newClassroom, description: e.target.value })
|
||||||
|
}
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder="Ders hakkında kısa açıklama..."
|
placeholder="Ders hakkında kısa açıklama..."
|
||||||
|
|
@ -484,8 +472,8 @@ const ClassList: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.subject}
|
value={newClassroom.subject}
|
||||||
onChange={(e) => setFormData({ ...formData, subject: e.target.value })}
|
onChange={(e) => setNewClassroom({ ...newClassroom, subject: e.target.value })}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder="Örn: Matematik, Fizik, Kimya"
|
placeholder="Örn: Matematik, Fizik, Kimya"
|
||||||
/>
|
/>
|
||||||
|
|
@ -498,10 +486,10 @@ const ClassList: React.FC = () => {
|
||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
required
|
required
|
||||||
value={formData.scheduledStartTime}
|
value={newClassroom.scheduledStartTime}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setNewClassroom({
|
||||||
...formData,
|
...newClassroom,
|
||||||
scheduledStartTime: e.target.value,
|
scheduledStartTime: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -519,10 +507,10 @@ const ClassList: React.FC = () => {
|
||||||
type="number"
|
type="number"
|
||||||
min="15"
|
min="15"
|
||||||
max="480"
|
max="480"
|
||||||
value={formData.duration}
|
value={newClassroom.duration}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setNewClassroom({
|
||||||
...formData,
|
...newClassroom,
|
||||||
duration: parseInt(e.target.value),
|
duration: parseInt(e.target.value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -538,10 +526,10 @@ const ClassList: React.FC = () => {
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
max="100"
|
max="100"
|
||||||
value={formData.maxParticipants}
|
value={newClassroom.maxParticipants}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setNewClassroom({
|
||||||
...formData,
|
...newClassroom,
|
||||||
maxParticipants: parseInt(e.target.value),
|
maxParticipants: parseInt(e.target.value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -560,12 +548,12 @@ const ClassList: React.FC = () => {
|
||||||
<label className="flex items-center space-x-3">
|
<label className="flex items-center space-x-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={formData.settings.allowHandRaise}
|
checked={newClassroom.settings.allowHandRaise}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setNewClassroom({
|
||||||
...formData,
|
...newClassroom,
|
||||||
settings: {
|
settings: {
|
||||||
...formData.settings,
|
...newClassroom.settings,
|
||||||
allowHandRaise: e.target.checked,
|
allowHandRaise: e.target.checked,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -577,12 +565,12 @@ const ClassList: React.FC = () => {
|
||||||
<label className="flex items-center space-x-3">
|
<label className="flex items-center space-x-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={formData.settings.allowStudentChat}
|
checked={newClassroom.settings.allowStudentChat}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setNewClassroom({
|
||||||
...formData,
|
...newClassroom,
|
||||||
settings: {
|
settings: {
|
||||||
...formData.settings,
|
...newClassroom.settings,
|
||||||
allowStudentChat: e.target.checked,
|
allowStudentChat: e.target.checked,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -594,12 +582,12 @@ const ClassList: React.FC = () => {
|
||||||
<label className="flex items-center space-x-3">
|
<label className="flex items-center space-x-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={formData.settings.allowPrivateMessages}
|
checked={newClassroom.settings.allowPrivateMessages}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setNewClassroom({
|
||||||
...formData,
|
...newClassroom,
|
||||||
settings: {
|
settings: {
|
||||||
...formData.settings,
|
...newClassroom.settings,
|
||||||
allowPrivateMessages: e.target.checked,
|
allowPrivateMessages: e.target.checked,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -611,12 +599,12 @@ const ClassList: React.FC = () => {
|
||||||
<label className="flex items-center space-x-3">
|
<label className="flex items-center space-x-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={formData.settings.allowStudentScreenShare}
|
checked={newClassroom.settings.allowStudentScreenShare}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setNewClassroom({
|
||||||
...formData,
|
...newClassroom,
|
||||||
settings: {
|
settings: {
|
||||||
...formData.settings,
|
...newClassroom.settings,
|
||||||
allowStudentScreenShare: e.target.checked,
|
allowStudentScreenShare: e.target.checked,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -635,12 +623,12 @@ const ClassList: React.FC = () => {
|
||||||
Varsayılan mikrofon durumu
|
Varsayılan mikrofon durumu
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={formData.settings.defaultMicrophoneState}
|
value={newClassroom.settings.defaultMicrophoneState}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setNewClassroom({
|
||||||
...formData,
|
...newClassroom,
|
||||||
settings: {
|
settings: {
|
||||||
...formData.settings,
|
...newClassroom.settings,
|
||||||
defaultMicrophoneState: e.target.value as 'muted' | 'unmuted',
|
defaultMicrophoneState: e.target.value as 'muted' | 'unmuted',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -657,12 +645,12 @@ const ClassList: React.FC = () => {
|
||||||
Varsayılan kamera durumu
|
Varsayılan kamera durumu
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={formData.settings.defaultCameraState}
|
value={newClassroom.settings.defaultCameraState}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setNewClassroom({
|
||||||
...formData,
|
...newClassroom,
|
||||||
settings: {
|
settings: {
|
||||||
...formData.settings,
|
...newClassroom.settings,
|
||||||
defaultCameraState: e.target.value as 'on' | 'off',
|
defaultCameraState: e.target.value as 'on' | 'off',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -679,12 +667,12 @@ const ClassList: React.FC = () => {
|
||||||
Varsayılan layout
|
Varsayılan layout
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={formData.settings.defaultLayout}
|
value={newClassroom.settings.defaultLayout}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setNewClassroom({
|
||||||
...formData,
|
...newClassroom,
|
||||||
settings: {
|
settings: {
|
||||||
...formData.settings,
|
...newClassroom.settings,
|
||||||
defaultLayout: e.target.value,
|
defaultLayout: e.target.value,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -701,12 +689,12 @@ const ClassList: React.FC = () => {
|
||||||
<label className="flex items-center space-x-3">
|
<label className="flex items-center space-x-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={formData.settings.autoMuteNewParticipants}
|
checked={newClassroom.settings.autoMuteNewParticipants}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setNewClassroom({
|
||||||
...formData,
|
...newClassroom,
|
||||||
settings: {
|
settings: {
|
||||||
...formData.settings,
|
...newClassroom.settings,
|
||||||
autoMuteNewParticipants: e.target.checked,
|
autoMuteNewParticipants: e.target.checked,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -719,142 +707,16 @@ const ClassList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-end space-x-4 pt-6 border-t border-gray-200">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowCreateModal(false)}
|
|
||||||
className="px-6 py-3 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
|
||||||
>
|
|
||||||
İptal
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
|
||||||
>
|
|
||||||
Sınıf Oluştur
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Edit Class Modal */}
|
|
||||||
{showEditModal && editingClass && (
|
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, scale: 0.95 }}
|
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
|
||||||
className="bg-white rounded-lg max-w-2xl w-full max-h-[95vh] overflow-y-auto"
|
|
||||||
>
|
|
||||||
<div className="p-4 sm:p-6 border-b border-gray-200">
|
|
||||||
<h2 className="text-xl sm:text-2xl font-bold text-gray-900">Sınıfı Düzenle</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={handleEditClass} className="p-4 sm:p-6 space-y-4 sm:space-y-6">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">Sınıf Adı *</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
value={formData.name}
|
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="Örn: Matematik 101 - Diferansiyel Denklemler"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">Açıklama</label>
|
|
||||||
<textarea
|
|
||||||
value={formData.description}
|
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
|
||||||
rows={3}
|
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="Ders hakkında kısa açıklama..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
Ders Konusu
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={formData.subject}
|
|
||||||
onChange={(e) => setFormData({ ...formData, subject: e.target.value })}
|
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="Örn: Matematik, Fizik, Kimya"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
Başlangıç Tarihi ve Saati *
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="datetime-local"
|
|
||||||
required
|
|
||||||
value={formData.scheduledStartTime}
|
|
||||||
onChange={(e) =>
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
scheduledStartTime: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
Süre (dakika)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
min="15"
|
|
||||||
max="480"
|
|
||||||
value={formData.duration}
|
|
||||||
onChange={(e) =>
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
duration: parseInt(e.target.value),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
Maksimum Katılımcı
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="100"
|
|
||||||
value={formData.maxParticipants}
|
|
||||||
onChange={(e) =>
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
maxParticipants: parseInt(e.target.value),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-end space-x-4 pt-6 border-t border-gray-200">
|
<div className="flex items-center justify-end space-x-4 pt-6 border-t border-gray-200">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowEditModal(false)
|
if (showCreateModal) setShowCreateModal(false)
|
||||||
setEditingClass(null)
|
if (showEditModal) {
|
||||||
resetForm()
|
setShowEditModal(false)
|
||||||
|
setEditingClass(null)
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className="px-6 py-3 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
className="px-6 py-3 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
||||||
>
|
>
|
||||||
|
|
@ -864,7 +726,7 @@ const ClassList: React.FC = () => {
|
||||||
type="submit"
|
type="submit"
|
||||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||||
>
|
>
|
||||||
Değişiklikleri Kaydet
|
{showCreateModal ? 'Sınıf Oluştur' : 'Değişiklikleri Kaydet'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,76 @@
|
||||||
import { useEffect } from 'react'
|
import React from 'react'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { FaGraduationCap, FaUserCheck, FaEye } from 'react-icons/fa'
|
||||||
|
import { Role } from '@/proxy/classroom/models'
|
||||||
|
import { useStoreActions, useStoreState } from '@/store/store'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useClassroomLogic } from '@/utils/hooks/useClassroomLogic'
|
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { Container } from '@/components/shared'
|
|
||||||
import { RoleSelector } from '@/components/classroom/RoleSelector'
|
|
||||||
import RoomDetail from './RoomDetail'
|
|
||||||
|
|
||||||
const Dashboard: React.FC = () => {
|
const Dashboard: React.FC = () => {
|
||||||
const { roleState, currentClass, handleRoleSelect, handleLeaveClass } = useClassroomLogic()
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const { user } = useStoreState((state) => state.auth)
|
||||||
|
const { setUser } = useStoreActions((actions) => actions.auth.user)
|
||||||
|
|
||||||
// Eğer dashboard seçildiyse otomatik yönlendirme yap
|
const handleRoleSelect = (role: Role) => {
|
||||||
useEffect(() => {
|
setUser({
|
||||||
if (roleState === 'dashboard') {
|
...user,
|
||||||
navigate(ROUTES_ENUM.protected.admin.classroom.classes, { replace: true })
|
role,
|
||||||
}
|
})
|
||||||
}, [roleState, navigate])
|
|
||||||
|
navigate(ROUTES_ENUM.protected.admin.classroom.classes, { replace: true })
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<div className="flex items-center justify-center p-4">
|
||||||
{roleState === 'role-selection' && <RoleSelector onRoleSelect={handleRoleSelect} />}
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="text-center w-full max-w-4xl"
|
||||||
|
>
|
||||||
|
<p className="text-lg sm:text-xl text-gray-600 mb-8 sm:mb-12">Lütfen rolünüzü seçin</p>
|
||||||
|
|
||||||
{roleState === 'classroom' && currentClass && <RoomDetail />}
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8">
|
||||||
</Container>
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
onClick={() => handleRoleSelect('teacher')}
|
||||||
|
className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-blue-500"
|
||||||
|
>
|
||||||
|
<FaGraduationCap size={48} className="mx-auto text-blue-600 mb-4 sm:mb-4" />
|
||||||
|
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">Öğretmen</h2>
|
||||||
|
<p className="text-gray-600 text-sm sm:text-base">
|
||||||
|
Ders başlatın, öğrencilerle iletişim kurun ve katılım raporlarını görün
|
||||||
|
</p>
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
onClick={() => handleRoleSelect('student')}
|
||||||
|
className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-green-500"
|
||||||
|
>
|
||||||
|
<FaUserCheck size={48} className="mx-auto text-green-600 mb-4 sm:mb-4" />
|
||||||
|
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">Öğrenci</h2>
|
||||||
|
<p className="text-gray-600 text-sm sm:text-base">
|
||||||
|
Aktif derslere katılın, öğretmeniniz ve diğer öğrencilerle etkileşim kurun
|
||||||
|
</p>
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
onClick={() => handleRoleSelect('observer')}
|
||||||
|
className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-purple-500 md:col-span-2 lg:col-span-1"
|
||||||
|
>
|
||||||
|
<FaEye size={48} className="mx-auto text-purple-600 mb-4 sm:mb-4" />
|
||||||
|
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">Gözlemci</h2>
|
||||||
|
<p className="text-gray-600 text-sm sm:text-base">
|
||||||
|
Sınıfı gözlemleyin, eğitim sürecini takip edin (ses/video paylaşımı yok)
|
||||||
|
</p>
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,13 +69,14 @@ const newClassSession: ClassroomDto = {
|
||||||
name: '',
|
name: '',
|
||||||
teacherId: '',
|
teacherId: '',
|
||||||
teacherName: '',
|
teacherName: '',
|
||||||
startTime: '',
|
|
||||||
scheduledStartTime: '',
|
scheduledStartTime: '',
|
||||||
|
actualStartTime: '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
isActive: false,
|
isActive: false,
|
||||||
isScheduled: false,
|
isScheduled: false,
|
||||||
participantCount: 0,
|
participantCount: 0,
|
||||||
settings: undefined,
|
settings: undefined,
|
||||||
|
canJoin: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomDetail: React.FC = () => {
|
const RoomDetail: React.FC = () => {
|
||||||
|
|
@ -128,9 +129,7 @@ const RoomDetail: React.FC = () => {
|
||||||
allowStudentScreenShare: false,
|
allowStudentScreenShare: false,
|
||||||
allowStudentChat: true,
|
allowStudentChat: true,
|
||||||
allowPrivateMessages: true,
|
allowPrivateMessages: true,
|
||||||
autoMuteNewParticipants: true,
|
autoMuteNewParticipants: true
|
||||||
recordSession: false,
|
|
||||||
waitingRoomEnabled: false,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const signalRServiceRef = useRef<SignalRService>()
|
const signalRServiceRef = useRef<SignalRService>()
|
||||||
|
|
@ -1445,30 +1444,6 @@ const RoomDetail: React.FC = () => {
|
||||||
disabled={user.role !== 'teacher'}
|
disabled={user.role !== 'teacher'}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label className="flex items-center justify-between">
|
|
||||||
<span className="text-sm">Bekleme odası aktif</span>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={classSettings.waitingRoomEnabled}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleSettingsChange({ waitingRoomEnabled: e.target.checked })
|
|
||||||
}
|
|
||||||
className="rounded"
|
|
||||||
disabled={user.role !== 'teacher'}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label className="flex items-center justify-between">
|
|
||||||
<span className="text-sm">Oturumu kaydet</span>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={classSettings.recordSession}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleSettingsChange({ recordSession: e.target.checked })
|
|
||||||
}
|
|
||||||
className="rounded"
|
|
||||||
disabled={user.role !== 'teacher'}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue