This commit is contained in:
2026-03-29 23:47:31 +02:00
commit 216d5d2280
75 changed files with 5702 additions and 0 deletions

View File

@@ -0,0 +1,136 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
using LehrerApp.Core.Services;
using System.Collections.ObjectModel;
namespace LehrerApp.Desktop.ViewModels;
public partial class DashboardViewModel : ObservableObject
{
private readonly IGroupRepository _groups;
private readonly ILessonRepository _lessons;
private readonly IWorkTaskRepository _tasks;
private readonly SchoolYearService _schoolYearService;
[ObservableProperty] private string _greeting = "";
[ObservableProperty] private string _currentDate = "";
[ObservableProperty] private string _currentSchoolYear = "";
public ObservableCollection<LessonItem> TodaysLessons { get; } = [];
public ObservableCollection<TaskItem> OpenTasks { get; } = [];
public ObservableCollection<GroupChip> CurrentGroups { get; } = [];
public DashboardViewModel(
IGroupRepository groups,
ILessonRepository lessons,
IWorkTaskRepository tasks,
SchoolYearService schoolYearService)
{
_groups = groups;
_lessons = lessons;
_tasks = tasks;
_schoolYearService = schoolYearService;
Load();
}
private void Load()
{
var now = DateTime.Now;
var today = DateOnly.FromDateTime(now);
CurrentDate = now.ToString("dddd, d. MMMM yyyy",
new System.Globalization.CultureInfo("de-DE"));
CurrentSchoolYear = _schoolYearService.CurrentSchoolYear();
Greeting = now.Hour < 12 ? "Guten Morgen" :
now.Hour < 18 ? "Guten Tag" : "Guten Abend";
// Heutige Stunden
TodaysLessons.Clear();
var schoolYear = _schoolYearService.CurrentSchoolYear();
var allGroups = _groups.GetBySchoolYear(schoolYear)
.ToDictionary(g => g.Id);
var todayLessons = allGroups.Keys
.SelectMany(gid => _lessons.GetByGroupAndDate(gid, today))
.OrderBy(l => l.LessonNumber)
.ToList();
foreach (var lesson in todayLessons)
{
if (!allGroups.TryGetValue(lesson.GroupId, out var group)) continue;
TodaysLessons.Add(new LessonItem
{
GroupName = group.Name,
Topic = lesson.Topic,
Status = lesson.Status,
});
}
// Offene Aufgaben (mit Fälligkeit)
OpenTasks.Clear();
var openTasks = _tasks.GetByStatus(WorkTaskStatus.Open)
.Concat(_tasks.GetByStatus(WorkTaskStatus.InProgress))
.OrderBy(t => t.DueDate ?? DateOnly.MaxValue)
.Take(5);
foreach (var task in openTasks)
{
var isOverdue = task.DueDate.HasValue && task.DueDate < today;
var isDueSoon = task.DueDate.HasValue &&
task.DueDate >= today &&
task.DueDate <= today.AddDays(3);
OpenTasks.Add(new TaskItem
{
Title = task.Title,
DueDate = task.DueDate?.ToString("dd.MM.") ?? "",
IsOverdue = isOverdue,
IsDueSoon = isDueSoon,
Status = task.Status,
});
}
// Aktuelle Lerngruppen als Chips
CurrentGroups.Clear();
foreach (var group in _groups.GetBySchoolYear(schoolYear)
.OrderBy(g => g.Name))
{
CurrentGroups.Add(new GroupChip
{
GroupId = group.Id,
Name = group.Name,
Subject = group.Subject ?? "",
});
}
}
[RelayCommand]
private void Refresh() => Load();
}
// Anzeigemodelle für Dashboard-Widgets
public class LessonItem
{
public string GroupName { get; set; } = "";
public string Topic { get; set; } = "";
public LessonStatus Status { get; set; }
}
public class TaskItem
{
public string Title { get; set; } = "";
public string DueDate { get; set; } = "";
public bool IsOverdue { get; set; }
public bool IsDueSoon { get; set; }
public WorkTaskStatus Status { get; set; }
}
public class GroupChip
{
public Guid GroupId { get; set; }
public string Name { get; set; } = "";
public string Subject { get; set; } = "";
}

View File

@@ -0,0 +1,158 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LehrerApp.Data;
using LehrerApp.Sync;
using LehrerApp.Sync.Models;
namespace LehrerApp.Desktop.ViewModels;
/// <summary>
/// Steuert den Dialog zum Einrichten eines neuen Geräts.
///
/// Zwei Modi:
/// Sender → "Neues Gerät hinzufügen" erzeugt Snapshot und zeigt Code
/// Empfänger → "Mit bestehendem Account verbinden" Code eingeben, Restore
/// </summary>
public partial class DevicePairingViewModel : ObservableObject
{
private readonly SnapshotService _snapshotService;
// ── Gemeinsamer State ─────────────────────────────────────────────────────
[ObservableProperty] private PairingMode _mode = PairingMode.SelectMode;
[ObservableProperty] private bool _isBusy;
[ObservableProperty] private string _statusMessage = "";
[ObservableProperty] private int _progressPercent;
[ObservableProperty] private bool _isError;
[ObservableProperty] private bool _isComplete;
// ── Sender-State ─────────────────────────────────────────────────────────
[ObservableProperty] private string _generatedCode = "";
[ObservableProperty] private DateTime _codeExpiresAt;
[ObservableProperty] private string _codeExpiresText = "";
// ── Empfänger-State ───────────────────────────────────────────────────────
[ObservableProperty] private string _inputCode = "";
public DevicePairingViewModel(SnapshotService snapshotService)
{
_snapshotService = snapshotService;
_snapshotService.ProgressChanged += OnProgress;
}
// ── Navigation zwischen Modi ──────────────────────────────────────────────
[RelayCommand]
private void SelectSender() => Mode = PairingMode.Sender;
[RelayCommand]
private void SelectReceiver() => Mode = PairingMode.Receiver;
[RelayCommand]
private void Back()
{
Mode = PairingMode.SelectMode;
Reset();
}
// ── Sender: Snapshot erstellen ────────────────────────────────────────────
[RelayCommand(CanExecute = nameof(CanCreateSnapshot))]
private async Task CreateSnapshotAsync()
{
IsBusy = true;
IsError = false;
IsComplete = false;
try
{
var result = await _snapshotService.CreateAndUploadAsync();
GeneratedCode = result.Code;
CodeExpiresAt = result.ExpiresAt;
CodeExpiresText = $"Gültig bis {result.ExpiresAt:HH:mm} Uhr " +
$"({result.ExpiresAt:dd.MM.yyyy})";
Mode = PairingMode.SenderShowCode;
}
catch (Exception ex)
{
IsError = true;
StatusMessage = $"Fehler: {ex.Message}";
}
finally
{
IsBusy = false;
}
}
private bool CanCreateSnapshot() => !IsBusy;
// ── Empfänger: Snapshot wiederherstellen ──────────────────────────────────
[RelayCommand(CanExecute = nameof(CanRestore))]
private async Task RestoreAsync(string targetDbPath)
{
IsBusy = true;
IsError = false;
IsComplete = false;
try
{
// Schlüssel wird aus dem Code extrahiert und lokal gespeichert
// → App-Neustart lädt ihn automatisch
await _snapshotService.RestoreFromCodeAsync(
InputCode.Trim(), targetDbPath);
IsComplete = true;
StatusMessage = "Erfolgreich! Bitte die App neu starten.";
Mode = PairingMode.ReceiverDone;
}
catch (SnapshotNotFoundException ex)
{
IsError = true;
StatusMessage = ex.Message;
}
catch (Exception ex)
{
IsError = true;
StatusMessage = $"Fehler beim Wiederherstellen: {ex.Message}";
}
finally
{
IsBusy = false;
}
}
private bool CanRestore() =>
!IsBusy && InputCode.Trim().Length >= 5;
// ── Hilfsmethoden ─────────────────────────────────────────────────────────
private void OnProgress(SnapshotProgress progress)
{
StatusMessage = progress.Message;
ProgressPercent = progress.PercentComplete;
}
private void Reset()
{
StatusMessage = "";
ProgressPercent = 0;
IsError = false;
IsComplete = false;
GeneratedCode = "";
InputCode = "";
IsBusy = false;
}
}
public enum PairingMode
{
SelectMode, // Startbildschirm: Sender oder Empfänger wählen
Sender, // Sender bereit, Snapshot erstellen
SenderShowCode, // Code wird angezeigt
Receiver, // Code-Eingabe
ReceiverDone, // Erfolgreich wiederhergestellt
}

View File

@@ -0,0 +1,90 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
using LehrerApp.Core.Services;
namespace LehrerApp.Desktop.ViewModels.Groups;
public partial class AddGroupDialogViewModel : ObservableObject
{
private readonly IGroupRepository _groups;
private readonly SchoolYearService _schoolYearService;
[ObservableProperty] private string _name = "";
[ObservableProperty] private string _subject = "";
[ObservableProperty] private GroupType _type = GroupType.Course;
[ObservableProperty] private int _gradeLevel = 10;
[ObservableProperty] private GradingSystem _gradingSystem = GradingSystem.Grades1To6;
[ObservableProperty] private int? _hoursPerWeek;
[ObservableProperty] private string _selectedSchoolYear = "";
[ObservableProperty] private string _validationMessage = "";
public List<string> SchoolYears { get; }
public List<GroupTypeItem> GroupTypes { get; } =
[
new(GroupType.Course, "Fachkurs"),
new(GroupType.Class, "Klassenverband"),
];
public List<GradingSystemItem> GradingSystems { get; } =
[
new(GradingSystem.Grades1To6, "Noten 16 (Sek I)"),
new(GradingSystem.Points0To15, "Punkte 015 (Oberstufe)"),
];
// Wird vom Dialog aufgerufen wenn OK geklickt wurde
public LearningGroup? Result { get; private set; }
public AddGroupDialogViewModel(
IGroupRepository groups,
SchoolYearService schoolYearService)
{
_groups = groups;
_schoolYearService = schoolYearService;
SchoolYears = schoolYearService.RecentSchoolYears(3);
SelectedSchoolYear = schoolYearService.CurrentSchoolYear();
// Automatisch Notensystem vorschlagen wenn Klassenstufe sich ändert
PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(GradeLevel))
GradingSystem = GradeLevel >= 11
? GradingSystem.Points0To15
: GradingSystem.Grades1To6;
};
}
[RelayCommand]
private bool Save()
{
// Validierung
if (string.IsNullOrWhiteSpace(Name))
{
ValidationMessage = "Bitte einen Namen eingeben.";
return false;
}
if (GradeLevel is < 1 or > 13)
{
ValidationMessage = "Klassenstufe muss zwischen 1 und 13 liegen.";
return false;
}
Result = new LearningGroup
{
Name = Name.Trim(),
Subject = string.IsNullOrWhiteSpace(Subject) ? null : Subject.Trim(),
Type = Type,
GradeLevel = GradeLevel,
GradingSystem = GradingSystem,
SchoolYear = SelectedSchoolYear,
HoursPerWeek = HoursPerWeek,
};
_groups.Save(Result);
ValidationMessage = "";
return true;
}
}
public record GroupTypeItem(GroupType Value, string Label);
public record GradingSystemItem(GradingSystem Value, string Label);

View File

@@ -0,0 +1,208 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
using LehrerApp.Core.Services;
using System.Collections.ObjectModel;
namespace LehrerApp.Desktop.ViewModels.Groups;
// ── Gruppenliste ──────────────────────────────────────────────────────────────
public partial class GroupListViewModel : ObservableObject
{
private readonly IGroupRepository _groups;
private readonly SchoolYearService _schoolYearService;
[ObservableProperty] private string _selectedSchoolYear = "";
[ObservableProperty] private string _searchText = "";
[ObservableProperty] private GroupListItem? _selectedGroup;
public ObservableCollection<string> SchoolYears { get; } = [];
public ObservableCollection<GroupListItem> Groups { get; } = [];
public GroupListViewModel(
IGroupRepository groups,
SchoolYearService schoolYearService)
{
_groups = groups;
_schoolYearService = schoolYearService;
foreach (var y in schoolYearService.RecentSchoolYears())
SchoolYears.Add(y);
SelectedSchoolYear = schoolYearService.CurrentSchoolYear();
LoadGroups();
}
partial void OnSelectedSchoolYearChanged(string value) => LoadGroups();
partial void OnSearchTextChanged(string value) => LoadGroups();
private void LoadGroups()
{
Groups.Clear();
var all = _groups.GetBySchoolYear(SelectedSchoolYear);
var filtered = string.IsNullOrWhiteSpace(SearchText)
? all
: all.Where(g =>
g.Name.Contains(SearchText, StringComparison.OrdinalIgnoreCase) ||
(g.Subject?.Contains(SearchText, StringComparison.OrdinalIgnoreCase) ?? false));
foreach (var g in filtered)
Groups.Add(new GroupListItem(g));
}
[RelayCommand]
private void AddGroup()
{
// TODO: Dialog öffnen
}
[RelayCommand]
private void Refresh() => LoadGroups();
}
public class GroupListItem
{
public Guid Id { get; }
public string Name { get; }
public string Subject { get; }
public string SchoolYear { get; }
public int GradeLevel { get; }
public string TypeLabel { get; }
public string GradingLabel { get; }
public GroupListItem(LearningGroup g)
{
Id = g.Id;
Name = g.Name;
Subject = g.Subject ?? "";
SchoolYear = g.SchoolYear;
GradeLevel = g.GradeLevel;
TypeLabel = g.Type == GroupType.Class ? "Klasse" : "Kurs";
GradingLabel = g.GradingSystem == GradingSystem.Grades1To6
? "Noten 16"
: "Punkte 015";
}
}
// ── Gruppendetail (Tab-Navigation) ────────────────────────────────────────────
public partial class GroupDetailViewModel : ObservableObject
{
private readonly IGroupRepository _groups;
private readonly IStudentRepository _students;
private readonly IEnrollmentRepository _enrollments;
private readonly IExamRepository _exams;
private readonly SchoolYearService _schoolYearService;
[ObservableProperty] private LearningGroup? _group;
[ObservableProperty] private string _groupTitle = "";
[ObservableProperty] private int _studentCount;
[ObservableProperty] private GroupTab _activeTab = GroupTab.Overview;
// Sub-ViewModels für Tabs lazy geladen
public ObservableCollection<StudentSummary> Students { get; } = [];
public ObservableCollection<ExamSummary> Exams { get; } = [];
public GroupDetailViewModel(
IGroupRepository groups,
IStudentRepository students,
IEnrollmentRepository enrollments,
IExamRepository exams,
SchoolYearService schoolYearService)
{
_groups = groups;
_students = students;
_enrollments = enrollments;
_exams = exams;
_schoolYearService = schoolYearService;
}
public void LoadGroup(Guid groupId)
{
Group = _groups.GetById(groupId);
if (Group is null) return;
GroupTitle = $"{Group.Name} · {Group.SchoolYear}";
LoadStudents();
LoadExams();
}
[RelayCommand]
private void SwitchTab(GroupTab tab)
{
ActiveTab = tab;
}
private void LoadStudents()
{
if (Group is null) return;
Students.Clear();
var enrolled = _students.GetByGroup(Group.Id, Group.SchoolYear);
StudentCount = enrolled.Count;
foreach (var s in enrolled)
Students.Add(new StudentSummary(s));
}
private void LoadExams()
{
if (Group is null) return;
Exams.Clear();
foreach (var e in _exams.GetByGroup(Group.Id))
Exams.Add(new ExamSummary(e));
}
[RelayCommand]
private void AddStudent()
{
// TODO: Schüler-Auswahl-Dialog
}
[RelayCommand]
private void AddExam()
{
// TODO: Klausur-Erstellen-Dialog
}
}
public enum GroupTab { Overview, Students, Exams, Grades, Planner, Documentation }
public class StudentSummary
{
public Guid Id { get; }
public string FullName { get; }
public StudentSummary(Core.Models.Student s)
{
Id = s.Id;
FullName = s.FullName;
}
}
public class ExamSummary
{
public Guid Id { get; }
public string Title { get; }
public string Date { get; }
public string Status { get; }
public ExamSummary(Core.Models.Exam e)
{
Id = e.Id;
Title = e.Title;
Date = e.Date.ToString("dd.MM.yyyy");
Status = e.Status switch
{
ExamStatus.Planned => "Geplant",
ExamStatus.Conducted => "Durchgeführt",
ExamStatus.Graded => "Korrigiert",
ExamStatus.Returned => "Zurückgegeben",
_ => "",
};
}
}

View File

@@ -0,0 +1,87 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LehrerApp.Core.Services;
using LehrerApp.Desktop.ViewModels.Groups;
using LehrerApp.Desktop.ViewModels.Students;
using Microsoft.Extensions.DependencyInjection;
namespace LehrerApp.Desktop.ViewModels;
public partial class MainWindowViewModel : ObservableObject
{
private readonly IServiceProvider _services;
[ObservableProperty]
private ObservableObject? _currentPage;
[ObservableProperty]
private NavItem _activeNavItem = NavItem.Dashboard;
// ── Fehlende Property die im MainWindow.axaml referenziert wird ───────────
[ObservableProperty]
private string _currentSchoolYear = "";
public MainWindowViewModel(
IServiceProvider services,
DashboardViewModel dashboard,
SchoolYearService schoolYearService)
{
_services = services;
CurrentPage = dashboard;
CurrentSchoolYear = schoolYearService.CurrentSchoolYear();
}
// ── Navigation ────────────────────────────────────────────────────────────
[RelayCommand]
private void NavigateTo(NavItem item)
{
ActiveNavItem = item;
CurrentPage = item switch
{
NavItem.Dashboard => _services.GetRequiredService<DashboardViewModel>(),
NavItem.Groups => _services.GetRequiredService<GroupListViewModel>(),
NavItem.Students => _services.GetRequiredService<StudentListViewModel>(),
NavItem.Exams => CreatePlaceholder("Klausuren"),
NavItem.Planner => CreatePlaceholder("Unterrichtsplanung"),
NavItem.Workload => CreatePlaceholder("Arbeitszeit"),
NavItem.Settings => CreatePlaceholder("Einstellungen"),
_ => CurrentPage,
};
}
public void NavigateToGroup(Guid groupId)
{
ActiveNavItem = NavItem.Groups;
var vm = _services.GetRequiredService<GroupDetailViewModel>();
vm.LoadGroup(groupId);
CurrentPage = vm;
}
public void NavigateToStudent(Guid studentId)
{
ActiveNavItem = NavItem.Students;
var vm = _services.GetRequiredService<StudentDetailViewModel>();
vm.LoadStudent(studentId);
CurrentPage = vm;
}
private static PlaceholderViewModel CreatePlaceholder(string title) =>
new() { Title = title };
}
public enum NavItem
{
Dashboard,
Groups,
Students,
Exams,
Planner,
Workload,
Settings,
}
public partial class PlaceholderViewModel : ObservableObject
{
[ObservableProperty] private string _title = "";
}

View File

@@ -0,0 +1,223 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
using System.Collections.ObjectModel;
namespace LehrerApp.Desktop.ViewModels.Students;
// ── Schülerliste ──────────────────────────────────────────────────────────────
public partial class StudentListViewModel : ObservableObject
{
private readonly IStudentRepository _students;
[ObservableProperty] private string _searchText = "";
[ObservableProperty] private bool _showInactive;
[ObservableProperty] private StudentListItem? _selectedStudent;
public ObservableCollection<StudentListItem> Students { get; } = [];
public StudentListViewModel(IStudentRepository students)
{
_students = students;
LoadStudents();
}
partial void OnSearchTextChanged(string value) => LoadStudents();
partial void OnShowInactiveChanged(bool value) => LoadStudents();
private void LoadStudents()
{
Students.Clear();
var all = _students.GetAll(includeInactive: ShowInactive);
var filtered = string.IsNullOrWhiteSpace(SearchText)
? all
: all.Where(s =>
s.LastName.Contains(SearchText, StringComparison.OrdinalIgnoreCase) ||
s.FirstName.Contains(SearchText, StringComparison.OrdinalIgnoreCase));
foreach (var s in filtered)
Students.Add(new StudentListItem(s));
}
[RelayCommand]
private void AddStudent()
{
// TODO: Neuer-Schüler-Dialog
}
[RelayCommand]
private void Refresh() => LoadStudents();
}
public class StudentListItem
{
public Guid Id { get; }
public string FullName { get; }
public string DateOfBirth { get; }
public bool IsActive { get; }
public StudentListItem(Student s)
{
Id = s.Id;
FullName = s.FullName;
DateOfBirth = s.DateOfBirth?.ToString("dd.MM.yyyy") ?? "";
IsActive = s.IsActive;
}
}
// ── Schülerdetail ─────────────────────────────────────────────────────────────
public partial class StudentDetailViewModel : ObservableObject
{
private readonly IStudentRepository _students;
private readonly IEnrollmentRepository _enrollments;
private readonly IGroupRepository _groups;
private readonly IGradeRepository _grades;
private readonly IDocumentationRepository _docs;
[ObservableProperty] private Student? _student;
[ObservableProperty] private string _studentTitle = "";
[ObservableProperty] private StudentTab _activeTab = StudentTab.Overview;
[ObservableProperty] private bool _isEditing;
// Bearbeitbare Felder
[ObservableProperty] private string _editFirstName = "";
[ObservableProperty] private string _editLastName = "";
[ObservableProperty] private string _editNotes = "";
public ObservableCollection<EnrollmentEntry> Enrollments { get; } = [];
public ObservableCollection<GradeEntry> Grades { get; } = [];
public ObservableCollection<DocEntry> Documentation { get; } = [];
public StudentDetailViewModel(
IStudentRepository students,
IEnrollmentRepository enrollments,
IGroupRepository groups,
IGradeRepository grades,
IDocumentationRepository docs)
{
_students = students;
_enrollments = enrollments;
_groups = groups;
_grades = grades;
_docs = docs;
}
public void LoadStudent(Guid studentId)
{
Student = _students.GetById(studentId);
if (Student is null) return;
StudentTitle = Student.FullName;
EditFirstName = Student.FirstName;
EditLastName = Student.LastName;
EditNotes = Student.Notes ?? "";
LoadEnrollments();
LoadDocs();
}
[RelayCommand]
private void SwitchTab(StudentTab tab) => ActiveTab = tab;
[RelayCommand]
private void StartEdit() => IsEditing = true;
[RelayCommand]
private void CancelEdit()
{
if (Student is null) return;
EditFirstName = Student.FirstName;
EditLastName = Student.LastName;
EditNotes = Student.Notes ?? "";
IsEditing = false;
}
[RelayCommand]
private void SaveEdit()
{
if (Student is null) return;
Student.FirstName = EditFirstName;
Student.LastName = EditLastName;
Student.Notes = string.IsNullOrWhiteSpace(EditNotes) ? null : EditNotes;
_students.Save(Student);
StudentTitle = Student.FullName;
IsEditing = false;
}
private void LoadEnrollments()
{
if (Student is null) return;
Enrollments.Clear();
var enrollments = _enrollments.GetByStudent(Student.Id);
var groupIds = enrollments.Select(e => e.GroupId).Distinct();
var groupMap = groupIds
.Select(id => _groups.GetById(id))
.Where(g => g is not null)
.ToDictionary(g => g!.Id);
foreach (var e in enrollments.OrderByDescending(e => e.SchoolYear))
{
if (!groupMap.TryGetValue(e.GroupId, out var group)) continue;
Enrollments.Add(new EnrollmentEntry
{
SchoolYear = e.SchoolYear,
GroupName = group.Name,
Subject = group.Subject ?? "",
});
}
}
private void LoadDocs()
{
if (Student is null) return;
Documentation.Clear();
foreach (var doc in _docs.GetByStudent(Student.Id))
{
Documentation.Add(new DocEntry
{
Date = doc.Date.ToString("dd.MM.yyyy"),
Title = doc.Title,
TypeLabel = doc.Type switch
{
DocumentationType.Conversation => "Gespräch",
DocumentationType.Incident => "Vorkommnis",
DocumentationType.SupportPlan => "Förderplan",
DocumentationType.Absence => "Fehlzeit",
_ => "",
},
IsConfidential = doc.IsConfidential,
});
}
}
}
public enum StudentTab { Overview, Grades, Documentation }
public class EnrollmentEntry
{
public string SchoolYear { get; set; } = "";
public string GroupName { get; set; } = "";
public string Subject { get; set; } = "";
}
public class GradeEntry
{
public string Date { get; set; } = "";
public string Value { get; set; } = "";
public string Category { get; set; } = "";
public string GroupName { get; set; } = "";
}
public class DocEntry
{
public string Date { get; set; } = "";
public string Title { get; set; } = "";
public string TypeLabel { get; set; } = "";
public bool IsConfidential { get; set; }
}

View File

@@ -0,0 +1,63 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LehrerApp.Sync;
using LehrerApp.Sync.Models;
namespace LehrerApp.Desktop.ViewModels;
public partial class SyncStatusViewModel : ObservableObject
{
private readonly SyncEngine? _engine;
[ObservableProperty] private string _statusText = "Kein Server konfiguriert";
[ObservableProperty] private string _lastSyncText = "";
[ObservableProperty] private int _pendingCount;
[ObservableProperty] private int _conflictCount;
[ObservableProperty] private bool _isSyncing;
[ObservableProperty] private bool _hasConflicts;
[ObservableProperty] private bool _isServerConfigured;
public SyncStatusViewModel(SyncEngine? engine)
{
_engine = engine;
IsServerConfigured = engine is not null;
if (_engine is not null)
{
_engine.StatusChanged += OnStatusChanged;
OnStatusChanged(_engine.Status);
}
}
private void OnStatusChanged(SyncStatus status)
{
IsSyncing = status.State == SyncState.Syncing;
HasConflicts = status.ConflictCount > 0;
PendingCount = status.PendingEvents;
ConflictCount = status.ConflictCount;
StatusText = status.State switch
{
SyncState.Idle => PendingCount > 0
? $"{PendingCount} ausstehend"
: "Synchronisiert",
SyncState.Syncing => "Synchronisiere...",
SyncState.Offline => "Offline",
SyncState.Error => $"Fehler: {status.ErrorMessage}",
_ => "",
};
LastSyncText = status.LastSyncAt.HasValue
? $"Zuletzt: {status.LastSyncAt:HH:mm}"
: "Noch nie synchronisiert";
}
[RelayCommand(CanExecute = nameof(CanSyncNow))]
private async Task SyncNow()
{
if (_engine is null) return;
await _engine.SyncNowAsync(isAutomatic: false);
}
private bool CanSyncNow() => _engine is not null && !IsSyncing;
}