From 5364536463495fdbaf70be93da043f6c30ed7fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Kr=C3=A1l?= <90798024+Jitralar@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:37:15 +0100 Subject: [PATCH 1/2] Switch to integer keys and rebuild schedule schema --- .gitignore | 5 + BlazorApp1/BlazorApp1.csproj | 5 + BlazorApp1/Components/Layout/NavMenu.razor | 11 +- .../Components/Layout/NavMenu.razor.css | 8 + BlazorApp1/Components/Pages/Home.razor | 15 +- BlazorApp1/Components/Pages/Management.razor | 435 ++++++++++++++++++ BlazorApp1/Components/Pages/Schedule.razor | 73 +++ .../Components/Pages/SubjectDetails.razor | 101 ++++ BlazorApp1/Components/_Imports.razor | 2 + BlazorApp1/Data/ScheduleDbContext.cs | 65 +++ BlazorApp1/Models/Classroom.cs | 20 + BlazorApp1/Models/Department.cs | 15 + BlazorApp1/Models/Lesson.cs | 31 ++ BlazorApp1/Models/LessonStudent.cs | 8 + BlazorApp1/Models/LessonType.cs | 11 + BlazorApp1/Models/Person.cs | 25 + BlazorApp1/Models/Role.cs | 11 + BlazorApp1/Models/Subject.cs | 21 + BlazorApp1/Program.cs | 16 + BlazorApp1/Services/ScheduleService.cs | 347 ++++++++++++++ BlazorApp1/appsettings.json | 3 + 21 files changed, 1219 insertions(+), 9 deletions(-) create mode 100644 .gitignore create mode 100644 BlazorApp1/Components/Pages/Management.razor create mode 100644 BlazorApp1/Components/Pages/Schedule.razor create mode 100644 BlazorApp1/Components/Pages/SubjectDetails.razor create mode 100644 BlazorApp1/Data/ScheduleDbContext.cs create mode 100644 BlazorApp1/Models/Classroom.cs create mode 100644 BlazorApp1/Models/Department.cs create mode 100644 BlazorApp1/Models/Lesson.cs create mode 100644 BlazorApp1/Models/LessonStudent.cs create mode 100644 BlazorApp1/Models/LessonType.cs create mode 100644 BlazorApp1/Models/Person.cs create mode 100644 BlazorApp1/Models/Role.cs create mode 100644 BlazorApp1/Models/Subject.cs create mode 100644 BlazorApp1/Services/ScheduleService.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb061c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +.vs/ +data/ +*.db diff --git a/BlazorApp1/BlazorApp1.csproj b/BlazorApp1/BlazorApp1.csproj index 1b28a01..c087473 100644 --- a/BlazorApp1/BlazorApp1.csproj +++ b/BlazorApp1/BlazorApp1.csproj @@ -6,4 +6,9 @@ enable + + + + + diff --git a/BlazorApp1/Components/Layout/NavMenu.razor b/BlazorApp1/Components/Layout/NavMenu.razor index ffda8b2..d1b23c8 100644 --- a/BlazorApp1/Components/Layout/NavMenu.razor +++ b/BlazorApp1/Components/Layout/NavMenu.razor @@ -1,6 +1,6 @@  @@ -15,14 +15,13 @@ - diff --git a/BlazorApp1/Components/Layout/NavMenu.razor.css b/BlazorApp1/Components/Layout/NavMenu.razor.css index 4e15395..17bc108 100644 --- a/BlazorApp1/Components/Layout/NavMenu.razor.css +++ b/BlazorApp1/Components/Layout/NavMenu.razor.css @@ -46,6 +46,14 @@ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); } +.bi-calendar-event-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-calendar-event-fill' viewBox='0 0 16 16'%3E%3Cpath d='M4 .5a.5.5 0 0 1 .5-.5h.5a.5.5 0 0 1 .5.5V1h4V.5a.5.5 0 0 1 .5-.5h.5a.5.5 0 0 1 .5.5V1h.5A2.5 2.5 0 0 1 15 3.5V4H1v-.5A2.5 2.5 0 0 1 3.5 1H4z'/%3E%3Cpath d='M1 5h14v7.5A2.5 2.5 0 0 1 12.5 15h-9A2.5 2.5 0 0 1 1 12.5zM4 7.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 1 0v-1A.5.5 0 0 0 4 7.5z'/%3E%3C/svg%3E"); +} + +.bi-clipboard-plus-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-clipboard-plus' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M10 1.5A1.5 1.5 0 0 1 11.5 3v1H14a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h2.5V3A1.5 1.5 0 0 1 6 1.5h4zM8 3a1 1 0 0 0 1-1H7a1 1 0 0 0 1 1z'/%3E%3Cpath d='M8.5 6.5a.5.5 0 0 0-1 0V8H6a.5.5 0 0 0 0 1h1.5v1.5a.5.5 0 0 0 1 0V9H10a.5.5 0 0 0 0-1H8.5z'/%3E%3C/svg%3E"); +} + .nav-item { font-size: 0.9rem; padding-bottom: 0.5rem; diff --git a/BlazorApp1/Components/Pages/Home.razor b/BlazorApp1/Components/Pages/Home.razor index 9001e0b..1f801d3 100644 --- a/BlazorApp1/Components/Pages/Home.razor +++ b/BlazorApp1/Components/Pages/Home.razor @@ -1,7 +1,16 @@ @page "/" -Home +Rozvrh -

Hello, world!

+

Jednoduchý rezervační systém

-Welcome to your new app. +

+ Prohlížejte si rozvrh, otevírejte detail předmětu a přidávejte nové záznamy pro učebny, katedry, osoby + i lekce. Aplikace je připravena na další rozšiřování podle zadání semestrální práce. +

+ + diff --git a/BlazorApp1/Components/Pages/Management.razor b/BlazorApp1/Components/Pages/Management.razor new file mode 100644 index 0000000..68f2793 --- /dev/null +++ b/BlazorApp1/Components/Pages/Management.razor @@ -0,0 +1,435 @@ +@page "/sprava" +@inject ScheduleService ScheduleService + +

Správa evidence

+

Přidávejte nové entity pro rozvrh. Záznamy se ukládají do lokální databáze SQLite v adresáři data + a po restartu se z ní znovu načtou.

+ + + +@if (_activeTab == "main") +{ +
+
+

Učebna

+ + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+

Předmět

+ + + +
+ + +
+
+ + + @foreach (var dep in _departments) + { + + } + +
+
+ + +
+
+ + +
+ +
+
+
+ +
+ +
+
+

Role & Osoby

+ + + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + + + + +
+
+ + + + @foreach (var role in _roles) + { + + } + +
+ +
+
+
+

Lekce

+ + + +
+ + + @foreach (var subject in _subjects) + { + + } + +
+
+ + + @foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek))) + { + + } + +
+
+
+ + +
+
+ + +
+
+
+ + + @foreach (var room in _classrooms) + { + + } + +
+
+ + + @foreach (var lt in _lessonTypes) + { + + } + +
+
+ + + @foreach (var person in _people.Where(p => p.Affiliation == "Akademický pracovník")) + { + + } + +
+
+ + @foreach (var student in _people.Where(p => p.Affiliation == "Student")) + { +
+ + +
+ } +
+ +
+
+
+} +else +{ +
+
+

Katedra

+

Tabulka indexů pro vazbu předmětů na katedry.

+ + + +
+ + +
+
+ + +
+ +
+
+
+

Role

+

Index pro akademické pracovníky.

+ + + +
+ + + +
+
+
+
+

Druh lekce

+

Index pro typy lekcí (přednáška, cvičení...).

+ + + +
+ + +
+ +
+
+
+} + +@if (!string.IsNullOrWhiteSpace(_message)) +{ +
@_message
+} + +@code { + private Classroom _newClassroom = new(); + private Department _newDepartment = new(); + private Subject _newSubject = new(); + private Role _newRole = new(); + private Person _newPerson = new(); + private LessonType _newLessonType = new(); + private Lesson _newLesson = new() { Day = DayOfWeek.Monday }; + + private List _classrooms = new(); + private List _departments = new(); + private List _subjects = new(); + private List _roles = new(); + private List _people = new(); + private List _lessonTypes = new(); + + private string _start = "08:00"; + private string _end = "09:30"; + private string _message = string.Empty; + private string _activeTab = "main"; + + protected override async Task OnInitializedAsync() + { + await ScheduleService.InitializeAsync(); + await LoadDataAsync(); + SetDefaultSubjectDepartment(); + ResetPersonDefaults(); + ResetLessonDefaults(); + } + + private void SwitchTab(string tab) => _activeTab = tab; + + private async Task LoadDataAsync() + { + _classrooms = await ScheduleService.GetClassroomsAsync(); + _departments = await ScheduleService.GetDepartmentsAsync(); + _subjects = await ScheduleService.GetSubjectsAsync(); + _roles = await ScheduleService.GetRolesAsync(); + _people = await ScheduleService.GetPeopleAsync(); + _lessonTypes = await ScheduleService.GetLessonTypesAsync(); + } + + private async Task HandleAddClassroomAsync() + { + var addedCode = _newClassroom.Code; + await ScheduleService.AddClassroomAsync(_newClassroom); + await LoadDataAsync(); + _newLesson.ClassroomCode = addedCode; + _newClassroom = new Classroom(); + _message = "Učebna byla přidána."; + } + + private async Task HandleAddDepartmentAsync() + { + var addedCode = _newDepartment.Code; + await ScheduleService.AddDepartmentAsync(_newDepartment); + await LoadDataAsync(); + _newDepartment = new Department(); + _newSubject.DepartmentCode = addedCode; + _message = "Katedra byla přidána."; + } + + private async Task HandleAddSubjectAsync() + { + var addedCode = _newSubject.SubjectCode; + await ScheduleService.AddSubjectAsync(_newSubject); + await LoadDataAsync(); + _newSubject = new Subject(); + SetDefaultSubjectDepartment(); + _newLesson.SubjectCode = addedCode; + _message = "Předmět byl přidán."; + } + + private async Task HandleAddRoleAsync() + { + var addedRole = await ScheduleService.AddRoleAsync(_newRole); + await LoadDataAsync(); + _newRole = new Role(); + _newPerson.RoleId = addedRole.Id; + _message = "Role byla přidána."; + } + + private async Task HandleAddPersonAsync() + { + var createdPerson = await ScheduleService.AddPersonAsync(_newPerson); + await LoadDataAsync(); + if (_newPerson.Affiliation == "Akademický pracovník") + { + _newLesson.LecturerId = createdPerson.Id; + } + _newPerson = new Person(); + ResetPersonDefaults(); + _message = "Osoba byla přidána."; + } + + private async Task HandleAddLessonTypeAsync() + { + var addedLessonType = await ScheduleService.AddLessonTypeAsync(_newLessonType); + await LoadDataAsync(); + _newLessonType = new LessonType(); + _newLesson.LessonTypeId = addedLessonType.Id; + _message = "Druh lekce byl přidán."; + } + + private async Task HandleAddLessonAsync() + { + if (TimeSpan.TryParse(_start, out var start) && TimeSpan.TryParse(_end, out var end)) + { + _newLesson.StartTime = start; + _newLesson.EndTime = end; + await ScheduleService.AddLessonAsync(_newLesson); + await LoadDataAsync(); + _newLesson = new Lesson { Day = DayOfWeek.Monday }; + ResetLessonDefaults(); + _start = "08:00"; + _end = "09:30"; + _message = "Lekce byla přidána."; + } + else + { + _message = "Čas nebyl ve správném formátu (HH:mm)."; + } + } + + private void ToggleStudent(int studentId, object? value) + { + var isChecked = value switch + { + bool boolean => boolean, + string text when text.Equals("on", StringComparison.OrdinalIgnoreCase) => true, + string text when bool.TryParse(text, out var parsed) => parsed, + _ => false + }; + if (isChecked && !_newLesson.StudentIds.Contains(studentId)) + { + _newLesson.StudentIds.Add(studentId); + } + else if (!isChecked) + { + _newLesson.StudentIds.Remove(studentId); + } + } + + private void ResetPersonDefaults() + { + if (string.IsNullOrWhiteSpace(_newPerson.Affiliation)) + { + _newPerson.Affiliation = "Student"; + } + } + + private void SetDefaultSubjectDepartment() + { + if (string.IsNullOrWhiteSpace(_newSubject.DepartmentCode) && _departments.Any()) + { + _newSubject.DepartmentCode = _departments.First().Code; + } + } + + private void ResetLessonDefaults() + { + if (string.IsNullOrWhiteSpace(_newLesson.SubjectCode) && _subjects.Any()) + { + _newLesson.SubjectCode = _subjects.First().SubjectCode; + } + + if (string.IsNullOrWhiteSpace(_newLesson.ClassroomCode) && _classrooms.Any()) + { + _newLesson.ClassroomCode = _classrooms.First().Code; + } + + if (_newLesson.LessonTypeId == 0 && _lessonTypes.Any()) + { + _newLesson.LessonTypeId = _lessonTypes.First().Id; + } + + if (_newLesson.LecturerId == 0) + { + var lecturer = _people.FirstOrDefault(p => p.Affiliation == "Akademický pracovník"); + if (lecturer is not null) + { + _newLesson.LecturerId = lecturer.Id; + } + } + } +} diff --git a/BlazorApp1/Components/Pages/Schedule.razor b/BlazorApp1/Components/Pages/Schedule.razor new file mode 100644 index 0000000..e547265 --- /dev/null +++ b/BlazorApp1/Components/Pages/Schedule.razor @@ -0,0 +1,73 @@ +@page "/schedule" +@inject ScheduleService ScheduleService + +

Rozvrh týdne

+

Kliknutím na název předmětu zobrazíte detail, včetně navazujících lekcí a kontaktních osob.

+ +@foreach (var dayGroup in _groupedLessons) +{ +
+

@dayGroup.Key

+ @if (dayGroup.Any()) + { +
+ @foreach (var lesson in dayGroup) + { + _subjects.TryGetValue(lesson.SubjectCode, out var subject); + _classrooms.TryGetValue(lesson.ClassroomCode, out var classroom); + _lessonTypes.TryGetValue(lesson.LessonTypeId, out var lessonType); + _people.TryGetValue(lesson.LecturerId, out var lecturer); +
+
+
+ @subject?.Name (@lesson.SubjectCode) +
@lessonType?.Name · @classroom?.Name (@classroom?.Code)
+
Vyučující: @lecturer?.FullName
+
+ @FormatTime(lesson.StartTime) - @FormatTime(lesson.EndTime) +
+
+ } +
+ } + else + { +
V tento den nejsou v rozvrhu žádné lekce.
+ } +
+} + +@code { + private IEnumerable> _groupedLessons = Enumerable.Empty>(); + private Dictionary _subjects = new(); + private Dictionary _classrooms = new(); + private Dictionary _lessonTypes = new(); + private Dictionary _people = new(); + + protected override async Task OnInitializedAsync() + { + await ScheduleService.InitializeAsync(); + var lessons = await ScheduleService.GetLessonsAsync(); + _subjects = (await ScheduleService.GetSubjectsAsync()).ToDictionary(s => s.SubjectCode, s => s); + _classrooms = (await ScheduleService.GetClassroomsAsync()).ToDictionary(c => c.Code, c => c); + _lessonTypes = (await ScheduleService.GetLessonTypesAsync()).ToDictionary(l => l.Id, l => l); + _people = (await ScheduleService.GetPeopleAsync()).ToDictionary(p => p.Id, p => p); + + _groupedLessons = lessons + .GroupBy(l => TranslateDay(l.Day)); + } + + private static string FormatTime(TimeSpan time) => time.ToString("hh\\:mm"); + + private static string TranslateDay(DayOfWeek day) => day switch + { + DayOfWeek.Monday => "Pondělí", + DayOfWeek.Tuesday => "Úterý", + DayOfWeek.Wednesday => "Středa", + DayOfWeek.Thursday => "Čtvrtek", + DayOfWeek.Friday => "Pátek", + DayOfWeek.Saturday => "Sobota", + DayOfWeek.Sunday => "Neděle", + _ => day.ToString() + }; +} diff --git a/BlazorApp1/Components/Pages/SubjectDetails.razor b/BlazorApp1/Components/Pages/SubjectDetails.razor new file mode 100644 index 0000000..08d9ce0 --- /dev/null +++ b/BlazorApp1/Components/Pages/SubjectDetails.razor @@ -0,0 +1,101 @@ +@page "/subjects/{SubjectCode}" +@inject ScheduleService ScheduleService + +Detail předmětu + +@if (_subject is null) +{ +
Předmět s kódem @SubjectCode nebyl nalezen.
+} +else +{ +

@_subject.Name (@_subject.SubjectCode)

+

@_subject.DepartmentCode · @_subject.Credits kreditů

+ +

Lekce v týdnu

+ @if (!_lessons.Any()) + { +
Pro tento předmět zatím nejsou naplánované žádné lekce.
+ } + else + { +
    + @foreach (var lesson in _lessons) + { + _classrooms.TryGetValue(lesson.ClassroomCode, out var classroom); + _lessonTypes.TryGetValue(lesson.LessonTypeId, out var lessonType); + _people.TryGetValue(lesson.LecturerId, out var lecturer); +
  • +
    +
    @TranslateDay(lesson.Day) · @lessonType?.Name
    +
    @classroom?.Name (@classroom?.Code)
    +
    Vyučující: @lecturer?.FullName
    +
    + @FormatTime(lesson.StartTime) – @FormatTime(lesson.EndTime) +
  • + } +
+ } + +

Zapsaní studenti

+ @if (_students.Any()) + { +
    + @foreach (var student in _students) + { +
  • @student.FullName
  • + } +
+ } + else + { +

Pro tento předmět nejsou zapsáni žádní studenti.

+ } +} + +@code { + [Parameter] + public string SubjectCode { get; set; } = string.Empty; + + private Subject? _subject; + private List _lessons = new(); + private List _students = new(); + private Dictionary _classrooms = new(); + private Dictionary _lessonTypes = new(); + private Dictionary _people = new(); + + protected override async Task OnParametersSetAsync() + { + await ScheduleService.InitializeAsync(); + _subject = await ScheduleService.GetSubjectByCodeAsync(SubjectCode); + _lessons = (await ScheduleService.GetLessonsAsync()) + .Where(l => l.SubjectCode.Equals(SubjectCode, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + _classrooms = (await ScheduleService.GetClassroomsAsync()).ToDictionary(c => c.Code, c => c); + _lessonTypes = (await ScheduleService.GetLessonTypesAsync()).ToDictionary(l => l.Id, l => l); + _people = (await ScheduleService.GetPeopleAsync()).ToDictionary(p => p.Id, p => p); + + _students = _lessons + .SelectMany(l => l.StudentIds) + .Distinct() + .Select(id => _people.TryGetValue(id, out var person) ? person : null) + .Where(p => p is not null) + .Cast() + .ToList(); + } + + private static string FormatTime(TimeSpan time) => time.ToString("hh\\:mm"); + + private static string TranslateDay(DayOfWeek day) => day switch + { + DayOfWeek.Monday => "Pondělí", + DayOfWeek.Tuesday => "Úterý", + DayOfWeek.Wednesday => "Středa", + DayOfWeek.Thursday => "Čtvrtek", + DayOfWeek.Friday => "Pátek", + DayOfWeek.Saturday => "Sobota", + DayOfWeek.Sunday => "Neděle", + _ => day.ToString() + }; +} diff --git a/BlazorApp1/Components/_Imports.razor b/BlazorApp1/Components/_Imports.razor index 7a1de04..b7de097 100644 --- a/BlazorApp1/Components/_Imports.razor +++ b/BlazorApp1/Components/_Imports.razor @@ -8,3 +8,5 @@ @using Microsoft.JSInterop @using BlazorApp1 @using BlazorApp1.Components +@using BlazorApp1.Models +@using BlazorApp1.Services diff --git a/BlazorApp1/Data/ScheduleDbContext.cs b/BlazorApp1/Data/ScheduleDbContext.cs new file mode 100644 index 0000000..b232e53 --- /dev/null +++ b/BlazorApp1/Data/ScheduleDbContext.cs @@ -0,0 +1,65 @@ +using BlazorApp1.Models; +using Microsoft.EntityFrameworkCore; + +namespace BlazorApp1.Data; + +public class ScheduleDbContext : DbContext +{ + public ScheduleDbContext(DbContextOptions options) : base(options) + { + } + + public DbSet Classrooms => Set(); + public DbSet Departments => Set(); + public DbSet Subjects => Set(); + public DbSet Roles => Set(); + public DbSet People => Set(); + public DbSet LessonTypes => Set(); + public DbSet Lessons => Set(); + public DbSet LessonStudents => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasKey(d => d.Code); + modelBuilder.Entity().Property(d => d.Code).IsRequired(); + modelBuilder.Entity().Property(d => d.Name).IsRequired(); + + modelBuilder.Entity().Property(c => c.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(c => c.Name).IsRequired(); + modelBuilder.Entity().Property(c => c.Code).IsRequired(); + + modelBuilder.Entity().Property(s => s.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(s => s.Name).IsRequired(); + modelBuilder.Entity().Property(s => s.SubjectCode).IsRequired(); + modelBuilder.Entity().Property(s => s.DepartmentCode).IsRequired(); + modelBuilder.Entity().HasIndex(s => s.SubjectCode).IsUnique(); + + modelBuilder.Entity().Property(l => l.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(l => l.Name).IsRequired(); + + modelBuilder.Entity().Property(r => r.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(r => r.RoleType).IsRequired(); + + modelBuilder.Entity().Property(p => p.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(p => p.FirstName).IsRequired(); + modelBuilder.Entity().Property(p => p.LastName).IsRequired(); + modelBuilder.Entity().Property(p => p.Affiliation).IsRequired(); + + modelBuilder.Entity().Property(l => l.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity() + .HasKey(ls => new { ls.LessonId, ls.PersonId }); + + modelBuilder.Entity() + .HasOne() + .WithMany() + .HasForeignKey(ls => ls.LessonId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasOne() + .WithMany() + .HasForeignKey(ls => ls.PersonId) + .OnDelete(DeleteBehavior.Cascade); + } +} diff --git a/BlazorApp1/Models/Classroom.cs b/BlazorApp1/Models/Classroom.cs new file mode 100644 index 0000000..4169b5a --- /dev/null +++ b/BlazorApp1/Models/Classroom.cs @@ -0,0 +1,20 @@ +namespace BlazorApp1.Models; + +using System.ComponentModel.DataAnnotations; + +public class Classroom +{ + public int Id { get; set; } + + [Required] + public string Name { get; set; } = string.Empty; + + [Required] + public string Code { get; set; } = string.Empty; + + public int Floor { get; set; } + + public int Capacity { get; set; } + + public string Purpose { get; set; } = string.Empty; +} diff --git a/BlazorApp1/Models/Department.cs b/BlazorApp1/Models/Department.cs new file mode 100644 index 0000000..06ac45c --- /dev/null +++ b/BlazorApp1/Models/Department.cs @@ -0,0 +1,15 @@ +namespace BlazorApp1.Models; + +using System.ComponentModel.DataAnnotations; + +public class Department +{ + public int Id { get; set; } + + [Required] + [StringLength(3, MinimumLength = 3)] + public string Code { get; set; } = string.Empty; + + [Required] + public string Name { get; set; } = string.Empty; +} diff --git a/BlazorApp1/Models/Lesson.cs b/BlazorApp1/Models/Lesson.cs new file mode 100644 index 0000000..8e81ac2 --- /dev/null +++ b/BlazorApp1/Models/Lesson.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace BlazorApp1.Models; + +public class Lesson +{ + public int Id { get; set; } + + [Required] + public DayOfWeek Day { get; set; } + + public TimeSpan StartTime { get; set; } + + public TimeSpan EndTime { get; set; } + + [Required] + public string ClassroomCode { get; set; } = string.Empty; + + [Required] + public string SubjectCode { get; set; } = string.Empty; + + [Required] + public int LessonTypeId { get; set; } + + [Required] + public int LecturerId { get; set; } + + [NotMapped] + public List StudentIds { get; set; } = new(); +} diff --git a/BlazorApp1/Models/LessonStudent.cs b/BlazorApp1/Models/LessonStudent.cs new file mode 100644 index 0000000..afa5214 --- /dev/null +++ b/BlazorApp1/Models/LessonStudent.cs @@ -0,0 +1,8 @@ +namespace BlazorApp1.Models; + +public class LessonStudent +{ + public int LessonId { get; set; } + + public int PersonId { get; set; } +} diff --git a/BlazorApp1/Models/LessonType.cs b/BlazorApp1/Models/LessonType.cs new file mode 100644 index 0000000..fe94e0a --- /dev/null +++ b/BlazorApp1/Models/LessonType.cs @@ -0,0 +1,11 @@ +namespace BlazorApp1.Models; + +using System.ComponentModel.DataAnnotations; + +public class LessonType +{ + public int Id { get; set; } + + [Required] + public string Name { get; set; } = string.Empty; +} diff --git a/BlazorApp1/Models/Person.cs b/BlazorApp1/Models/Person.cs new file mode 100644 index 0000000..d8609d6 --- /dev/null +++ b/BlazorApp1/Models/Person.cs @@ -0,0 +1,25 @@ +namespace BlazorApp1.Models; + +using System.ComponentModel.DataAnnotations; + +public class Person +{ + public int Id { get; set; } + + [Required] + public string FirstName { get; set; } = string.Empty; + + [Required] + public string LastName { get; set; } = string.Empty; + + public string? Title { get; set; } + + [Required] + public string Affiliation { get; set; } = string.Empty; + + public int? RoleId { get; set; } + + public string FullName => string.IsNullOrWhiteSpace(Title) + ? $"{FirstName} {LastName}" + : $"{Title} {FirstName} {LastName}"; +} diff --git a/BlazorApp1/Models/Role.cs b/BlazorApp1/Models/Role.cs new file mode 100644 index 0000000..db0e919 --- /dev/null +++ b/BlazorApp1/Models/Role.cs @@ -0,0 +1,11 @@ +namespace BlazorApp1.Models; + +using System.ComponentModel.DataAnnotations; + +public class Role +{ + public int Id { get; set; } + + [Required] + public string RoleType { get; set; } = string.Empty; +} diff --git a/BlazorApp1/Models/Subject.cs b/BlazorApp1/Models/Subject.cs new file mode 100644 index 0000000..28585c0 --- /dev/null +++ b/BlazorApp1/Models/Subject.cs @@ -0,0 +1,21 @@ +namespace BlazorApp1.Models; + +using System.ComponentModel.DataAnnotations; + +public class Subject +{ + public int Id { get; set; } + + [Required] + public string Name { get; set; } = string.Empty; + + [Required] + public string DepartmentCode { get; set; } = string.Empty; + + [Required] + [StringLength(5, MinimumLength = 3)] + public string SubjectCode { get; set; } = string.Empty; + + [Range(0, 30)] + public int Credits { get; set; } +} diff --git a/BlazorApp1/Program.cs b/BlazorApp1/Program.cs index 1953994..9935e51 100644 --- a/BlazorApp1/Program.cs +++ b/BlazorApp1/Program.cs @@ -1,13 +1,29 @@ using BlazorApp1.Components; +using BlazorApp1.Data; +using BlazorApp1.Services; +using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); +var dataDirectory = Path.Combine(builder.Environment.ContentRootPath, "data"); +Directory.CreateDirectory(dataDirectory); +var connectionString = builder.Configuration.GetConnectionString("Schedule") ?? $"Data Source={Path.Combine(dataDirectory, "schedule.db")}"; // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); +builder.Services.AddDbContextFactory(options => + options.UseSqlite(connectionString)); +builder.Services.AddScoped(); + var app = builder.Build(); +await using (var scope = app.Services.CreateAsyncScope()) +{ + var scheduleService = scope.ServiceProvider.GetRequiredService(); + await scheduleService.InitializeAsync(); +} + // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { diff --git a/BlazorApp1/Services/ScheduleService.cs b/BlazorApp1/Services/ScheduleService.cs new file mode 100644 index 0000000..2120ff0 --- /dev/null +++ b/BlazorApp1/Services/ScheduleService.cs @@ -0,0 +1,347 @@ +using BlazorApp1.Data; +using BlazorApp1.Models; +using Microsoft.EntityFrameworkCore; +using System.Data.Common; + +namespace BlazorApp1.Services; + +public class ScheduleService +{ + private readonly IDbContextFactory _dbFactory; + + public ScheduleService(IDbContextFactory dbFactory) + { + _dbFactory = dbFactory; + } + + public async Task InitializeAsync() + { + await using var db = await _dbFactory.CreateDbContextAsync(); + var created = await db.Database.EnsureCreatedAsync(); + + if (!created && !await HasExpectedColumnsAsync(db)) + { + await db.Database.EnsureDeletedAsync(); + await db.Database.EnsureCreatedAsync(); + } + + if (!await db.Departments.AnyAsync()) + { + await SeedAsync(db); + } + } + + public async Task> GetClassroomsAsync() + { + await using var db = await _dbFactory.CreateDbContextAsync(); + return await db.Classrooms.AsNoTracking().OrderBy(c => c.Name).ToListAsync(); + } + + public async Task> GetDepartmentsAsync() + { + await using var db = await _dbFactory.CreateDbContextAsync(); + return await db.Departments.AsNoTracking().OrderBy(d => d.Code).ToListAsync(); + } + + public async Task> GetSubjectsAsync() + { + await using var db = await _dbFactory.CreateDbContextAsync(); + return await db.Subjects.AsNoTracking().OrderBy(s => s.SubjectCode).ToListAsync(); + } + + public async Task> GetRolesAsync() + { + await using var db = await _dbFactory.CreateDbContextAsync(); + return await db.Roles.AsNoTracking().OrderBy(r => r.RoleType).ToListAsync(); + } + + public async Task> GetPeopleAsync() + { + await using var db = await _dbFactory.CreateDbContextAsync(); + return await db.People.AsNoTracking().OrderBy(p => p.LastName).ThenBy(p => p.FirstName).ToListAsync(); + } + + public async Task> GetLessonTypesAsync() + { + await using var db = await _dbFactory.CreateDbContextAsync(); + return await db.LessonTypes.AsNoTracking().OrderBy(l => l.Name).ToListAsync(); + } + + public async Task> GetLessonsAsync() + { + await using var db = await _dbFactory.CreateDbContextAsync(); + var lessons = await db.Lessons.AsNoTracking().ToListAsync(); + var studentLinks = await db.LessonStudents.AsNoTracking().ToListAsync(); + var studentLookup = studentLinks + .GroupBy(l => l.LessonId) + .ToDictionary(g => g.Key, g => g.Select(s => s.PersonId).ToList()); + + foreach (var lesson in lessons) + { + if (studentLookup.TryGetValue(lesson.Id, out var students)) + { + lesson.StudentIds = students; + } + } + + return lessons + .OrderBy(l => l.Day) + .ThenBy(l => l.StartTime) + .ToList(); + } + + public async Task GetSubjectByCodeAsync(string code) + { + await using var db = await _dbFactory.CreateDbContextAsync(); + return await db.Subjects.AsNoTracking().FirstOrDefaultAsync(s => s.SubjectCode == code); + } + + public async Task GetClassroomByCodeAsync(string code) + { + await using var db = await _dbFactory.CreateDbContextAsync(); + return await db.Classrooms.AsNoTracking().FirstOrDefaultAsync(c => c.Code == code); + } + + public async Task GetLessonTypeAsync(int id) + { + await using var db = await _dbFactory.CreateDbContextAsync(); + return await db.LessonTypes.AsNoTracking().FirstOrDefaultAsync(l => l.Id == id); + } + + public async Task GetPersonAsync(int id) + { + await using var db = await _dbFactory.CreateDbContextAsync(); + return await db.People.AsNoTracking().FirstOrDefaultAsync(p => p.Id == id); + } + + public async Task AddClassroomAsync(Classroom classroom) + { + await using var db = await _dbFactory.CreateDbContextAsync(); + db.Classrooms.Add(classroom); + await db.SaveChangesAsync(); + return classroom; + } + + public async Task AddDepartmentAsync(Department department) + { + await using var db = await _dbFactory.CreateDbContextAsync(); + db.Departments.Add(department); + await db.SaveChangesAsync(); + return department; + } + + public async Task AddSubjectAsync(Subject subject) + { + await using var db = await _dbFactory.CreateDbContextAsync(); + db.Subjects.Add(subject); + await db.SaveChangesAsync(); + return subject; + } + + public async Task AddRoleAsync(Role role) + { + await using var db = await _dbFactory.CreateDbContextAsync(); + db.Roles.Add(role); + await db.SaveChangesAsync(); + return role; + } + + public async Task AddPersonAsync(Person person) + { + await using var db = await _dbFactory.CreateDbContextAsync(); + db.People.Add(person); + await db.SaveChangesAsync(); + return person; + } + + public async Task AddLessonTypeAsync(LessonType lessonType) + { + await using var db = await _dbFactory.CreateDbContextAsync(); + db.LessonTypes.Add(lessonType); + await db.SaveChangesAsync(); + return lessonType; + } + + public async Task AddLessonAsync(Lesson lesson) + { + await using var db = await _dbFactory.CreateDbContextAsync(); + var studentIds = lesson.StudentIds.ToList(); + lesson.StudentIds = new List(); + + db.Lessons.Add(lesson); + await db.SaveChangesAsync(); + + if (studentIds.Any()) + { + db.LessonStudents.AddRange(studentIds.Select(s => new LessonStudent + { + LessonId = lesson.Id, + PersonId = s + })); + await db.SaveChangesAsync(); + } + + return lesson; + } + + private static async Task SeedAsync(ScheduleDbContext db) + { + var departments = new List + { + new() { Code = "KMA", Name = "Katedra matematiky" }, + new() { Code = "KIT", Name = "Katedra informačních technologií" } + }; + db.Departments.AddRange(departments); + await db.SaveChangesAsync(); + + var roles = new List + { + new() { RoleType = "Vyučující" }, + new() { RoleType = "Přednášející" }, + new() { RoleType = "Garant" } + }; + db.Roles.AddRange(roles); + await db.SaveChangesAsync(); + + var people = new List + { + new() + { + FirstName = "Jan", + LastName = "Novík", + Title = "Ing.", + Affiliation = "Akademický pracovník", + RoleId = roles.First().Id + }, + new() + { + FirstName = "Petra", + LastName = "Nováková", + Affiliation = "Student" + }, + new() + { + FirstName = "Jan", + LastName = "Dvořák", + Affiliation = "Student" + } + }; + db.People.AddRange(people); + await db.SaveChangesAsync(); + + var classrooms = new List + { + new() { Name = "Velká posluchárna", Code = "VP101", Floor = 1, Capacity = 120, Purpose = "Přednášková" }, + new() { Name = "Počítačová laboratoř", Code = "PC204", Floor = 2, Capacity = 32, Purpose = "Počítačová" } + }; + db.Classrooms.AddRange(classrooms); + await db.SaveChangesAsync(); + + var lessonTypes = new List + { + new() { Name = "Přednáška" }, + new() { Name = "Cvičení" }, + new() { Name = "Seminář" } + }; + db.LessonTypes.AddRange(lessonTypes); + await db.SaveChangesAsync(); + + var subjects = new List + { + new() + { + Name = "Programování v C#", + DepartmentCode = "KIT", + SubjectCode = "KIT-CSP", + Credits = 5 + }, + new() + { + Name = "Lineární algebra", + DepartmentCode = "KMA", + SubjectCode = "KMA-LA", + Credits = 4 + } + }; + db.Subjects.AddRange(subjects); + await db.SaveChangesAsync(); + + var lecturer = people.First(p => p.Affiliation == "Akademický pracovník"); + var studentIds = people.Where(p => p.Affiliation == "Student").Select(p => p.Id).ToList(); + + var lecture = lessonTypes.First(); + var lab = lessonTypes.First(lt => lt.Name == "Cvičení"); + + var lessons = new List + { + new() + { + SubjectCode = "KIT-CSP", + Day = DayOfWeek.Monday, + StartTime = new TimeSpan(9, 0, 0), + EndTime = new TimeSpan(10, 30, 0), + ClassroomCode = "VP101", + LessonTypeId = lecture.Id, + LecturerId = lecturer.Id + }, + new() + { + SubjectCode = "KIT-CSP", + Day = DayOfWeek.Wednesday, + StartTime = new TimeSpan(14, 0, 0), + EndTime = new TimeSpan(15, 30, 0), + ClassroomCode = "PC204", + LessonTypeId = lab.Id, + LecturerId = lecturer.Id + }, + new() + { + SubjectCode = "KMA-LA", + Day = DayOfWeek.Thursday, + StartTime = new TimeSpan(8, 0, 0), + EndTime = new TimeSpan(9, 30, 0), + ClassroomCode = "VP101", + LessonTypeId = lecture.Id, + LecturerId = lecturer.Id + } + }; + db.Lessons.AddRange(lessons); + await db.SaveChangesAsync(); + + db.LessonStudents.AddRange(lessons.SelectMany(l => studentIds.Select(s => new LessonStudent + { + LessonId = l.Id, + PersonId = s + }))); + + await db.SaveChangesAsync(); + } + + private static async Task HasExpectedColumnsAsync(ScheduleDbContext db) + { + var connection = db.Database.GetDbConnection(); + await connection.OpenAsync(); + await using var command = connection.CreateCommand(); + command.CommandText = "PRAGMA table_info('People');"; + var columns = new Dictionary(StringComparer.OrdinalIgnoreCase); + + await using var reader = await command.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { + var name = reader.GetString(1); + var type = reader.GetString(2); + columns[name] = type; + } + + await connection.CloseAsync(); + + var expectedColumns = new[] { "Id", "FirstName", "LastName", "Title", "Affiliation", "RoleId" }; + if (expectedColumns.Any(c => !columns.ContainsKey(c))) + { + return false; + } + + return columns.TryGetValue("Id", out var idType) + && idType.Contains("INT", StringComparison.OrdinalIgnoreCase); + } +} diff --git a/BlazorApp1/appsettings.json b/BlazorApp1/appsettings.json index 10f68b8..297588e 100644 --- a/BlazorApp1/appsettings.json +++ b/BlazorApp1/appsettings.json @@ -1,4 +1,7 @@ { + "ConnectionStrings": { + "Schedule": "Data Source=data/schedule.db" + }, "Logging": { "LogLevel": { "Default": "Information", From 65f523c7d565e963b23d1f108b1e53b0d170125f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:51:05 +0000 Subject: [PATCH 2/2] Initial plan