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.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Kurs.Platform.Classrooms;
|
||||
|
|
@ -19,26 +21,32 @@ public class ClassroomDto : FullAuditedEntityDto<Guid>
|
|||
public bool IsScheduled { get; set; }
|
||||
public int ParticipantCount { 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 string Description { get; set; }
|
||||
public string Subject { get; set; }
|
||||
public DateTime ScheduledStartTime { get; set; }
|
||||
public int Duration { get; set; } = 60;
|
||||
public int MaxParticipants { get; set; } = 30;
|
||||
}
|
||||
|
||||
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 bool AllowHandRaise { get; set; }
|
||||
public bool AllowStudentChat { get; set; }
|
||||
public bool AllowPrivateMessages { get; set; }
|
||||
public bool AllowStudentScreenShare { get; set; }
|
||||
public string DefaultMicrophoneState { get; set; } = "muted"; // 'muted' | 'unmuted'
|
||||
public string DefaultCameraState { get; set; } = "off"; // 'on' | 'off'
|
||||
public string DefaultLayout { get; set; } = "grid";
|
||||
public bool AutoMuteNewParticipants { get; set; }
|
||||
}
|
||||
|
||||
public class GetClassroomListDto : PagedAndSortedResultRequestDto
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ namespace Kurs.Platform.Classrooms;
|
|||
|
||||
public interface IClassroomAppService : IApplicationService
|
||||
{
|
||||
Task<ClassroomDto> CreateAsync(CreateClassroomDto input);
|
||||
Task<PagedResultDto<ClassroomDto>> GetListAsync(GetClassroomListDto input);
|
||||
Task<ClassroomDto> CreateAsync(ClassroomDto input);
|
||||
Task<PagedResultDto<ClassroomDto>> GetListAsync(PagedAndSortedResultRequestDto input);
|
||||
Task<ClassroomDto> GetAsync(Guid id);
|
||||
Task<ClassroomDto> UpdateAsync(Guid id, UpdateClassroomDto input);
|
||||
Task<ClassroomDto> UpdateAsync(Guid id, ClassroomDto input);
|
||||
Task DeleteAsync(Guid id);
|
||||
Task<ClassroomDto> StartClassAsync(Guid id);
|
||||
Task EndClassAsync(Guid id);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Kurs.Platform.Entities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
|
@ -26,40 +27,15 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
|||
_attendanceRepository = attendanceRepository;
|
||||
}
|
||||
|
||||
public async Task<ClassroomDto> CreateAsync(CreateClassroomDto input)
|
||||
public async Task<ClassroomDto> GetAsync(Guid id)
|
||||
{
|
||||
var classSession = new Classroom(
|
||||
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();
|
||||
|
||||
var classSession = await _classSessionRepository.GetAsync(id);
|
||||
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();
|
||||
|
||||
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 items = query
|
||||
.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);
|
||||
}
|
||||
|
||||
public async Task<ClassroomDto> UpdateAsync(Guid id, UpdateClassroomDto input)
|
||||
public async Task<ClassroomDto> UpdateAsync(Guid id, ClassroomDto input)
|
||||
{
|
||||
var classSession = await _classSessionRepository.GetAsync(id);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,6 @@ public class ClassroomAutoMapperProfile : Profile
|
|||
CreateMap<Classroom, ClassroomDto>()
|
||||
.ForMember(dest => dest.CanJoin, opt => opt.MapFrom(src => src.CanJoin()));
|
||||
|
||||
CreateMap<CreateClassroomDto, Classroom>();
|
||||
CreateMap<UpdateClassroomDto, Classroom>();
|
||||
|
||||
CreateMap<ClassAttandance, ClassAttendanceDto>();
|
||||
CreateMap<ClassParticipant, ClassParticipantDto>();
|
||||
CreateMap<ClassChat, ClassChatDto>();
|
||||
|
|
|
|||
|
|
@ -14,11 +14,12 @@ public class Classroom : FullAuditedEntity<Guid>
|
|||
public DateTime ScheduledStartTime { get; set; }
|
||||
public DateTime? ActualStartTime { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
public int Duration { get; set; } // minutes
|
||||
public int Duration { get; set; }
|
||||
public int MaxParticipants { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public bool IsScheduled { get; set; }
|
||||
public int ParticipantCount { get; set; }
|
||||
public string SettingsJson { get; set; }
|
||||
|
||||
public virtual ICollection<ClassParticipant> Participants { get; set; }
|
||||
public virtual ICollection<ClassAttandance> AttendanceRecords { get; set; }
|
||||
|
|
@ -40,7 +41,10 @@ public class Classroom : FullAuditedEntity<Guid>
|
|||
string teacherName,
|
||||
DateTime scheduledStartTime,
|
||||
int duration,
|
||||
int maxParticipants
|
||||
int maxParticipants,
|
||||
bool isActive,
|
||||
bool isScheduled,
|
||||
string settingsJson
|
||||
) : base(id)
|
||||
{
|
||||
Name = name;
|
||||
|
|
@ -51,9 +55,9 @@ public class Classroom : FullAuditedEntity<Guid>
|
|||
ScheduledStartTime = scheduledStartTime;
|
||||
Duration = duration;
|
||||
MaxParticipants = maxParticipants;
|
||||
IsActive = false;
|
||||
IsScheduled = true;
|
||||
ParticipantCount = 0;
|
||||
IsActive = isActive;
|
||||
IsScheduled = isScheduled;
|
||||
SettingsJson = settingsJson;
|
||||
|
||||
Participants = new HashSet<ClassParticipant>();
|
||||
AttendanceRecords = new HashSet<ClassAttandance>();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
|||
namespace Kurs.Platform.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlatformDbContext))]
|
||||
[Migration("20250826055306_Initial")]
|
||||
[Migration("20250826203853_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -1840,6 +1840,9 @@ namespace Kurs.Platform.Migrations
|
|||
b.Property<DateTime>("ScheduledStartTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("SettingsJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
|
@ -804,6 +804,7 @@ namespace Kurs.Platform.Migrations
|
|||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||
IsScheduled = table.Column<bool>(type: "bit", 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),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
|
|
@ -1837,6 +1837,9 @@ namespace Kurs.Platform.Migrations
|
|||
b.Property<DateTime>("ScheduledStartTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("SettingsJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
|||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||
}, {
|
||||
"url": "index.html",
|
||||
"revision": "0.lcv915aoevo"
|
||||
"revision": "0.8cs01edqoio"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/signalr": "^9.0.6",
|
||||
"@babel/generator": "^7.28.3",
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@babel/standalone": "^7.28.0",
|
||||
|
|
@ -30,6 +29,7 @@
|
|||
"@fullcalendar/react": "^6.1.8",
|
||||
"@fullcalendar/timegrid": "^6.1.8",
|
||||
"@marsidev/react-turnstile": "^0.2.1",
|
||||
"@microsoft/signalr": "^9.0.6",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@tanstack/react-query": "^4.29.19",
|
||||
"@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
|
||||
name: string
|
||||
description?: string
|
||||
subject?: string
|
||||
teacherId: string
|
||||
teacherName: string
|
||||
startTime: string
|
||||
scheduledStartTime: string
|
||||
actualStartTime?: string
|
||||
endTime?: string
|
||||
duration?: number
|
||||
maxParticipants?: number
|
||||
isActive: boolean
|
||||
isScheduled: boolean
|
||||
participantCount: number
|
||||
maxParticipants?: number
|
||||
subject?: string
|
||||
duration?: number // in minutes
|
||||
canJoin: boolean
|
||||
settings?: ClassroomSettingsDto
|
||||
}
|
||||
|
||||
export interface ClassroomSettingsDto {
|
||||
allowHandRaise: boolean
|
||||
allowStudentChat: boolean
|
||||
allowPrivateMessages: boolean
|
||||
allowStudentScreenShare: boolean
|
||||
defaultMicrophoneState: 'muted' | 'unmuted'
|
||||
defaultCameraState: 'on' | 'off'
|
||||
defaultLayout: string
|
||||
allowStudentScreenShare: boolean
|
||||
allowStudentChat: boolean
|
||||
allowPrivateMessages: boolean
|
||||
autoMuteNewParticipants: boolean
|
||||
recordSession: boolean
|
||||
waitingRoomEnabled: boolean
|
||||
}
|
||||
|
||||
export interface ClassAttendanceDto {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { ClassroomDto } from '@/proxy/classroom/models'
|
||||
import apiService from './api.service'
|
||||
import { PagedAndSortedResultRequestDto, PagedResultDto } from '@/proxy'
|
||||
|
||||
export const getClassroomById = (id: string) =>
|
||||
apiService.fetchData<ClassroomDto>({
|
||||
|
|
@ -8,47 +9,29 @@ export const getClassroomById = (id: string) =>
|
|||
params: { id },
|
||||
})
|
||||
|
||||
export const getClassrooms = () =>
|
||||
apiService.fetchData<ClassroomDto[]>({
|
||||
export const getClassrooms = (input: PagedAndSortedResultRequestDto) =>
|
||||
apiService.fetchData<PagedResultDto<ClassroomDto>>({
|
||||
method: 'GET',
|
||||
url: `/api/app/classroom`,
|
||||
params: input,
|
||||
})
|
||||
|
||||
// export const getChartOptions = (chartCode: string) =>
|
||||
// apiService.fetchData<ChartDto>({
|
||||
// method: 'GET',
|
||||
// url: `/api/app/charts/chart-options`,
|
||||
// params: { chartCode },
|
||||
// })
|
||||
export const createClassroom = (input: ClassroomDto) =>
|
||||
apiService.fetchData<ClassroomDto>({
|
||||
method: 'POST',
|
||||
url: `/api/app/classroom`,
|
||||
data: input as any,
|
||||
})
|
||||
|
||||
// export const getChartSelect = (chartCode: string, filter?: string) =>
|
||||
// apiService.fetchData({
|
||||
// method: 'GET',
|
||||
// url: `/api/app/chart-select/select`,
|
||||
// params: { chartCode, filter },
|
||||
// })
|
||||
export const updateClassroom = (input: ClassroomDto) =>
|
||||
apiService.fetchData({
|
||||
method: 'PUT',
|
||||
url: `/api/app/classroom/${input.id}`,
|
||||
data: input,
|
||||
})
|
||||
|
||||
// export const putCharts = (input: ChartEditDto) =>
|
||||
// apiService.fetchData({
|
||||
// method: 'PUT',
|
||||
// url: `/api/app/charts/${input.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,
|
||||
// })
|
||||
export const deleteClassroom = (id: string) =>
|
||||
apiService.fetchData({
|
||||
method: 'DELETE',
|
||||
url: `/api/app/classroom/${id}`,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import * as signalR from '@microsoft/signalr'
|
|||
export class SignalRService {
|
||||
private connection!: signalR.HubConnection
|
||||
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 onAttendanceUpdate?: (record: ClassAttendanceDto) => void
|
||||
private onParticipantJoined?: (userId: string, name: string) => void
|
||||
|
|
|
|||
|
|
@ -18,16 +18,6 @@ export function useClassroomLogic() {
|
|||
setRoleState('dashboard')
|
||||
}
|
||||
|
||||
const handleJoinClass = (classSession: ClassroomDto, userName?: string) => {
|
||||
setCurrentClass(classSession)
|
||||
setRoleState('classroom')
|
||||
}
|
||||
|
||||
const handleLeaveClass = () => {
|
||||
setCurrentClass(null)
|
||||
setRoleState('dashboard')
|
||||
}
|
||||
|
||||
const handleCreateClass = (classData: Partial<ClassroomDto>) => {
|
||||
const newClass = {
|
||||
...classData,
|
||||
|
|
@ -57,8 +47,6 @@ export function useClassroomLogic() {
|
|||
allClasses,
|
||||
setAllClasses,
|
||||
handleRoleSelect,
|
||||
handleJoinClass,
|
||||
handleLeaveClass,
|
||||
handleCreateClass,
|
||||
handleEditClass,
|
||||
handleDeleteClass,
|
||||
|
|
|
|||
|
|
@ -13,24 +13,21 @@ import {
|
|||
} from 'react-icons/fa'
|
||||
|
||||
import { ClassroomDto } from '@/proxy/classroom/models'
|
||||
import { initialScheduledClasses } from '@/proxy/classroom/data'
|
||||
import { useStoreState } from '@/store/store'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
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 navigate = useNavigate()
|
||||
const { user } = useStoreState((state) => state.auth)
|
||||
|
||||
const [showCreateModal, setShowCreateModal] = useState(false)
|
||||
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({
|
||||
const newClassEntity = {
|
||||
name: '',
|
||||
description: '',
|
||||
subject: '',
|
||||
|
|
@ -39,17 +36,133 @@ const ClassList: React.FC = () => {
|
|||
maxParticipants: 30,
|
||||
settings: {
|
||||
allowHandRaise: true,
|
||||
defaultMicrophoneState: 'muted' as 'muted' | 'unmuted',
|
||||
defaultCameraState: 'on' as 'on' | 'off',
|
||||
defaultLayout: 'grid',
|
||||
allowStudentScreenShare: false,
|
||||
allowStudentChat: true,
|
||||
allowPrivateMessages: true,
|
||||
allowStudentScreenShare: false,
|
||||
defaultMicrophoneState: 'muted',
|
||||
defaultCameraState: 'on',
|
||||
defaultLayout: 'grid',
|
||||
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 scheduled = new Date(scheduledTime)
|
||||
|
|
@ -85,136 +198,6 @@ const ClassList: React.FC = () => {
|
|||
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) => {
|
||||
return new Date(dateString).toLocaleString('tr-TR', {
|
||||
day: '2-digit',
|
||||
|
|
@ -242,9 +225,7 @@ const ClassList: React.FC = () => {
|
|||
</div>
|
||||
<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-xl sm:text-2xl font-bold text-gray-900">
|
||||
{scheduledClasses.length}
|
||||
</p>
|
||||
<p className="text-xl sm:text-2xl font-bold text-gray-900">{classList.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
|
@ -262,7 +243,7 @@ const ClassList: React.FC = () => {
|
|||
<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-xl sm:text-2xl font-bold text-gray-900">
|
||||
{scheduledClasses.filter((c) => c.isActive).length}
|
||||
{classList.filter((c) => c.isActive).length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -281,7 +262,7 @@ const ClassList: React.FC = () => {
|
|||
<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-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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -304,14 +285,14 @@ const ClassList: React.FC = () => {
|
|||
)}
|
||||
</div>
|
||||
<div className="p-4 sm:p-6">
|
||||
{scheduledClasses.length === 0 ? (
|
||||
{classList.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<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>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 sm:gap-6">
|
||||
{scheduledClasses.map((classSession, index) => (
|
||||
{classList.map((classSession, index) => (
|
||||
<motion.div
|
||||
key={classSession.id}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
|
|
@ -440,8 +421,8 @@ const ClassList: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Create Class Modal */}
|
||||
{showCreateModal && (
|
||||
{/* Class Modal (Create/Edit) */}
|
||||
{(showCreateModal || (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 }}
|
||||
|
|
@ -449,18 +430,23 @@ const ClassList: React.FC = () => {
|
|||
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">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Sınıf Adı *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
autoFocus
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
autoFocus={showCreateModal}
|
||||
value={newClassroom.name}
|
||||
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"
|
||||
placeholder="Örn: Matematik 101 - Diferansiyel Denklemler"
|
||||
/>
|
||||
|
|
@ -469,8 +455,10 @@ const ClassList: React.FC = () => {
|
|||
<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 })}
|
||||
value={newClassroom.description}
|
||||
onChange={(e) =>
|
||||
setNewClassroom({ ...newClassroom, description: e.target.value })
|
||||
}
|
||||
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"
|
||||
placeholder="Ders hakkında kısa açıklama..."
|
||||
|
|
@ -484,8 +472,8 @@ const ClassList: React.FC = () => {
|
|||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.subject}
|
||||
onChange={(e) => setFormData({ ...formData, subject: e.target.value })}
|
||||
value={newClassroom.subject}
|
||||
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"
|
||||
placeholder="Örn: Matematik, Fizik, Kimya"
|
||||
/>
|
||||
|
|
@ -498,10 +486,10 @@ const ClassList: React.FC = () => {
|
|||
<input
|
||||
type="datetime-local"
|
||||
required
|
||||
value={formData.scheduledStartTime}
|
||||
value={newClassroom.scheduledStartTime}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
setNewClassroom({
|
||||
...newClassroom,
|
||||
scheduledStartTime: e.target.value,
|
||||
})
|
||||
}
|
||||
|
|
@ -519,10 +507,10 @@ const ClassList: React.FC = () => {
|
|||
type="number"
|
||||
min="15"
|
||||
max="480"
|
||||
value={formData.duration}
|
||||
value={newClassroom.duration}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
setNewClassroom({
|
||||
...newClassroom,
|
||||
duration: parseInt(e.target.value),
|
||||
})
|
||||
}
|
||||
|
|
@ -538,10 +526,10 @@ const ClassList: React.FC = () => {
|
|||
type="number"
|
||||
min="1"
|
||||
max="100"
|
||||
value={formData.maxParticipants}
|
||||
value={newClassroom.maxParticipants}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
setNewClassroom({
|
||||
...newClassroom,
|
||||
maxParticipants: parseInt(e.target.value),
|
||||
})
|
||||
}
|
||||
|
|
@ -560,12 +548,12 @@ const ClassList: React.FC = () => {
|
|||
<label className="flex items-center space-x-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.settings.allowHandRaise}
|
||||
checked={newClassroom.settings.allowHandRaise}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
setNewClassroom({
|
||||
...newClassroom,
|
||||
settings: {
|
||||
...formData.settings,
|
||||
...newClassroom.settings,
|
||||
allowHandRaise: e.target.checked,
|
||||
},
|
||||
})
|
||||
|
|
@ -577,12 +565,12 @@ const ClassList: React.FC = () => {
|
|||
<label className="flex items-center space-x-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.settings.allowStudentChat}
|
||||
checked={newClassroom.settings.allowStudentChat}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
setNewClassroom({
|
||||
...newClassroom,
|
||||
settings: {
|
||||
...formData.settings,
|
||||
...newClassroom.settings,
|
||||
allowStudentChat: e.target.checked,
|
||||
},
|
||||
})
|
||||
|
|
@ -594,12 +582,12 @@ const ClassList: React.FC = () => {
|
|||
<label className="flex items-center space-x-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.settings.allowPrivateMessages}
|
||||
checked={newClassroom.settings.allowPrivateMessages}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
setNewClassroom({
|
||||
...newClassroom,
|
||||
settings: {
|
||||
...formData.settings,
|
||||
...newClassroom.settings,
|
||||
allowPrivateMessages: e.target.checked,
|
||||
},
|
||||
})
|
||||
|
|
@ -611,12 +599,12 @@ const ClassList: React.FC = () => {
|
|||
<label className="flex items-center space-x-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.settings.allowStudentScreenShare}
|
||||
checked={newClassroom.settings.allowStudentScreenShare}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
setNewClassroom({
|
||||
...newClassroom,
|
||||
settings: {
|
||||
...formData.settings,
|
||||
...newClassroom.settings,
|
||||
allowStudentScreenShare: e.target.checked,
|
||||
},
|
||||
})
|
||||
|
|
@ -635,12 +623,12 @@ const ClassList: React.FC = () => {
|
|||
Varsayılan mikrofon durumu
|
||||
</label>
|
||||
<select
|
||||
value={formData.settings.defaultMicrophoneState}
|
||||
value={newClassroom.settings.defaultMicrophoneState}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
setNewClassroom({
|
||||
...newClassroom,
|
||||
settings: {
|
||||
...formData.settings,
|
||||
...newClassroom.settings,
|
||||
defaultMicrophoneState: e.target.value as 'muted' | 'unmuted',
|
||||
},
|
||||
})
|
||||
|
|
@ -657,12 +645,12 @@ const ClassList: React.FC = () => {
|
|||
Varsayılan kamera durumu
|
||||
</label>
|
||||
<select
|
||||
value={formData.settings.defaultCameraState}
|
||||
value={newClassroom.settings.defaultCameraState}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
setNewClassroom({
|
||||
...newClassroom,
|
||||
settings: {
|
||||
...formData.settings,
|
||||
...newClassroom.settings,
|
||||
defaultCameraState: e.target.value as 'on' | 'off',
|
||||
},
|
||||
})
|
||||
|
|
@ -679,12 +667,12 @@ const ClassList: React.FC = () => {
|
|||
Varsayılan layout
|
||||
</label>
|
||||
<select
|
||||
value={formData.settings.defaultLayout}
|
||||
value={newClassroom.settings.defaultLayout}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
setNewClassroom({
|
||||
...newClassroom,
|
||||
settings: {
|
||||
...formData.settings,
|
||||
...newClassroom.settings,
|
||||
defaultLayout: e.target.value,
|
||||
},
|
||||
})
|
||||
|
|
@ -701,12 +689,12 @@ const ClassList: React.FC = () => {
|
|||
<label className="flex items-center space-x-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.settings.autoMuteNewParticipants}
|
||||
checked={newClassroom.settings.autoMuteNewParticipants}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
setNewClassroom({
|
||||
...newClassroom,
|
||||
settings: {
|
||||
...formData.settings,
|
||||
...newClassroom.settings,
|
||||
autoMuteNewParticipants: e.target.checked,
|
||||
},
|
||||
})
|
||||
|
|
@ -719,142 +707,16 @@ const ClassList: React.FC = () => {
|
|||
</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">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowEditModal(false)
|
||||
setEditingClass(null)
|
||||
resetForm()
|
||||
if (showCreateModal) setShowCreateModal(false)
|
||||
if (showEditModal) {
|
||||
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"
|
||||
>
|
||||
|
|
@ -864,7 +726,7 @@ const ClassList: React.FC = () => {
|
|||
type="submit"
|
||||
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>
|
||||
</div>
|
||||
</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 { useClassroomLogic } from '@/utils/hooks/useClassroomLogic'
|
||||
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 { roleState, currentClass, handleRoleSelect, handleLeaveClass } = useClassroomLogic()
|
||||
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
|
||||
useEffect(() => {
|
||||
if (roleState === 'dashboard') {
|
||||
navigate(ROUTES_ENUM.protected.admin.classroom.classes, { replace: true })
|
||||
}
|
||||
}, [roleState, navigate])
|
||||
const handleRoleSelect = (role: Role) => {
|
||||
setUser({
|
||||
...user,
|
||||
role,
|
||||
})
|
||||
|
||||
navigate(ROUTES_ENUM.protected.admin.classroom.classes, { replace: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{roleState === 'role-selection' && <RoleSelector onRoleSelect={handleRoleSelect} />}
|
||||
<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>
|
||||
|
||||
{roleState === 'classroom' && currentClass && <RoomDetail />}
|
||||
</Container>
|
||||
<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={() => 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: '',
|
||||
teacherId: '',
|
||||
teacherName: '',
|
||||
startTime: '',
|
||||
scheduledStartTime: '',
|
||||
actualStartTime: '',
|
||||
endTime: '',
|
||||
isActive: false,
|
||||
isScheduled: false,
|
||||
participantCount: 0,
|
||||
settings: undefined,
|
||||
canJoin: false,
|
||||
}
|
||||
|
||||
const RoomDetail: React.FC = () => {
|
||||
|
|
@ -128,9 +129,7 @@ const RoomDetail: React.FC = () => {
|
|||
allowStudentScreenShare: false,
|
||||
allowStudentChat: true,
|
||||
allowPrivateMessages: true,
|
||||
autoMuteNewParticipants: true,
|
||||
recordSession: false,
|
||||
waitingRoomEnabled: false,
|
||||
autoMuteNewParticipants: true
|
||||
})
|
||||
|
||||
const signalRServiceRef = useRef<SignalRService>()
|
||||
|
|
@ -1445,30 +1444,6 @@ const RoomDetail: React.FC = () => {
|
|||
disabled={user.role !== 'teacher'}
|
||||
/>
|
||||
</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>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue