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 @@
-
- Counter
+
+ Rozvrh týdne
-
-
- Weather
+
+ Správa dat
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.
+
+
+
+ Rozvrh týdne: přehled všech plánovaných lekcí s návazností na předměty a vyučující.
+ Detail předmětu: harmonogram, základní informace a seznam zapsaných studentů.
+ Správa dat: formuláře pro přidání entit (učebna, katedra, předmět, role, osoba, druh lekce, lekce).
+
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.
+
+
+
+ SwitchTab("main")'>Základní údaje
+
+
+ SwitchTab("advanced")'>Pokročilé indexy
+
+
+
+@if (_activeTab == "main")
+{
+
+
+
Učebna
+
+
+
+
+ Název učebny
+
+
+
+ Zkratka
+
+
+
+ Patro
+
+
+
+ Kapacita
+
+
+
+ Určení
+
+
+ Přidat učebnu
+
+
+
+
Předmět
+
+
+
+
+ Název předmětu
+
+
+
+ Katedra
+
+ @foreach (var dep in _departments)
+ {
+ @dep.Code - @dep.Name
+ }
+
+
+
+ Zkratka předmětu (3-5 znaků)
+
+
+
+ Počet kreditů
+
+
+ Přidat předmět
+
+
+
+
+
+
+
+
+
Role & Osoby
+
+
+
+
+
+ Jméno
+
+
+
+ Příjmení
+
+
+
+
+ Titul (volitelné)
+
+
+
+ Zařazení
+
+ Akademický pracovník
+ Student
+
+
+
+ Typ role (pouze akademici)
+
+ Bez role
+ @foreach (var role in _roles)
+ {
+ @role.RoleType
+ }
+
+
+ Přidat osobu
+
+
+
+
Lekce
+
+
+
+
+ Předmět
+
+ @foreach (var subject in _subjects)
+ {
+ @subject.SubjectCode - @subject.Name
+ }
+
+
+
+ Den
+
+ @foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek)))
+ {
+ @day
+ }
+
+
+
+
+ Začátek (HH:mm)
+
+
+
+ Konec (HH:mm)
+
+
+
+
+ Učebna
+
+ @foreach (var room in _classrooms)
+ {
+ @room.Code - @room.Name
+ }
+
+
+
+ Druh lekce
+
+ @foreach (var lt in _lessonTypes)
+ {
+ @lt.Name
+ }
+
+
+
+ Vyučující
+
+ @foreach (var person in _people.Where(p => p.Affiliation == "Akademický pracovník"))
+ {
+ @person.FullName
+ }
+
+
+
+
Zapsaní studenti
+ @foreach (var student in _people.Where(p => p.Affiliation == "Student"))
+ {
+
+ ToggleStudent(student.Id, args.Value)">
+ @student.FullName
+
+ }
+
+ Přidat lekci
+
+
+
+}
+else
+{
+
+
+
Katedra
+
Tabulka indexů pro vazbu předmětů na katedry.
+
+
+
+
+ Třímístná zkratka
+
+
+
+ Název katedry
+
+
+ Přidat katedru
+
+
+
+
Role
+
Index pro akademické pracovníky.
+
+
+
+
+ Typ role
+
+ Přidat roli
+
+
+
+
+
Druh lekce
+
Index pro typy lekcí (přednáška, cvičení...).
+
+
+
+
+ Popis druhu lekce
+
+
+ Přidat druh
+
+
+
+}
+
+@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);
+
+
+
+
@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