virtual classroom
This commit is contained in:
parent
e96faabd76
commit
0b60f006d0
18 changed files with 356 additions and 324 deletions
|
|
@ -13,14 +13,12 @@ public class ClassroomDto : FullAuditedEntityDto<Guid>
|
||||||
public Guid TeacherId { get; set; }
|
public Guid TeacherId { get; set; }
|
||||||
public string TeacherName { get; set; }
|
public string TeacherName { get; set; }
|
||||||
public DateTime ScheduledStartTime { get; set; }
|
public DateTime ScheduledStartTime { get; set; }
|
||||||
public DateTime? ActualStartTime { get; set; }
|
public DateTime? ScheduledEndTime { get; set; }
|
||||||
public DateTime? EndTime { get; set; }
|
|
||||||
public int Duration { get; set; }
|
public int Duration { get; set; }
|
||||||
|
public DateTime? ActualStartTime { get; set; }
|
||||||
|
public DateTime? ActualEndTime { get; set; }
|
||||||
public int MaxParticipants { get; set; }
|
public int MaxParticipants { get; set; }
|
||||||
public bool IsActive { get; set; }
|
|
||||||
public bool IsScheduled { get; set; }
|
|
||||||
public int ParticipantCount { get; set; }
|
public int ParticipantCount { get; set; }
|
||||||
public bool CanJoin { get; set; }
|
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string SettingsJson { get; set; }
|
public string SettingsJson { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -60,10 +60,9 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
CurrentUser.Id,
|
CurrentUser.Id,
|
||||||
CurrentUser.Name,
|
CurrentUser.Name,
|
||||||
input.ScheduledStartTime,
|
input.ScheduledStartTime,
|
||||||
|
input.ScheduledStartTime.AddMinutes(input.Duration),
|
||||||
input.Duration,
|
input.Duration,
|
||||||
input.MaxParticipants,
|
input.MaxParticipants,
|
||||||
false,
|
|
||||||
true,
|
|
||||||
input.SettingsJson = JsonSerializer.Serialize(input.SettingsDto)
|
input.SettingsJson = JsonSerializer.Serialize(input.SettingsDto)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -81,22 +80,15 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
throw new UnauthorizedAccessException("Only the teacher can update this class");
|
throw new UnauthorizedAccessException("Only the teacher can update this class");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (classSession.IsActive)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Cannot update an active class");
|
|
||||||
}
|
|
||||||
|
|
||||||
classSession.Name = input.Name;
|
classSession.Name = input.Name;
|
||||||
classSession.Description = input.Description;
|
classSession.Description = input.Description;
|
||||||
classSession.Subject = input.Subject;
|
classSession.Subject = input.Subject;
|
||||||
classSession.TeacherId = input.TeacherId;
|
classSession.TeacherId = input.TeacherId;
|
||||||
classSession.TeacherName = input.TeacherName;
|
classSession.TeacherName = input.TeacherName;
|
||||||
classSession.ScheduledStartTime = input.ScheduledStartTime;
|
classSession.ScheduledStartTime = input.ScheduledStartTime;
|
||||||
classSession.ActualStartTime = input.ActualStartTime;
|
classSession.ScheduledEndTime = input.ScheduledStartTime.AddMinutes(input.Duration);
|
||||||
classSession.Duration = input.Duration;
|
classSession.Duration = input.Duration;
|
||||||
classSession.MaxParticipants = input.MaxParticipants;
|
classSession.MaxParticipants = input.MaxParticipants;
|
||||||
classSession.IsActive = input.IsActive;
|
|
||||||
classSession.IsScheduled = input.IsScheduled;
|
|
||||||
classSession.SettingsJson = JsonSerializer.Serialize(input.SettingsDto);
|
classSession.SettingsJson = JsonSerializer.Serialize(input.SettingsDto);
|
||||||
|
|
||||||
await _classSessionRepository.UpdateAsync(classSession);
|
await _classSessionRepository.UpdateAsync(classSession);
|
||||||
|
|
@ -112,11 +104,6 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
throw new UnauthorizedAccessException("Only the teacher can delete this class");
|
throw new UnauthorizedAccessException("Only the teacher can delete this class");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (classSession.IsActive)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Cannot delete an active class");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _classSessionRepository.DeleteAsync(id);
|
await _classSessionRepository.DeleteAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,15 +117,6 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
throw new UnauthorizedAccessException("Only the teacher can start this class");
|
throw new UnauthorizedAccessException("Only the teacher can start this class");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!classSession.CanJoin())
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Class cannot be started at this time");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (classSession.IsActive)
|
|
||||||
throw new InvalidOperationException("Class is already active");
|
|
||||||
|
|
||||||
classSession.IsActive = true;
|
|
||||||
classSession.ActualStartTime = DateTime.Now;
|
classSession.ActualStartTime = DateTime.Now;
|
||||||
|
|
||||||
await _classSessionRepository.UpdateAsync(classSession);
|
await _classSessionRepository.UpdateAsync(classSession);
|
||||||
|
|
@ -156,11 +134,7 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
throw new UnauthorizedAccessException("Only the teacher can end this class");
|
throw new UnauthorizedAccessException("Only the teacher can end this class");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!classSession.IsActive)
|
classSession.ActualEndTime = DateTime.Now;
|
||||||
throw new InvalidOperationException("Class is not active");
|
|
||||||
|
|
||||||
classSession.IsActive = false;
|
|
||||||
classSession.EndTime = DateTime.Now;
|
|
||||||
|
|
||||||
await _classSessionRepository.UpdateAsync(classSession);
|
await _classSessionRepository.UpdateAsync(classSession);
|
||||||
|
|
||||||
|
|
@ -171,7 +145,7 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
|
|
||||||
foreach (var attendance in activeAttendances)
|
foreach (var attendance in activeAttendances)
|
||||||
{
|
{
|
||||||
attendance.LeaveTime = DateTime.UtcNow;
|
attendance.LeaveTime = DateTime.Now;
|
||||||
attendance.CalculateDuration();
|
attendance.CalculateDuration();
|
||||||
await _attendanceRepository.UpdateAsync(attendance);
|
await _attendanceRepository.UpdateAsync(attendance);
|
||||||
}
|
}
|
||||||
|
|
@ -181,11 +155,6 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
{
|
{
|
||||||
var classSession = await _classSessionRepository.GetAsync(id);
|
var classSession = await _classSessionRepository.GetAsync(id);
|
||||||
|
|
||||||
if (!classSession.CanJoin())
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Cannot join this class at this time");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (classSession.ParticipantCount >= classSession.MaxParticipants)
|
if (classSession.ParticipantCount >= classSession.MaxParticipants)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Class is full");
|
throw new InvalidOperationException("Class is full");
|
||||||
|
|
@ -216,7 +185,7 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
id,
|
id,
|
||||||
CurrentUser.Id,
|
CurrentUser.Id,
|
||||||
CurrentUser.Name,
|
CurrentUser.Name,
|
||||||
DateTime.UtcNow
|
DateTime.Now
|
||||||
);
|
);
|
||||||
|
|
||||||
await _attendanceRepository.InsertAsync(attendance);
|
await _attendanceRepository.InsertAsync(attendance);
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,7 @@ public class ClassroomAutoMapperProfile : Profile
|
||||||
{
|
{
|
||||||
public ClassroomAutoMapperProfile()
|
public ClassroomAutoMapperProfile()
|
||||||
{
|
{
|
||||||
CreateMap<Classroom, ClassroomDto>()
|
CreateMap<Classroom, ClassroomDto>();
|
||||||
.ForMember(dest => dest.CanJoin, opt => opt.MapFrom(src => src.CanJoin()));
|
|
||||||
|
|
||||||
CreateMap<ClassAttandance, ClassAttendanceDto>();
|
CreateMap<ClassAttandance, ClassAttendanceDto>();
|
||||||
CreateMap<ClassParticipant, ClassParticipantDto>();
|
CreateMap<ClassParticipant, ClassParticipantDto>();
|
||||||
CreateMap<ClassChat, ClassChatDto>();
|
CreateMap<ClassChat, ClassChatDto>();
|
||||||
|
|
|
||||||
|
|
@ -1046,10 +1046,9 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
item.TeacherId,
|
item.TeacherId,
|
||||||
item.TeacherName,
|
item.TeacherName,
|
||||||
item.ScheduledStartTime,
|
item.ScheduledStartTime,
|
||||||
|
item.ScheduledEndTime,
|
||||||
item.Duration,
|
item.Duration,
|
||||||
item.MaxParticipants,
|
item.MaxParticipants,
|
||||||
item.IsActive,
|
|
||||||
item.IsScheduled,
|
|
||||||
item.SettingsJson
|
item.SettingsJson
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17683,9 +17683,10 @@
|
||||||
"teacherId": "995220ff-2751-afd6-3d99-3a1bfc55f78e",
|
"teacherId": "995220ff-2751-afd6-3d99-3a1bfc55f78e",
|
||||||
"teacherName": "Prof. Dr. Mehmet Özkan",
|
"teacherName": "Prof. Dr. Mehmet Özkan",
|
||||||
"scheduledStartTime": "2025-08-27T10:00:00Z",
|
"scheduledStartTime": "2025-08-27T10:00:00Z",
|
||||||
"actualStartTime": "",
|
"scheduledEndTime": "2025-08-28T11:30:00Z",
|
||||||
"endTime": "",
|
|
||||||
"duration": 90,
|
"duration": 90,
|
||||||
|
"actualStartTime": "",
|
||||||
|
"actualEndTime": "",
|
||||||
"maxParticipants": 30,
|
"maxParticipants": 30,
|
||||||
"isActive": false,
|
"isActive": false,
|
||||||
"isScheduled": true,
|
"isScheduled": true,
|
||||||
|
|
@ -17699,9 +17700,10 @@
|
||||||
"teacherId": "995220ff-2751-afd6-3d99-3a1bfc55f78e",
|
"teacherId": "995220ff-2751-afd6-3d99-3a1bfc55f78e",
|
||||||
"teacherName": "Dr. Ayşe Kaya",
|
"teacherName": "Dr. Ayşe Kaya",
|
||||||
"scheduledStartTime": "2025-08-26T10:00:00Z",
|
"scheduledStartTime": "2025-08-26T10:00:00Z",
|
||||||
"actualStartTime": "",
|
"scheduledEndTime": "2025-08-28T12:00:00Z",
|
||||||
"endTime": "",
|
|
||||||
"duration": 120,
|
"duration": 120,
|
||||||
|
"actualStartTime": "",
|
||||||
|
"actualEndTime": "",
|
||||||
"maxParticipants": 25,
|
"maxParticipants": 25,
|
||||||
"isActive": false,
|
"isActive": false,
|
||||||
"isScheduled": true,
|
"isScheduled": true,
|
||||||
|
|
@ -17715,9 +17717,10 @@
|
||||||
"teacherId": "995220ff-2751-afd6-3d99-3a1bfc55f78e",
|
"teacherId": "995220ff-2751-afd6-3d99-3a1bfc55f78e",
|
||||||
"teacherName": "Dr. Ali Veli",
|
"teacherName": "Dr. Ali Veli",
|
||||||
"scheduledStartTime": "2025-08-28T10:00:00Z",
|
"scheduledStartTime": "2025-08-28T10:00:00Z",
|
||||||
"actualStartTime": "",
|
"scheduledEndTime": "2025-08-28T11:15:00Z",
|
||||||
"endTime": "",
|
|
||||||
"duration": 75,
|
"duration": 75,
|
||||||
|
"actualStartTime": "",
|
||||||
|
"actualEndTime": "",
|
||||||
"maxParticipants": 20,
|
"maxParticipants": 20,
|
||||||
"isActive": false,
|
"isActive": false,
|
||||||
"isScheduled": true,
|
"isScheduled": true,
|
||||||
|
|
|
||||||
|
|
@ -335,8 +335,7 @@ public class ClassroomSeedDto
|
||||||
public Guid? TeacherId { get; set; }
|
public Guid? TeacherId { get; set; }
|
||||||
public string TeacherName { get; set; }
|
public string TeacherName { get; set; }
|
||||||
public DateTime ScheduledStartTime { get; set; }
|
public DateTime ScheduledStartTime { get; set; }
|
||||||
public DateTime? ActualStartTime { get; set; }
|
public DateTime ScheduledEndTime { get; set; }
|
||||||
public DateTime? EndTime { get; set; }
|
|
||||||
public int Duration { get; set; }
|
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; }
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,11 @@ public class Classroom : FullAuditedEntity<Guid>
|
||||||
public Guid? TeacherId { get; set; }
|
public Guid? TeacherId { get; set; }
|
||||||
public string TeacherName { get; set; }
|
public string TeacherName { get; set; }
|
||||||
public DateTime ScheduledStartTime { get; set; }
|
public DateTime ScheduledStartTime { get; set; }
|
||||||
public DateTime? ActualStartTime { get; set; }
|
public DateTime? ScheduledEndTime { get; set; }
|
||||||
public DateTime? EndTime { get; set; }
|
|
||||||
public int Duration { get; set; }
|
public int Duration { get; set; }
|
||||||
|
public DateTime? ActualStartTime { get; set; }
|
||||||
|
public DateTime? ActualEndTime { get; set; }
|
||||||
public int MaxParticipants { get; set; }
|
public int MaxParticipants { get; set; }
|
||||||
public bool IsActive { get; set; }
|
|
||||||
public bool IsScheduled { get; set; }
|
|
||||||
public int ParticipantCount { get; set; }
|
public int ParticipantCount { get; set; }
|
||||||
public string SettingsJson { get; set; }
|
public string SettingsJson { get; set; }
|
||||||
|
|
||||||
|
|
@ -40,10 +39,9 @@ public class Classroom : FullAuditedEntity<Guid>
|
||||||
Guid? teacherId,
|
Guid? teacherId,
|
||||||
string teacherName,
|
string teacherName,
|
||||||
DateTime scheduledStartTime,
|
DateTime scheduledStartTime,
|
||||||
|
DateTime? scheduledEndTime,
|
||||||
int duration,
|
int duration,
|
||||||
int maxParticipants,
|
int maxParticipants,
|
||||||
bool isActive,
|
|
||||||
bool isScheduled,
|
|
||||||
string settingsJson
|
string settingsJson
|
||||||
) : base(id)
|
) : base(id)
|
||||||
{
|
{
|
||||||
|
|
@ -53,23 +51,13 @@ public class Classroom : FullAuditedEntity<Guid>
|
||||||
TeacherId = teacherId;
|
TeacherId = teacherId;
|
||||||
TeacherName = teacherName;
|
TeacherName = teacherName;
|
||||||
ScheduledStartTime = scheduledStartTime;
|
ScheduledStartTime = scheduledStartTime;
|
||||||
|
ScheduledEndTime = scheduledEndTime;
|
||||||
Duration = duration;
|
Duration = duration;
|
||||||
MaxParticipants = maxParticipants;
|
MaxParticipants = maxParticipants;
|
||||||
IsActive = isActive;
|
|
||||||
IsScheduled = isScheduled;
|
|
||||||
SettingsJson = settingsJson;
|
SettingsJson = settingsJson;
|
||||||
|
|
||||||
Participants = new HashSet<ClassParticipant>();
|
Participants = new HashSet<ClassParticipant>();
|
||||||
AttendanceRecords = new HashSet<ClassAttandance>();
|
AttendanceRecords = new HashSet<ClassAttandance>();
|
||||||
ChatMessages = new HashSet<ClassChat>();
|
ChatMessages = new HashSet<ClassChat>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanJoin()
|
|
||||||
{
|
|
||||||
var now = DateTime.Now;
|
|
||||||
var tenMinutesBefore = ScheduledStartTime.AddMinutes(-10);
|
|
||||||
var twoHoursAfter = ScheduledStartTime.AddHours(2);
|
|
||||||
|
|
||||||
return now >= tenMinutesBefore && now <= twoHoursAfter && ParticipantCount < MaxParticipants;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -894,7 +894,6 @@ public class PlatformDbContext :
|
||||||
|
|
||||||
b.HasIndex(x => x.TeacherId);
|
b.HasIndex(x => x.TeacherId);
|
||||||
b.HasIndex(x => x.ScheduledStartTime);
|
b.HasIndex(x => x.ScheduledStartTime);
|
||||||
b.HasIndex(x => x.IsActive);
|
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
b.HasMany(x => x.Participants)
|
b.HasMany(x => x.Participants)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Kurs.Platform.Migrations
|
namespace Kurs.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20250826203853_Initial")]
|
[Migration("20250828112303_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -1668,7 +1668,7 @@ namespace Kurs.Platform.Migrations
|
||||||
.HasMaxLength(2000)
|
.HasMaxLength(2000)
|
||||||
.HasColumnType("nvarchar(2000)");
|
.HasColumnType("nvarchar(2000)");
|
||||||
|
|
||||||
b.Property<Guid>("SenderId")
|
b.Property<Guid?>("SenderId")
|
||||||
.HasColumnType("uniqueidentifier");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("SenderName")
|
b.Property<string>("SenderName")
|
||||||
|
|
@ -1777,6 +1777,9 @@ namespace Kurs.Platform.Migrations
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.HasColumnType("uniqueidentifier");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ActualEndTime")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
b.Property<DateTime?>("ActualStartTime")
|
b.Property<DateTime?>("ActualStartTime")
|
||||||
.HasColumnType("datetime2");
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
|
@ -1803,21 +1806,12 @@ namespace Kurs.Platform.Migrations
|
||||||
b.Property<int>("Duration")
|
b.Property<int>("Duration")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<DateTime?>("EndTime")
|
|
||||||
.HasColumnType("datetime2");
|
|
||||||
|
|
||||||
b.Property<bool>("IsActive")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<bool>("IsDeleted")
|
b.Property<bool>("IsDeleted")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("bit")
|
.HasColumnType("bit")
|
||||||
.HasDefaultValue(false)
|
.HasDefaultValue(false)
|
||||||
.HasColumnName("IsDeleted");
|
.HasColumnName("IsDeleted");
|
||||||
|
|
||||||
b.Property<bool>("IsScheduled")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("LastModificationTime")
|
b.Property<DateTime?>("LastModificationTime")
|
||||||
.HasColumnType("datetime2")
|
.HasColumnType("datetime2")
|
||||||
.HasColumnName("LastModificationTime");
|
.HasColumnName("LastModificationTime");
|
||||||
|
|
@ -1837,6 +1831,9 @@ namespace Kurs.Platform.Migrations
|
||||||
b.Property<int>("ParticipantCount")
|
b.Property<int>("ParticipantCount")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ScheduledEndTime")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
b.Property<DateTime>("ScheduledStartTime")
|
b.Property<DateTime>("ScheduledStartTime")
|
||||||
.HasColumnType("datetime2");
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
|
@ -1857,8 +1854,6 @@ namespace Kurs.Platform.Migrations
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("IsActive");
|
|
||||||
|
|
||||||
b.HasIndex("ScheduledStartTime");
|
b.HasIndex("ScheduledStartTime");
|
||||||
|
|
||||||
b.HasIndex("TeacherId");
|
b.HasIndex("TeacherId");
|
||||||
|
|
@ -797,12 +797,11 @@ namespace Kurs.Platform.Migrations
|
||||||
TeacherId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
TeacherId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
TeacherName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
TeacherName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
ScheduledStartTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
ScheduledStartTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
ActualStartTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
ScheduledEndTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
EndTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
|
||||||
Duration = table.Column<int>(type: "int", nullable: false),
|
Duration = table.Column<int>(type: "int", nullable: false),
|
||||||
|
ActualStartTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
ActualEndTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
MaxParticipants = table.Column<int>(type: "int", nullable: false),
|
MaxParticipants = table.Column<int>(type: "int", nullable: false),
|
||||||
IsActive = 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),
|
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),
|
||||||
|
|
@ -1958,7 +1957,7 @@ namespace Kurs.Platform.Migrations
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
SessionId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
SessionId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
SenderId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
SenderId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
SenderName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
SenderName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
Message = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false),
|
Message = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false),
|
||||||
Timestamp = table.Column<DateTime>(type: "datetime2", nullable: false),
|
Timestamp = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
|
@ -3039,11 +3038,6 @@ namespace Kurs.Platform.Migrations
|
||||||
table: "PClassParticipant",
|
table: "PClassParticipant",
|
||||||
column: "UserId");
|
column: "UserId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_PClassroom_IsActive",
|
|
||||||
table: "PClassroom",
|
|
||||||
column: "IsActive");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_PClassroom_ScheduledStartTime",
|
name: "IX_PClassroom_ScheduledStartTime",
|
||||||
table: "PClassroom",
|
table: "PClassroom",
|
||||||
|
|
@ -1665,7 +1665,7 @@ namespace Kurs.Platform.Migrations
|
||||||
.HasMaxLength(2000)
|
.HasMaxLength(2000)
|
||||||
.HasColumnType("nvarchar(2000)");
|
.HasColumnType("nvarchar(2000)");
|
||||||
|
|
||||||
b.Property<Guid>("SenderId")
|
b.Property<Guid?>("SenderId")
|
||||||
.HasColumnType("uniqueidentifier");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("SenderName")
|
b.Property<string>("SenderName")
|
||||||
|
|
@ -1774,6 +1774,9 @@ namespace Kurs.Platform.Migrations
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.HasColumnType("uniqueidentifier");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ActualEndTime")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
b.Property<DateTime?>("ActualStartTime")
|
b.Property<DateTime?>("ActualStartTime")
|
||||||
.HasColumnType("datetime2");
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
|
@ -1800,21 +1803,12 @@ namespace Kurs.Platform.Migrations
|
||||||
b.Property<int>("Duration")
|
b.Property<int>("Duration")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<DateTime?>("EndTime")
|
|
||||||
.HasColumnType("datetime2");
|
|
||||||
|
|
||||||
b.Property<bool>("IsActive")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<bool>("IsDeleted")
|
b.Property<bool>("IsDeleted")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("bit")
|
.HasColumnType("bit")
|
||||||
.HasDefaultValue(false)
|
.HasDefaultValue(false)
|
||||||
.HasColumnName("IsDeleted");
|
.HasColumnName("IsDeleted");
|
||||||
|
|
||||||
b.Property<bool>("IsScheduled")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("LastModificationTime")
|
b.Property<DateTime?>("LastModificationTime")
|
||||||
.HasColumnType("datetime2")
|
.HasColumnType("datetime2")
|
||||||
.HasColumnName("LastModificationTime");
|
.HasColumnName("LastModificationTime");
|
||||||
|
|
@ -1834,6 +1828,9 @@ namespace Kurs.Platform.Migrations
|
||||||
b.Property<int>("ParticipantCount")
|
b.Property<int>("ParticipantCount")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ScheduledEndTime")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
b.Property<DateTime>("ScheduledStartTime")
|
b.Property<DateTime>("ScheduledStartTime")
|
||||||
.HasColumnType("datetime2");
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
|
@ -1854,8 +1851,6 @@ namespace Kurs.Platform.Migrations
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("IsActive");
|
|
||||||
|
|
||||||
b.HasIndex("ScheduledStartTime");
|
b.HasIndex("ScheduledStartTime");
|
||||||
|
|
||||||
b.HasIndex("TeacherId");
|
b.HasIndex("TeacherId");
|
||||||
|
|
|
||||||
|
|
@ -41,12 +41,6 @@ public class ClassroomHub : Hub
|
||||||
{
|
{
|
||||||
var classSession = await _classSessionRepository.GetAsync(sessionId);
|
var classSession = await _classSessionRepository.GetAsync(sessionId);
|
||||||
|
|
||||||
if (!classSession.CanJoin())
|
|
||||||
{
|
|
||||||
await Clients.Caller.SendAsync("Error", "Cannot join this class at this time");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to SignalR group
|
// Add to SignalR group
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, sessionId.ToString());
|
await Groups.AddToGroupAsync(Context.ConnectionId, sessionId.ToString());
|
||||||
|
|
||||||
|
|
@ -154,21 +148,39 @@ public class ClassroomHub : Hub
|
||||||
|
|
||||||
public override async Task OnDisconnectedAsync(Exception exception)
|
public override async Task OnDisconnectedAsync(Exception exception)
|
||||||
{
|
{
|
||||||
// Handle cleanup when user disconnects
|
try
|
||||||
var userId = _currentUser.Id;
|
|
||||||
if (userId.HasValue)
|
|
||||||
{
|
{
|
||||||
var participants = await _participantRepository.GetListAsync(
|
// bağlantı gerçekten iptal edilmişse DB sorgusu çalıştırma
|
||||||
x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId
|
if (Context.ConnectionAborted.IsCancellationRequested)
|
||||||
);
|
return;
|
||||||
foreach (var participant in participants)
|
|
||||||
|
var userId = _currentUser.Id;
|
||||||
|
if (userId.HasValue)
|
||||||
{
|
{
|
||||||
await Clients.Group(participant.SessionId.ToString())
|
var participants = await _participantRepository
|
||||||
.SendAsync("ParticipantLeft", userId.Value);
|
.GetListAsync(x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var participant in participants)
|
||||||
|
{
|
||||||
|
await Clients.Group(participant.SessionId.ToString())
|
||||||
|
.SendAsync("ParticipantLeft", userId.Value)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// bağlantı kapandığında doğal, error yerine debug seviyesinde loglayın
|
||||||
|
_logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted).");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// beklenmeyen hataları error olarak loglayın
|
||||||
|
_logger.LogError(ex, "OnDisconnectedAsync hata");
|
||||||
|
}
|
||||||
|
|
||||||
await base.OnDisconnectedAsync(exception);
|
await base.OnDisconnectedAsync(exception).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
||||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||||
}, {
|
}, {
|
||||||
"url": "index.html",
|
"url": "index.html",
|
||||||
"revision": "0.cb06g8q0ck8"
|
"revision": "0.b9bfk61okp"
|
||||||
}], {});
|
}], {});
|
||||||
workbox.cleanupOutdatedCaches();
|
workbox.cleanupOutdatedCaches();
|
||||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,12 @@ export interface ClassroomDto {
|
||||||
teacherId: string
|
teacherId: string
|
||||||
teacherName: string
|
teacherName: string
|
||||||
scheduledStartTime: string
|
scheduledStartTime: string
|
||||||
actualStartTime?: string
|
scheduledEndTime: string
|
||||||
endTime?: string
|
|
||||||
duration?: number
|
duration?: number
|
||||||
|
actualStartTime?: string
|
||||||
|
actualEndTime?: string
|
||||||
maxParticipants?: number
|
maxParticipants?: number
|
||||||
isActive: boolean
|
|
||||||
isScheduled: boolean
|
|
||||||
participantCount: number
|
participantCount: number
|
||||||
canJoin: boolean
|
|
||||||
settingsDto?: ClassroomSettingsDto
|
settingsDto?: ClassroomSettingsDto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +104,6 @@ export interface ScheduledClassDto {
|
||||||
name: string
|
name: string
|
||||||
scheduledTime: string
|
scheduledTime: string
|
||||||
duration: number
|
duration: number
|
||||||
canJoin: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HandRaiseDto {
|
export interface HandRaiseDto {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ 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 = 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
|
||||||
|
|
@ -24,22 +23,20 @@ export class SignalRService {
|
||||||
const { auth } = store.getState()
|
const { auth } = store.getState()
|
||||||
|
|
||||||
// Only initialize connection if not in demo mode
|
// Only initialize connection if not in demo mode
|
||||||
if (!this.demoMode) {
|
// In production, replace with your actual SignalR hub URL
|
||||||
// In production, replace with your actual SignalR hub URL
|
this.connection = new signalR.HubConnectionBuilder()
|
||||||
this.connection = new signalR.HubConnectionBuilder()
|
.withUrl(`${import.meta.env.VITE_API_URL}/classroomhub`, {
|
||||||
.withUrl('https://localhost:44344/classroomhub', {
|
accessTokenFactory: () => auth.session.token || '',
|
||||||
accessTokenFactory: () => auth.session.token || '',
|
})
|
||||||
})
|
.withAutomaticReconnect()
|
||||||
.withAutomaticReconnect()
|
.configureLogging(signalR.LogLevel.Information)
|
||||||
.configureLogging(signalR.LogLevel.Information)
|
.build()
|
||||||
.build()
|
|
||||||
|
|
||||||
this.setupEventHandlers()
|
this.setupEventHandlers()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupEventHandlers() {
|
private setupEventHandlers() {
|
||||||
if (this.demoMode || !this.connection) return
|
if (!this.connection) return
|
||||||
|
|
||||||
this.connection.on('ReceiveSignalingMessage', (message: SignalingMessageDto) => {
|
this.connection.on('ReceiveSignalingMessage', (message: SignalingMessageDto) => {
|
||||||
this.onSignalingMessage?.(message)
|
this.onSignalingMessage?.(message)
|
||||||
|
|
@ -87,11 +84,6 @@ export class SignalRService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
if (this.demoMode) {
|
|
||||||
console.log('SignalR running in demo mode - no backend connection required')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.connection.start()
|
await this.connection.start()
|
||||||
this.isConnected = true
|
this.isConnected = true
|
||||||
|
|
@ -99,15 +91,13 @@ export class SignalRService {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error starting SignalR connection:', error)
|
console.error('Error starting SignalR connection:', error)
|
||||||
// Switch to demo mode if connection fails
|
// Switch to demo mode if connection fails
|
||||||
this.demoMode = true
|
|
||||||
this.isConnected = false
|
this.isConnected = false
|
||||||
console.log('Switched to demo mode - SignalR simulation active')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async joinClass(sessionId: string, userName: string): Promise<void> {
|
async joinClass(sessionId: string, userName: string): Promise<void> {
|
||||||
if (this.demoMode || !this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Demo mode: Simulating join class for', userName)
|
console.log('Error starting SignalR connection join class for', userName)
|
||||||
// Simulate successful join in demo mode
|
// Simulate successful join in demo mode
|
||||||
// Don't auto-add participants in demo mode - let manual simulation handle this
|
// Don't auto-add participants in demo mode - let manual simulation handle this
|
||||||
return
|
return
|
||||||
|
|
@ -123,8 +113,8 @@ export class SignalRService {
|
||||||
async leaveClass(sessionId: string): Promise<void> {
|
async leaveClass(sessionId: string): Promise<void> {
|
||||||
const { auth } = store.getState()
|
const { auth } = store.getState()
|
||||||
|
|
||||||
if (this.demoMode || !this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Demo mode: Simulating leave class for user', auth.user.id)
|
console.log('Error starting SignalR connection simulating leave class for user', auth.user.id)
|
||||||
// Simulate successful leave in demo mode
|
// Simulate successful leave in demo mode
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.onParticipantLeft?.(auth.user.id)
|
this.onParticipantLeft?.(auth.user.id)
|
||||||
|
|
@ -140,8 +130,8 @@ export class SignalRService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendSignalingMessage(message: SignalingMessageDto): Promise<void> {
|
async sendSignalingMessage(message: SignalingMessageDto): Promise<void> {
|
||||||
if (this.demoMode || !this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Demo mode: Simulating signaling message', message.type)
|
console.log('Error starting SignalR connection signaling message', message.type)
|
||||||
// In demo mode, we can't send real signaling messages
|
// In demo mode, we can't send real signaling messages
|
||||||
// WebRTC will need to work in local-only mode
|
// WebRTC will need to work in local-only mode
|
||||||
return
|
return
|
||||||
|
|
@ -161,8 +151,8 @@ export class SignalRService {
|
||||||
message: string,
|
message: string,
|
||||||
isTeacher: boolean,
|
isTeacher: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (this.demoMode || !this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Demo mode: Simulating chat message from', senderName)
|
console.log('Error starting SignalR connection simulating chat message from', senderName)
|
||||||
const chatMessage: ClassChatDto = {
|
const chatMessage: ClassChatDto = {
|
||||||
id: `msg-${Date.now()}`,
|
id: `msg-${Date.now()}`,
|
||||||
senderId,
|
senderId,
|
||||||
|
|
@ -202,8 +192,13 @@ export class SignalRService {
|
||||||
recipientName: string,
|
recipientName: string,
|
||||||
isTeacher: boolean,
|
isTeacher: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (this.demoMode || !this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Demo mode: Simulating private message from', senderName, 'to', recipientName)
|
console.log(
|
||||||
|
'Error starting SignalR connection simulating private message from',
|
||||||
|
senderName,
|
||||||
|
'to',
|
||||||
|
recipientName,
|
||||||
|
)
|
||||||
const chatMessage: ClassChatDto = {
|
const chatMessage: ClassChatDto = {
|
||||||
id: `msg-${Date.now()}`,
|
id: `msg-${Date.now()}`,
|
||||||
senderId,
|
senderId,
|
||||||
|
|
@ -243,8 +238,8 @@ export class SignalRService {
|
||||||
senderName: string,
|
senderName: string,
|
||||||
message: string,
|
message: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (this.demoMode || !this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Demo mode: Simulating announcement from', senderName)
|
console.log('Error starting SignalR connection simulating announcement from', senderName)
|
||||||
const chatMessage: ClassChatDto = {
|
const chatMessage: ClassChatDto = {
|
||||||
id: `msg-${Date.now()}`,
|
id: `msg-${Date.now()}`,
|
||||||
senderId,
|
senderId,
|
||||||
|
|
@ -268,8 +263,8 @@ export class SignalRService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async muteParticipant(sessionId: string, userId: string, isMuted: boolean): Promise<void> {
|
async muteParticipant(sessionId: string, userId: string, isMuted: boolean): Promise<void> {
|
||||||
if (this.demoMode || !this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Demo mode: Simulating mute participant', userId, isMuted)
|
console.log('Error starting SignalR connection simulating mute participant', userId, isMuted)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.onParticipantMuted?.(userId, isMuted)
|
this.onParticipantMuted?.(userId, isMuted)
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
@ -284,8 +279,8 @@ export class SignalRService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async raiseHand(sessionId: string, studentId: string, studentName: string): Promise<void> {
|
async raiseHand(sessionId: string, studentId: string, studentName: string): Promise<void> {
|
||||||
if (this.demoMode || !this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Demo mode: Simulating hand raise from', studentName)
|
console.log('Error starting SignalR connection simulating hand raise from', studentName)
|
||||||
const handRaise: HandRaiseDto = {
|
const handRaise: HandRaiseDto = {
|
||||||
id: `hand-${Date.now()}`,
|
id: `hand-${Date.now()}`,
|
||||||
studentId,
|
studentId,
|
||||||
|
|
@ -307,8 +302,8 @@ export class SignalRService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async kickParticipant(sessionId: string, participantId: string): Promise<void> {
|
async kickParticipant(sessionId: string, participantId: string): Promise<void> {
|
||||||
if (this.demoMode || !this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Demo mode: Simulating kick participant', participantId)
|
console.log('Error starting SignalR connection simulating kick participant', participantId)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.onParticipantLeft?.(participantId)
|
this.onParticipantLeft?.(participantId)
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
@ -323,8 +318,8 @@ export class SignalRService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async approveHandRaise(sessionId: string, handRaiseId: string): Promise<void> {
|
async approveHandRaise(sessionId: string, handRaiseId: string): Promise<void> {
|
||||||
if (this.demoMode || !this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Demo mode: Simulating hand raise approval')
|
console.log('Error starting SignalR connection simulating hand raise approval')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.onHandRaiseDismissed?.(handRaiseId)
|
this.onHandRaiseDismissed?.(handRaiseId)
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
@ -339,8 +334,8 @@ export class SignalRService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async dismissHandRaise(sessionId: string, handRaiseId: string): Promise<void> {
|
async dismissHandRaise(sessionId: string, handRaiseId: string): Promise<void> {
|
||||||
if (this.demoMode || !this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Demo mode: Simulating hand raise dismissal')
|
console.log('Error starting SignalR connection simulating hand raise dismissal')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.onHandRaiseDismissed?.(handRaiseId)
|
this.onHandRaiseDismissed?.(handRaiseId)
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
@ -393,10 +388,6 @@ export class SignalRService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isInDemoMode(): boolean {
|
|
||||||
return this.demoMode
|
|
||||||
}
|
|
||||||
|
|
||||||
getConnectionState(): boolean {
|
getConnectionState(): boolean {
|
||||||
return this.isConnected
|
return this.isConnected
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ import {
|
||||||
FaEdit,
|
FaEdit,
|
||||||
FaTrash,
|
FaTrash,
|
||||||
FaEye,
|
FaEye,
|
||||||
|
FaHourglassEnd,
|
||||||
|
FaDoorOpen,
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
|
|
||||||
import { ClassroomDto } from '@/proxy/classroom/models'
|
import { ClassroomDto } from '@/proxy/classroom/models'
|
||||||
|
|
@ -25,6 +27,15 @@ import {
|
||||||
updateClassroom,
|
updateClassroom,
|
||||||
} from '@/services/classroom.service'
|
} from '@/services/classroom.service'
|
||||||
|
|
||||||
|
export interface ClassProps {
|
||||||
|
status: string
|
||||||
|
className: string
|
||||||
|
showButtons: boolean
|
||||||
|
title: string
|
||||||
|
classes: string
|
||||||
|
event?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -37,12 +48,10 @@ const ClassList: React.FC = () => {
|
||||||
teacherId: user.id,
|
teacherId: user.id,
|
||||||
teacherName: user.name,
|
teacherName: user.name,
|
||||||
scheduledStartTime: '',
|
scheduledStartTime: '',
|
||||||
|
scheduledEndTime: '',
|
||||||
duration: 60,
|
duration: 60,
|
||||||
maxParticipants: 30,
|
maxParticipants: 30,
|
||||||
isActive: false,
|
|
||||||
isScheduled: true,
|
|
||||||
participantCount: 0,
|
participantCount: 0,
|
||||||
canJoin: false,
|
|
||||||
settingsDto: {
|
settingsDto: {
|
||||||
allowHandRaise: true,
|
allowHandRaise: true,
|
||||||
allowStudentChat: true,
|
allowStudentChat: true,
|
||||||
|
|
@ -91,15 +100,11 @@ const ClassList: React.FC = () => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await createClassroom(newClassEntity)
|
await createClassroom(classroom)
|
||||||
|
|
||||||
getClassroomList()
|
getClassroomList()
|
||||||
setShowCreateModal(false)
|
setShowCreateModal(false)
|
||||||
setClassroom(newClassEntity)
|
setClassroom(newClassEntity)
|
||||||
|
|
||||||
if (classroom.id) {
|
|
||||||
handleJoinClass(classroom)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Sınıf oluştururken hata oluştu:', error)
|
console.error('Sınıf oluştururken hata oluştu:', error)
|
||||||
}
|
}
|
||||||
|
|
@ -161,38 +166,77 @@ const ClassList: React.FC = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const canJoinClass = (scheduledTime: string) => {
|
const canJoinClass = (actualStartTime: string) => {
|
||||||
const scheduled = new Date(scheduledTime)
|
const actualed = new Date(actualStartTime)
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const tenMinutesBefore = new Date(scheduled.getTime() - 10 * 60 * 1000)
|
const tenMinutesBefore = new Date(actualed.getTime() - 10 * 60 * 1000) //10 dakika öncesine kadar
|
||||||
const twoHoursAfter = new Date(scheduled.getTime() + 2 * 60 * 60 * 1000) // 2 saat sonrasına kadar
|
const twoHoursAfter = new Date(actualed.getTime() + 2 * 60 * 60 * 1000) // 2 saat sonrasına kadar
|
||||||
return now >= tenMinutesBefore && now <= twoHoursAfter
|
return now >= tenMinutesBefore && now <= twoHoursAfter
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTimeUntilClass = (scheduledTime: string) => {
|
const widgets = () => {
|
||||||
const scheduled = new Date(scheduledTime)
|
return {
|
||||||
const now = new Date()
|
totalCount: classList.length,
|
||||||
const diff = scheduled.getTime() - now.getTime()
|
activeCount: classList.filter((c) => !c.actualStartTime && !c.actualEndTime).length,
|
||||||
|
openCount: classList.filter(
|
||||||
|
(c) => c.actualStartTime && !c.actualEndTime, // && canJoinClass(c.actualStartTime),
|
||||||
|
).length,
|
||||||
|
passiveCount: classList.filter((c) => c.actualStartTime && c.actualEndTime).length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (diff <= 0) {
|
const getClassProps = (classSession: ClassroomDto): ClassProps => {
|
||||||
// Sınıf başladıysa, ne kadar süredir devam ettiğini göster
|
//Aktif -> boş boş
|
||||||
const elapsed = Math.abs(diff)
|
if (!classSession.actualStartTime && !classSession.actualEndTime) {
|
||||||
const elapsedMinutes = Math.floor(elapsed / (1000 * 60))
|
return {
|
||||||
if (elapsedMinutes < 60) {
|
status: 'Aktif',
|
||||||
return `${elapsedMinutes} dakikadır devam ediyor`
|
className: 'bg-blue-100 text-blue-800',
|
||||||
|
showButtons: true,
|
||||||
|
title:
|
||||||
|
user.role === 'teacher' && classSession.teacherId === user.id ? 'Dersi Başlat' : 'Katıl',
|
||||||
|
classes:
|
||||||
|
user.role === 'teacher' && classSession.teacherId === user.id
|
||||||
|
? 'bg-green-600 text-white hover:bg-green-700'
|
||||||
|
: 'bg-blue-600 text-white hover:bg-blue-700',
|
||||||
|
event: () => {
|
||||||
|
user.role === 'teacher' && classSession.teacherId === user.id
|
||||||
|
? handleStartClass(classSession)
|
||||||
|
: handleJoinClass(classSession)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
const elapsedHours = Math.floor(elapsedMinutes / 60)
|
|
||||||
const remainingMinutes = elapsedMinutes % 60
|
|
||||||
return `${elapsedHours}s ${remainingMinutes}d devam ediyor`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const hours = Math.floor(diff / (1000 * 60 * 60))
|
//Katılıma Açık -> dolu boş
|
||||||
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
|
if (
|
||||||
|
classSession.actualStartTime &&
|
||||||
if (hours > 0) {
|
!classSession.actualEndTime
|
||||||
return `${hours}s ${minutes}d kaldı`
|
//&& canJoinClass(classSession.actualStartTime)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
status: 'Katılım Açık',
|
||||||
|
className: 'bg-yellow-100 text-yellow-800',
|
||||||
|
showButtons: true,
|
||||||
|
title:
|
||||||
|
user.role === 'teacher' && classSession.teacherId === user.id ? 'Sınıfa Git' : 'Katıl',
|
||||||
|
classes:
|
||||||
|
user.role === 'teacher' && classSession.teacherId === user.id
|
||||||
|
? 'bg-green-600 text-white hover:bg-green-700'
|
||||||
|
: 'bg-blue-600 text-white hover:bg-blue-700',
|
||||||
|
event: () => {
|
||||||
|
handleJoinClass(classSession)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Pasif
|
||||||
|
return {
|
||||||
|
status: 'Pasif',
|
||||||
|
className: 'bg-gray-100 text-gray-800',
|
||||||
|
showButtons: false,
|
||||||
|
title: '',
|
||||||
|
classes: '',
|
||||||
|
event: () => {},
|
||||||
}
|
}
|
||||||
return `${minutes}d kaldı`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -200,7 +244,7 @@ const ClassList: React.FC = () => {
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 mb-6 sm:mb-8">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4 sm:gap-6 mb-6 sm:mb-8">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
|
@ -212,11 +256,14 @@ 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">{classList.length}</p>
|
<p className="text-xl sm:text-2xl font-bold text-gray-900">
|
||||||
|
{widgets().totalCount}{' '}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Aktif Sınıf */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
|
@ -230,12 +277,51 @@ 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">
|
||||||
{classList.filter((c) => c.isActive).length}
|
{widgets().activeCount}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Katılıma Açık */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.15 }}
|
||||||
|
className="bg-white rounded-lg shadow-md p-4 sm:p-6"
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="p-2 sm:p-3 bg-blue-100 rounded-full">
|
||||||
|
<FaDoorOpen className="text-blue-600" size={20} />
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 sm:ml-4">
|
||||||
|
<p className="text-xs sm:text-sm font-medium text-gray-600">Katılıma Açık</p>
|
||||||
|
<p className="text-xl sm:text-2xl font-bold text-gray-900">{widgets().openCount}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Pasif Sınıf */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.2 }}
|
||||||
|
className="bg-white rounded-lg shadow-md p-4 sm:p-6"
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="p-2 sm:p-3 bg-gray-100 rounded-full">
|
||||||
|
<FaHourglassEnd className="text-gray-600" size={20} />
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 sm:ml-4">
|
||||||
|
<p className="text-xs sm:text-sm font-medium text-gray-600">Pasif Sınıf</p>
|
||||||
|
<p className="text-xl sm:text-2xl font-bold text-gray-900">
|
||||||
|
{widgets().passiveCount}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Toplam Katılımcı */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
|
@ -279,128 +365,118 @@ const ClassList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-4 sm:gap-6">
|
<div className="grid gap-4 sm:gap-6">
|
||||||
{classList.map((classSession, index) => (
|
{classList.map((classSession, index) => {
|
||||||
<motion.div
|
const { status, className, showButtons, title, classes, event } =
|
||||||
key={classSession.id}
|
getClassProps(classSession)
|
||||||
initial={{ opacity: 0, x: -20 }}
|
return (
|
||||||
animate={{ opacity: 1, x: 0 }}
|
<motion.div
|
||||||
transition={{ delay: index * 0.1 }}
|
key={classSession.id}
|
||||||
className="border border-gray-200 rounded-lg p-4 sm:p-6 hover:shadow-md transition-shadow"
|
initial={{ opacity: 0, x: -20 }}
|
||||||
>
|
animate={{ opacity: 1, x: 0 }}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-2">
|
transition={{ delay: index * 0.1 }}
|
||||||
<div className="flex items-center space-x-3">
|
className="border border-gray-200 rounded-lg p-4 sm:p-6 hover:shadow-md transition-shadow"
|
||||||
<h3 className="text-base sm:text-lg font-semibold text-gray-900 break-words">
|
>
|
||||||
{classSession.name}
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
||||||
</h3>
|
<div className="flex items-center space-x-3">
|
||||||
<span
|
<h3 className="text-base sm:text-lg font-semibold text-gray-900 break-words">
|
||||||
className={`px-2 py-1 rounded-full text-xs font-medium ${
|
{classSession.name}
|
||||||
classSession.isActive
|
</h3>
|
||||||
? 'bg-green-100 text-green-800'
|
<span
|
||||||
: canJoinClass(classSession.scheduledStartTime)
|
className={`px-2 py-1 rounded-full text-xs font-medium ${className}`}
|
||||||
? 'bg-yellow-100 text-yellow-800'
|
>
|
||||||
: 'bg-gray-100 text-gray-800'
|
{status}
|
||||||
}`}
|
</span>
|
||||||
>
|
</div>
|
||||||
{classSession.isActive
|
|
||||||
? 'Aktif'
|
|
||||||
: canJoinClass(classSession.scheduledStartTime)
|
|
||||||
? 'Katılım Açık'
|
|
||||||
: 'Beklemede'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sağ kısım: buton */}
|
{/* Sağ kısım: buton */}
|
||||||
{canJoinClass(classSession.scheduledStartTime) && (
|
{showButtons && (
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
{user.role === 'teacher' && classSession.teacherId === user.id && (
|
{user.role === 'teacher' && classSession.teacherId === user.id && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => openEditModal(classSession)}
|
onClick={() => openEditModal(classSession)}
|
||||||
disabled={classSession.isActive}
|
disabled={classSession.actualStartTime ? true : false}
|
||||||
className="flex px-3 sm:px-4 py-2 rounded-lg bg-blue-600 text-white
|
className="flex px-3 sm:px-4 py-2 rounded-lg bg-blue-600 text-white
|
||||||
hover:bg-blue-700
|
hover:bg-blue-700
|
||||||
disabled:bg-gray-400 disabled:cursor-not-allowed disabled:hover:bg-gray-400"
|
disabled:bg-gray-400 disabled:cursor-not-allowed disabled:hover:bg-gray-400"
|
||||||
title="Sınıfı Düzenle"
|
title="Sınıfı Düzenle"
|
||||||
>
|
>
|
||||||
<FaEdit size={14} />
|
<FaEdit size={14} />
|
||||||
Düzenle
|
Düzenle
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => openDeleteModal(classSession)}
|
onClick={() => openDeleteModal(classSession)}
|
||||||
disabled={classSession.isActive}
|
disabled={classSession.actualStartTime ? true : false}
|
||||||
className="flex px-3 sm:px-4 py-2 rounded-lg bg-red-600 text-white
|
className="flex px-3 sm:px-4 py-2 rounded-lg bg-red-600 text-white
|
||||||
hover:bg-red-700
|
hover:bg-red-700
|
||||||
disabled:bg-gray-400 disabled:cursor-not-allowed disabled:hover:bg-gray-400"
|
disabled:bg-gray-400 disabled:cursor-not-allowed disabled:hover:bg-gray-400"
|
||||||
title="Sınıfı Sil"
|
title="Sınıfı Sil"
|
||||||
>
|
>
|
||||||
<FaTrash size={14} />
|
<FaTrash size={14} />
|
||||||
Sil
|
Sil
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={event}
|
||||||
user.role === 'teacher' && classSession.teacherId === user.id
|
className={`px-3 sm:px-4 py-2 rounded-lg transition-colors ${
|
||||||
? classSession.isActive
|
classes
|
||||||
? handleJoinClass(classSession)
|
}`}
|
||||||
: handleStartClass(classSession)
|
>
|
||||||
: handleJoinClass(classSession)
|
{title}
|
||||||
}
|
</button>
|
||||||
className={`px-3 sm:px-4 py-2 rounded-lg transition-colors ${
|
</div>
|
||||||
user.role === 'teacher' && classSession.teacherId === user.id
|
)}
|
||||||
? 'bg-green-600 text-white hover:bg-green-700'
|
</div>
|
||||||
: 'bg-blue-600 text-white hover:bg-blue-700'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{user.role === 'teacher' && classSession.teacherId === user.id
|
|
||||||
? classSession.isActive
|
|
||||||
? 'Sınıfa Git'
|
|
||||||
: 'Dersi Başlat'
|
|
||||||
: 'Katıl'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
||||||
<p className="text-gray-600 mb-3 text-sm sm:text-base">
|
<p className="text-gray-600 text-sm sm:text-base">{classSession.subject}</p>
|
||||||
{classSession.description}
|
</div>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
||||||
<div className="grid grid-cols-4 gap-3 w-full text-xs sm:text-sm text-gray-600">
|
<sub className="text-gray-500 mb-3 text-xs sm:text-sm">
|
||||||
<div className="col-span-1 flex items-center gap-2 px-3 py-2 rounded-lg">
|
{classSession.description}
|
||||||
<FaCalendarAlt size={14} className="text-gray-500" />
|
</sub>
|
||||||
<span className="truncate">
|
</div>
|
||||||
{showDbDateAsIs(classSession.scheduledStartTime)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-span-1 flex items-center gap-2 px-3 py-2 rounded-lg">
|
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
||||||
<FaClock size={14} className="text-gray-500" />
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 md:gap-3 w-full text-xs sm:text-sm text-gray-600">
|
||||||
<span>{classSession.duration} dakika</span>
|
<div className="col-span-1 flex items-center gap-2 p-1 rounded-lg">
|
||||||
</div>
|
<FaCalendarAlt size={14} className="text-gray-500" />
|
||||||
|
<span className="truncate">
|
||||||
|
{showDbDateAsIs(classSession.scheduledStartTime)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="col-span-1 flex items-center gap-2 px-3 py-2 rounded-lg">
|
<div className="col-span-1 flex items-center gap-2 p-1 rounded-lg">
|
||||||
<FaUsers size={14} className="text-gray-500" />
|
<FaClock size={14} className="text-gray-500" />
|
||||||
<span>
|
<span>{classSession.duration} dakika</span>
|
||||||
{classSession.participantCount}/{classSession.maxParticipants}
|
</div>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-span-1 flex items-center gap-2 px-3 py-2 rounded-lg">
|
<div className="col-span-1 flex items-center gap-2 p-1 rounded-lg">
|
||||||
<FaEye size={14} className="text-gray-500" />
|
{classSession.scheduledEndTime && (
|
||||||
<span className="truncate">
|
<>
|
||||||
{getTimeUntilClass(classSession.scheduledStartTime)}
|
<FaEye size={14} className="text-gray-500" />
|
||||||
</span>
|
<span className="truncate">
|
||||||
|
{showDbDateAsIs(classSession.scheduledEndTime!)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-span-1 flex items-center gap-2 p-1 rounded-lg">
|
||||||
|
<FaUsers size={14} className="text-gray-500" />
|
||||||
|
<span>
|
||||||
|
{classSession.participantCount}/{classSession.maxParticipants}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
</motion.div>
|
)
|
||||||
))}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,9 @@ import { KickParticipantModal } from '@/components/classroom/KickParticipantModa
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { getClassroomById } from '@/services/classroom.service'
|
import { getClassroomById } from '@/services/classroom.service'
|
||||||
import { showDbDateAsIs } from '@/utils/dateUtils'
|
import { showDbDateAsIs } from '@/utils/dateUtils'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { endClassroom } from '@/services/classroom.service'
|
||||||
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
|
|
||||||
type SidePanelType =
|
type SidePanelType =
|
||||||
| 'chat'
|
| 'chat'
|
||||||
|
|
@ -71,17 +74,16 @@ const newClassSession: ClassroomDto = {
|
||||||
teacherId: '',
|
teacherId: '',
|
||||||
teacherName: '',
|
teacherName: '',
|
||||||
scheduledStartTime: '',
|
scheduledStartTime: '',
|
||||||
|
scheduledEndTime: '',
|
||||||
actualStartTime: '',
|
actualStartTime: '',
|
||||||
endTime: '',
|
actualEndTime: '',
|
||||||
isActive: false,
|
|
||||||
isScheduled: false,
|
|
||||||
participantCount: 0,
|
participantCount: 0,
|
||||||
settingsDto: undefined,
|
settingsDto: undefined,
|
||||||
canJoin: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomDetail: React.FC = () => {
|
const RoomDetail: React.FC = () => {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
const navigate = useNavigate()
|
||||||
const { user } = useStoreState((state) => state.auth)
|
const { user } = useStoreState((state) => state.auth)
|
||||||
|
|
||||||
const [classSession, setClassSession] = useState<ClassroomDto>(newClassSession)
|
const [classSession, setClassSession] = useState<ClassroomDto>(newClassSession)
|
||||||
|
|
@ -223,6 +225,9 @@ const RoomDetail: React.FC = () => {
|
||||||
signalRServiceRef.current.setParticipantJoinHandler((userId, name) => {
|
signalRServiceRef.current.setParticipantJoinHandler((userId, name) => {
|
||||||
console.log(`Participant joined: ${name}`)
|
console.log(`Participant joined: ${name}`)
|
||||||
|
|
||||||
|
// Eğer kendimsem, ekleme
|
||||||
|
if (userId === user.id) return
|
||||||
|
|
||||||
// Create WebRTC connection for new participant
|
// Create WebRTC connection for new participant
|
||||||
if (webRTCServiceRef.current) {
|
if (webRTCServiceRef.current) {
|
||||||
webRTCServiceRef.current.createPeerConnection(userId)
|
webRTCServiceRef.current.createPeerConnection(userId)
|
||||||
|
|
@ -308,7 +313,21 @@ const RoomDetail: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLeaveCall = async () => {
|
const handleLeaveCall = async () => {
|
||||||
await cleanup()
|
try {
|
||||||
|
// Eğer teacher ise sınıfı kapat
|
||||||
|
if (user.role === 'teacher') {
|
||||||
|
await endClassroom(classSession.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bağlantıları kapat
|
||||||
|
await cleanup()
|
||||||
|
|
||||||
|
// Başka sayfaya yönlendir
|
||||||
|
navigate(ROUTES_ENUM.protected.admin.classroom.classes)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Leave işlemi sırasında hata:', err)
|
||||||
|
navigate(ROUTES_ENUM.protected.admin.classroom.classes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSendMessage = async (e: React.FormEvent) => {
|
const handleSendMessage = async (e: React.FormEvent) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue