Compare commits
2
Commits
7d3d021d09
...
89a6376fb6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89a6376fb6 | ||
|
|
b7a105af73 |
@@ -93,7 +93,7 @@ public interface IHasAttachments
|
|||||||
List<DocumentAttachment> Attachments { get; }
|
List<DocumentAttachment> Attachments { get; }
|
||||||
}
|
}
|
||||||
// Hinten angefügt, damit die numerischen Werte bereits gespeicherter LiteDB-Einträge stabil bleiben.
|
// Hinten angefügt, damit die numerischen Werte bereits gespeicherter LiteDB-Einträge stabil bleiben.
|
||||||
public enum DocumentationType { Conversation, Incident, SupportPlan, Absence, ParentCall, ParentLetter }
|
public enum DocumentationType { Conversation, Incident, SupportPlan, Absence, ParentCall, ParentLetter, Planning }
|
||||||
public enum SupportStatus { Active, Completed, Paused }
|
public enum SupportStatus { Active, Completed, Paused }
|
||||||
|
|
||||||
public class WorkTask
|
public class WorkTask
|
||||||
|
|||||||
@@ -59,12 +59,12 @@ public sealed class DashboardViewModelTests
|
|||||||
DashboardSettingsService? dashboardSettings = null, FakeSchoolHolidays? schoolHolidays = null,
|
DashboardSettingsService? dashboardSettings = null, FakeSchoolHolidays? schoolHolidays = null,
|
||||||
FakeLessons? lessons = null, FakeSubstitutionEntries? substitutions = null,
|
FakeLessons? lessons = null, FakeSubstitutionEntries? substitutions = null,
|
||||||
FakeSessions? sessions = null, FakeEntries? entries = null,
|
FakeSessions? sessions = null, FakeEntries? entries = null,
|
||||||
FakeAnnualPlanEvents? annualPlanEvents = null)
|
FakeAnnualPlanEvents? annualPlanEvents = null, List<LearningGroup>? allGroups = null)
|
||||||
{
|
{
|
||||||
lessons ??= new FakeLessons();
|
lessons ??= new FakeLessons();
|
||||||
lessons.Add(lesson);
|
lessons.Add(lesson);
|
||||||
return new DashboardViewModel(
|
return new DashboardViewModel(
|
||||||
new FakeGroups([group]), new FakeSubjects([]), lessons,
|
new FakeGroups(allGroups ?? [group]), new FakeSubjects([]), lessons,
|
||||||
exams ?? new FakeExams([]), results ?? new FakeResults(), grades ?? new FakeGrades(),
|
exams ?? new FakeExams([]), results ?? new FakeResults(), grades ?? new FakeGrades(),
|
||||||
reportGrades ?? new FakeReportGrades(), memberships ?? new FakeMemberships([]),
|
reportGrades ?? new FakeReportGrades(), memberships ?? new FakeMemberships([]),
|
||||||
tasks ?? new FakeWorkTasks(), sessions ?? new FakeSessions([]), entries ?? new FakeEntries(),
|
tasks ?? new FakeWorkTasks(), sessions ?? new FakeSessions([]), entries ?? new FakeEntries(),
|
||||||
@@ -242,6 +242,26 @@ public sealed class DashboardViewModelTests
|
|||||||
Assert.Contains(vm.SelectedDayEvents, e => e.Kind == CalendarEventKind.Exam && e.Title == "Test");
|
Assert.Contains(vm.SelectedDayEvents, e => e.Kind == CalendarEventKind.Exam && e.Title == "Test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Kalenderauswahl_SortiertKurseDesTagesVorDenAlphabetischenRest()
|
||||||
|
{
|
||||||
|
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||||
|
var selectedDate = today.AddDays(1);
|
||||||
|
var alphabeticallyFirst = new LearningGroup { Name = "10a" };
|
||||||
|
var selectedDayGroup = new LearningGroup { Name = "WAT 10c" };
|
||||||
|
var lessons = new FakeLessons();
|
||||||
|
lessons.Add(new Lesson { GroupId = selectedDayGroup.Id, Date = selectedDate });
|
||||||
|
var vm = BuildVm(alphabeticallyFirst,
|
||||||
|
new Lesson { GroupId = alphabeticallyFirst.Id, Date = today }, lessons: lessons,
|
||||||
|
allGroups: [alphabeticallyFirst, selectedDayGroup]);
|
||||||
|
|
||||||
|
vm.SelectCalendarDayCommand.Execute(vm.CalendarDays.Single(d => d.Date == selectedDate));
|
||||||
|
|
||||||
|
Assert.Equal(selectedDayGroup.Id, vm.CurrentGroups[0].GroupId);
|
||||||
|
Assert.True(vm.CurrentGroups[0].IsOnSelectedDay);
|
||||||
|
Assert.Equal(alphabeticallyFirst.Id, vm.CurrentGroups[1].GroupId);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Kalender_ZeigtMitarbeitssitzungenAlsEigenenTermintyp()
|
public void Kalender_ZeigtMitarbeitssitzungenAlsEigenenTermintyp()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -247,6 +247,25 @@ public sealed class DocumentationDialogViewModelTests
|
|||||||
Assert.Equal(groupId, vm.Result.GroupId);
|
Assert.Equal(groupId, vm.Result.GroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GesamteLerngruppe_SaveSpeichertOhneSchuelerbezug()
|
||||||
|
{
|
||||||
|
var groupId = Guid.NewGuid();
|
||||||
|
var wholeGroup = new StudentOption(Guid.Empty, "Gesamte Lerngruppe");
|
||||||
|
var vm = new DocumentationDialogViewModel(Guid.Empty, null, new FakeAttachmentStorage(),
|
||||||
|
[wholeGroup], groupId)
|
||||||
|
{
|
||||||
|
SelectedStudent = wholeGroup, Title = "Klausur planen", TypeName = "Planung / Erinnerung",
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.SaveCommand.Execute(null);
|
||||||
|
|
||||||
|
Assert.NotNull(vm.Result);
|
||||||
|
Assert.Equal(Guid.Empty, vm.Result!.StudentId);
|
||||||
|
Assert.Equal(groupId, vm.Result.GroupId);
|
||||||
|
Assert.Equal(DocumentationType.Planning, vm.Result.Type);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void MitStudentOptions_Bearbeiten_SchuelerIstVorausgewaehlt()
|
public void MitStudentOptions_Bearbeiten_SchuelerIstVorausgewaehlt()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,6 +29,20 @@ public sealed class ExamsOverviewViewModelTests
|
|||||||
Assert.Equal("Neue Klausur", vm.Rows[1].Title);
|
Assert.Equal("Neue Klausur", vm.Rows[1].Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LeereListe_BietetAbsprungZuLerngruppen()
|
||||||
|
{
|
||||||
|
var vm = BuildVm([], []);
|
||||||
|
var navigated = false;
|
||||||
|
vm.OnNavigateToGroups = () => navigated = true;
|
||||||
|
|
||||||
|
vm.LoadCommand.Execute(null);
|
||||||
|
vm.GoToGroupsCommand.Execute(null);
|
||||||
|
|
||||||
|
Assert.True(vm.HasNoExams);
|
||||||
|
Assert.True(navigated);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Load_ParallelkurseWerdenAlsGeschwisterErkannt()
|
public void Load_ParallelkurseWerdenAlsGeschwisterErkannt()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ public sealed class GlobalSearchViewModelTests
|
|||||||
Assert.Collection(vm.Results,
|
Assert.Collection(vm.Results,
|
||||||
item => Assert.Equal(GlobalSearchAction.NewTask, item.Action),
|
item => Assert.Equal(GlobalSearchAction.NewTask, item.Action),
|
||||||
item => Assert.Equal(GlobalSearchAction.NewReminder, item.Action),
|
item => Assert.Equal(GlobalSearchAction.NewReminder, item.Action),
|
||||||
item => Assert.Equal(GlobalSearchAction.NewStudent, item.Action));
|
item => Assert.Equal(GlobalSearchAction.NewStudent, item.Action),
|
||||||
|
item => Assert.Equal(GlobalSearchAction.NewGroupDocumentation, item.Action));
|
||||||
Assert.Same(vm.Results[0], vm.SelectedResult);
|
Assert.Same(vm.Results[0], vm.SelectedResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +68,36 @@ public sealed class GlobalSearchViewModelTests
|
|||||||
Assert.True(reminder);
|
Assert.True(reminder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Suche_FindetAktiveGruppeUeberFachUndZeigtFachImUntertitel()
|
||||||
|
{
|
||||||
|
var subject = new Subject { Name = "Wirtschaft-Arbeit-Technik", ShortName = "WAT" };
|
||||||
|
var group = new LearningGroup { Name = "10c", SubjectId = subject.Id, SchoolYear = "2026/27", GradeLevel = 10 };
|
||||||
|
var vm = BuildVm(groups: [group], subjects: [subject]);
|
||||||
|
|
||||||
|
vm.Query = "wat";
|
||||||
|
|
||||||
|
var result = Assert.Single(vm.Results, x => x.Kind == GlobalSearchResultKind.Group);
|
||||||
|
Assert.Contains("WAT", result.Subtitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Suche_UeberspringtArchivierteSchuelerUndGruppenSamtDerenKlausuren()
|
||||||
|
{
|
||||||
|
var archivedGroup = new LearningGroup { Name = "Testkurs Vorjahr", IsActive = false };
|
||||||
|
var archivedStudent = new Student { FirstName = "Test", LastName = "Archiv", IsActive = false };
|
||||||
|
var exam = new Exam { GroupId = archivedGroup.Id, Title = "Testklausur" };
|
||||||
|
var vm = BuildVm([archivedStudent], [archivedGroup], [exam]);
|
||||||
|
|
||||||
|
vm.Query = "Test";
|
||||||
|
|
||||||
|
Assert.DoesNotContain(vm.Results, x => x.Kind is GlobalSearchResultKind.Student
|
||||||
|
or GlobalSearchResultKind.Group or GlobalSearchResultKind.Exam);
|
||||||
|
}
|
||||||
|
|
||||||
private static GlobalSearchViewModel BuildVm(List<Student>? students = null,
|
private static GlobalSearchViewModel BuildVm(List<Student>? students = null,
|
||||||
List<LearningGroup>? groups = null, List<Exam>? exams = null, FakeWorkTasks? tasks = null) =>
|
List<LearningGroup>? groups = null, List<Exam>? exams = null, FakeWorkTasks? tasks = null,
|
||||||
|
List<Subject>? subjects = null) =>
|
||||||
new(new FakeStudents(students ?? []), new FakeGroups(groups ?? []),
|
new(new FakeStudents(students ?? []), new FakeGroups(groups ?? []),
|
||||||
new FakeExams(exams ?? []), tasks ?? new FakeWorkTasks());
|
new FakeSubjects(subjects ?? []), new FakeExams(exams ?? []), tasks ?? new FakeWorkTasks());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using LehrerApp.Core.Models;
|
||||||
|
using LehrerApp.Desktop.ViewModels.Students;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace LehrerApp.Desktop.Tests;
|
||||||
|
|
||||||
|
public sealed class GroupDocumentationQuickViewModelTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Save_ErstelltPlanungFuerGesamteLerngruppe()
|
||||||
|
{
|
||||||
|
var group = new GroupDocumentationOption(Guid.NewGuid(), "10c · WAT");
|
||||||
|
var vm = new GroupDocumentationQuickViewModel([group])
|
||||||
|
{
|
||||||
|
SelectedGroup = group, Title = "Klausur ankündigen", Content = "Termin abstimmen",
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.SaveCommand.Execute(null);
|
||||||
|
|
||||||
|
Assert.NotNull(vm.Result);
|
||||||
|
Assert.Equal(Guid.Empty, vm.Result!.StudentId);
|
||||||
|
Assert.Equal(group.Id, vm.Result.GroupId);
|
||||||
|
Assert.Equal(DocumentationType.Planning, vm.Result.Type);
|
||||||
|
Assert.True(vm.Result.ExcludeFromWebUntisSync);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,25 @@ public sealed class GroupDocumentationTabViewModelTests
|
|||||||
Assert.Equal("Alt", vm.Entries[1].Model.Title);
|
Assert.Equal("Alt", vm.Entries[1].Model.Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Initialize_ZeigtGruppenweitenEintragAuchOhneSchueler()
|
||||||
|
{
|
||||||
|
var group = new LearningGroup { Name = "10c" };
|
||||||
|
var docs = new FakeDocumentation();
|
||||||
|
docs.Add(new Documentation
|
||||||
|
{
|
||||||
|
StudentId = Guid.Empty, GroupId = group.Id, Type = DocumentationType.Planning,
|
||||||
|
Title = "Klausur planen", Date = new DateOnly(2026, 9, 1),
|
||||||
|
});
|
||||||
|
|
||||||
|
var vm = BuildVm([], [group], docs);
|
||||||
|
vm.Initialize(group.Id);
|
||||||
|
|
||||||
|
var entry = Assert.Single(vm.Entries);
|
||||||
|
Assert.Equal("Gesamte Lerngruppe", entry.StudentName);
|
||||||
|
Assert.Equal("Planung / Erinnerung", entry.TypeLabel);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Initialize_EintragAusAndererGruppe_WirdMitangezeigtAberAlsFremdMarkiert()
|
public void Initialize_EintragAusAndererGruppe_WirdMitangezeigtAberAlsFremdMarkiert()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -51,6 +51,18 @@ public class StudentListViewModelTests
|
|||||||
Assert.Equal("M", vm.Students.Single(s => s.Id == _ben.Id).AvatarLabel);
|
Assert.Equal("M", vm.Students.Single(s => s.Id == _ben.Id).AvatarLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SucheOhneTreffer_LiefertHilfreichenLeerzustand()
|
||||||
|
{
|
||||||
|
var vm = BuildViewModel();
|
||||||
|
|
||||||
|
vm.SearchText = "nicht vorhanden";
|
||||||
|
|
||||||
|
Assert.True(vm.HasNoStudents);
|
||||||
|
Assert.False(vm.HasStudents);
|
||||||
|
Assert.Contains("Suche", vm.EmptyListMessage);
|
||||||
|
}
|
||||||
|
|
||||||
private StudentListViewModel BuildViewModel() => new(
|
private StudentListViewModel BuildViewModel() => new(
|
||||||
new FakeStudents([_anna, _ben]),
|
new FakeStudents([_anna, _ben]),
|
||||||
new FakeGroups([_group]),
|
new FakeGroups([_group]),
|
||||||
|
|||||||
@@ -7,6 +7,17 @@
|
|||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ResourceInclude Source="avares://LehrerApp.Desktop/Styles/SemanticBrushes.axaml"/>
|
<ResourceInclude Source="avares://LehrerApp.Desktop/Styles/SemanticBrushes.axaml"/>
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
<!-- Plattformunabhängige Vektoricons für die Hauptnavigation (14.10). -->
|
||||||
|
<StreamGeometry x:Key="IconSearch">M10,2 A8,8 0 1 0 10,18 A8,8 0 1 0 10,2 M10,5 A5,5 0 1 1 10,15 A5,5 0 1 1 10,5 M15,14 L22,21 L20,23 L13,16 Z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="IconDashboard">M3,3 H10 V10 H3 Z M14,3 H21 V7 H14 Z M14,11 H21 V21 H14 Z M3,14 H10 V21 H3 Z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="IconGroups">M3,8 L12,3 L21,8 V10 H3 Z M5,11 H8 V18 H5 Z M10,11 H14 V18 H10 Z M16,11 H19 V18 H16 Z M3,20 H21 V22 H3 Z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="IconStudents">M12,3 A4,4 0 1 0 12,11 A4,4 0 1 0 12,3 M4,21 C4,16 7,13 12,13 C17,13 20,16 20,21 Z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="IconExams">M5,2 H15 L20,7 V22 H5 Z M7,5 V19 H18 V9 H13 V4 H7 Z M9,11 H16 V13 H9 Z M9,15 H16 V17 H9 Z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="IconCalendar">M3,4 H21 V22 H3 Z M5,10 V20 H19 V10 Z M7,2 H9 V7 H7 Z M15,2 H17 V7 H15 Z M7,12 H10 V15 H7 Z M12,12 H15 V15 H12 Z M7,17 H10 V19 H7 Z M12,17 H15 V19 H12 Z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="IconClock">M12,2 A10,10 0 1 0 12,22 A10,10 0 1 0 12,2 M12,5 A7,7 0 1 1 12,19 A7,7 0 1 1 12,5 M11,7 H13 V12 L17,14 L16,16 L11,13 Z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="IconClassTeacher">M2,8 L12,3 L22,8 L12,13 Z M6,11 V16 C9,19 15,19 18,16 V11 M22,8 V15</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="IconSettings">M3,5 H21 V7 H3 Z M8,3 A3,3 0 1 0 8,9 A3,3 0 1 0 8,3 M3,11 H21 V13 H3 Z M16,9 A3,3 0 1 0 16,15 A3,3 0 1 0 16,9 M3,17 H21 V19 H3 Z M10,15 A3,3 0 1 0 10,21 A3,3 0 1 0 10,15</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="IconRefresh">M12,3 A9,9 0 0 1 21,12 H18 A6,6 0 0 0 8,7 L11,10 H3 V2 L6,5 A9,9 0 0 1 12,3 M12,21 A9,9 0 0 1 3,12 H6 A6,6 0 0 0 16,17 L13,14 H21 V22 L18,19 A9,9 0 0 1 12,21</StreamGeometry>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
|
|
||||||
@@ -24,5 +35,17 @@
|
|||||||
<Setter Property="Opacity" Value="0.4"/>
|
<Setter Property="Opacity" Value="0.4"/>
|
||||||
<Setter Property="FontSize" Value="13"/>
|
<Setter Property="FontSize" Value="13"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="Button.touchTarget">
|
||||||
|
<Setter Property="MinWidth" Value="40"/>
|
||||||
|
<Setter Property="MinHeight" Value="40"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button:focus-visible">
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColor}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="2"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBox:focus-visible, ComboBox:focus-visible">
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColor}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="2"/>
|
||||||
|
</Style>
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
||||||
|
|||||||
@@ -154,6 +154,9 @@ public class App : Application
|
|||||||
dash.OnNavigateToExam = id => main.NavigateToGroupDetail(id, 4); // Tab "Klausuren"
|
dash.OnNavigateToExam = id => main.NavigateToGroupDetail(id, 4); // Tab "Klausuren"
|
||||||
dash.OnNavigateToUnplannedLesson = id => main.NavigateToGroupDetail(id, 6); // Tab "Planung"
|
dash.OnNavigateToUnplannedLesson = id => main.NavigateToGroupDetail(id, 6); // Tab "Planung"
|
||||||
|
|
||||||
|
var examsOverview = Services.GetRequiredService<ViewModels.Exams.ExamsOverviewViewModel>();
|
||||||
|
examsOverview.OnNavigateToGroups = () => main.NavigateToCommand.Execute(NavItem.Groups);
|
||||||
|
|
||||||
// Globale Suche/Schnellerfassung (14.2): Navigation bleibt im MainWindow-VM, die
|
// Globale Suche/Schnellerfassung (14.2): Navigation bleibt im MainWindow-VM, die
|
||||||
// vorhandenen Dialog-Helfer übernehmen Eingabe und Validierung.
|
// vorhandenen Dialog-Helfer übernehmen Eingabe und Validierung.
|
||||||
var search = Services.GetRequiredService<GlobalSearchViewModel>();
|
var search = Services.GetRequiredService<GlobalSearchViewModel>();
|
||||||
@@ -170,6 +173,7 @@ public class App : Application
|
|||||||
};
|
};
|
||||||
search.OnQuickAddTask = startAsReminder => ShowQuickTaskDialog(startAsReminder, dash);
|
search.OnQuickAddTask = startAsReminder => ShowQuickTaskDialog(startAsReminder, dash);
|
||||||
search.OnQuickAddStudent = ShowAddStudentDialog;
|
search.OnQuickAddStudent = ShowAddStudentDialog;
|
||||||
|
search.OnQuickAddGroupDocumentation = () => ShowQuickGroupDocumentationDialog(dash);
|
||||||
|
|
||||||
// StudentList → StudentDetail + Anlegen
|
// StudentList → StudentDetail + Anlegen
|
||||||
var sl = Services.GetRequiredService<StudentListViewModel>();
|
var sl = Services.GetRequiredService<StudentListViewModel>();
|
||||||
@@ -203,4 +207,26 @@ public class App : Application
|
|||||||
dashboard.RefreshCommand.Execute(null);
|
dashboard.RefreshCommand.Execute(null);
|
||||||
Services.GetRequiredService<WorkTaskListViewModel>().Load();
|
Services.GetRequiredService<WorkTaskListViewModel>().Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task ShowQuickGroupDocumentationDialog(DashboardViewModel dashboard)
|
||||||
|
{
|
||||||
|
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime
|
||||||
|
{ MainWindow: { } owner }) return;
|
||||||
|
|
||||||
|
var subjects = Services.GetRequiredService<ISubjectRepository>().GetAll()
|
||||||
|
.ToDictionary(s => s.Id);
|
||||||
|
var options = Services.GetRequiredService<IGroupRepository>().GetAll()
|
||||||
|
.Where(g => g.IsActive)
|
||||||
|
.OrderBy(g => g.Name)
|
||||||
|
.Select(g => new GroupDocumentationOption(g.Id,
|
||||||
|
g.SubjectId is { } subjectId && subjects.TryGetValue(subjectId, out var subject)
|
||||||
|
? $"{g.Name} · {(string.IsNullOrWhiteSpace(subject.ShortName) ? subject.Name : subject.ShortName)}"
|
||||||
|
: g.Name));
|
||||||
|
var vm = new GroupDocumentationQuickViewModel(options);
|
||||||
|
var dialog = new Views.Students.GroupDocumentationQuickDialog { DataContext = vm };
|
||||||
|
if (!await dialog.ShowDialog<bool>(owner) || vm.Result is null) return;
|
||||||
|
|
||||||
|
Services.GetRequiredService<IDocumentationRepository>().Save(vm.Result);
|
||||||
|
dashboard.RefreshCommand.Execute(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ public partial class DashboardViewModel : ObservableObject
|
|||||||
[ObservableProperty] private string _currentSchoolYear = "";
|
[ObservableProperty] private string _currentSchoolYear = "";
|
||||||
[ObservableProperty] private DateOnly _calendarMonth = FirstOfMonth(DateTime.Today);
|
[ObservableProperty] private DateOnly _calendarMonth = FirstOfMonth(DateTime.Today);
|
||||||
[ObservableProperty] private string _selectedDayLabel = "";
|
[ObservableProperty] private string _selectedDayLabel = "";
|
||||||
|
[ObservableProperty] private DateOnly _selectedCalendarDate = DateOnly.FromDateTime(DateTime.Today);
|
||||||
[ObservableProperty] private bool _isDashboardSettingsOpen;
|
[ObservableProperty] private bool _isDashboardSettingsOpen;
|
||||||
[ObservableProperty] private bool _isWeatherPanelVisible;
|
[ObservableProperty] private bool _isWeatherPanelVisible;
|
||||||
[ObservableProperty] private string _weatherSummary = "";
|
[ObservableProperty] private string _weatherSummary = "";
|
||||||
@@ -196,7 +197,7 @@ public partial class DashboardViewModel : ObservableObject
|
|||||||
IsHighPriority = t.Priority == TaskPriority.High });
|
IsHighPriority = t.Priority == TaskPriority.High });
|
||||||
|
|
||||||
CurrentGroups.Clear();
|
CurrentGroups.Clear();
|
||||||
foreach (var g in groups.Values.OrderBy(g => g.Name))
|
foreach (var g in groups.Values)
|
||||||
CurrentGroups.Add(new()
|
CurrentGroups.Add(new()
|
||||||
{
|
{
|
||||||
GroupId = g.Id,
|
GroupId = g.Id,
|
||||||
@@ -749,9 +750,35 @@ public partial class DashboardViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
if (day is null) return;
|
if (day is null) return;
|
||||||
foreach (var cell in CalendarDays) cell.IsSelected = cell == day;
|
foreach (var cell in CalendarDays) cell.IsSelected = cell == day;
|
||||||
|
SelectedCalendarDate = day.Date;
|
||||||
SelectedDayLabel = day.Date.ToString("dddd, d. MMMM", De);
|
SelectedDayLabel = day.Date.ToString("dddd, d. MMMM", De);
|
||||||
SelectedDayEvents.Clear();
|
SelectedDayEvents.Clear();
|
||||||
foreach (var item in day.Events) SelectedDayEvents.Add(item);
|
foreach (var item in day.Events) SelectedDayEvents.Add(item);
|
||||||
|
SortCurrentGroups(day.Date);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SortCurrentGroups(DateOnly date)
|
||||||
|
{
|
||||||
|
var schoolHolidays = _schoolHolidays.GetAll();
|
||||||
|
var publicHolidays = _publicHolidays.GetHolidays(date.Year, _calendarSettings.State)
|
||||||
|
.Select(h => h.Date).ToHashSet();
|
||||||
|
var isFreeDay = IsFreeDay(date, schoolHolidays, publicHolidays);
|
||||||
|
var cancelledPeriods = _substitutions.GetByDate(date)
|
||||||
|
.Where(s => s.Kind == SubstitutionKind.Cancelled)
|
||||||
|
.Select(s => s.PeriodNumber).ToHashSet();
|
||||||
|
|
||||||
|
foreach (var chip in CurrentGroups)
|
||||||
|
{
|
||||||
|
var hasLesson = _lessons.GetByGroupAndDate(chip.GroupId, date).Count > 0;
|
||||||
|
var hasActiveSlot = !isFreeDay && _timetableSlots.GetByGroup(chip.GroupId)
|
||||||
|
.Any(s => s.Weekday == date.DayOfWeek && !cancelledPeriods.Contains(s.PeriodNumber));
|
||||||
|
chip.IsOnSelectedDay = hasLesson || hasActiveSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sorted = CurrentGroups.OrderByDescending(g => g.IsOnSelectedDay)
|
||||||
|
.ThenBy(g => g.Name, StringComparer.CurrentCultureIgnoreCase).ToList();
|
||||||
|
CurrentGroups.Clear();
|
||||||
|
foreach (var chip in sorted) CurrentGroups.Add(chip);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -891,7 +918,13 @@ public class LessonItem
|
|||||||
public bool HasRoom => !string.IsNullOrWhiteSpace(Room);
|
public bool HasRoom => !string.IsNullOrWhiteSpace(Room);
|
||||||
}
|
}
|
||||||
public class TaskItem { public string Title { get; set; } = ""; public string DueDate { get; set; } = ""; public bool IsOverdue { get; set; } public bool IsReminder { get; set; } public bool IsHighPriority { get; set; } }
|
public class TaskItem { public string Title { get; set; } = ""; public string DueDate { get; set; } = ""; public bool IsOverdue { get; set; } public bool IsReminder { get; set; } public bool IsHighPriority { get; set; } }
|
||||||
public class GroupChip { public Guid GroupId { get; set; } public string Name { get; set; } = ""; public string Subject { get; set; } = ""; }
|
public class GroupChip
|
||||||
|
{
|
||||||
|
public Guid GroupId { get; set; }
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public string Subject { get; set; } = "";
|
||||||
|
public bool IsOnSelectedDay { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
// ── Offene Entschuldigungen (aus Mitarbeit-Fehltagen) ────────────────────────
|
// ── Offene Entschuldigungen (aus Mitarbeit-Fehltagen) ────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ public partial class ExamsOverviewViewModel : ObservableObject
|
|||||||
public Func<Exam, Task>? OnGradeExam { get; set; }
|
public Func<Exam, Task>? OnGradeExam { get; set; }
|
||||||
public Func<Exam, Task>? OnEvaluateExam { get; set; }
|
public Func<Exam, Task>? OnEvaluateExam { get; set; }
|
||||||
public Action<Guid>? OnNavigateToGroup { get; set; }
|
public Action<Guid>? OnNavigateToGroup { get; set; }
|
||||||
|
public Action? OnNavigateToGroups { get; set; }
|
||||||
|
public bool HasNoExams => Rows.Count == 0;
|
||||||
|
|
||||||
public ExamsOverviewViewModel(IExamRepository exams, IExamResultRepository examResults,
|
public ExamsOverviewViewModel(IExamRepository exams, IExamResultRepository examResults,
|
||||||
IGroupRepository groups, IGroupMembershipRepository memberships, GradingService grading,
|
IGroupRepository groups, IGroupMembershipRepository memberships, GradingService grading,
|
||||||
@@ -85,6 +87,7 @@ public partial class ExamsOverviewViewModel : ObservableObject
|
|||||||
Rows.Add(row);
|
Rows.Add(row);
|
||||||
|
|
||||||
EmptyHint = Rows.Count == 0 ? "Keine Klausuren angelegt." : "";
|
EmptyHint = Rows.Count == 0 ? "Keine Klausuren angelegt." : "";
|
||||||
|
OnPropertyChanged(nameof(HasNoExams));
|
||||||
SelectedRow = selectedId is { } id
|
SelectedRow = selectedId is { } id
|
||||||
? Rows.FirstOrDefault(r => r.Exam.Id == id) ?? Rows.FirstOrDefault()
|
? Rows.FirstOrDefault(r => r.Exam.Id == id) ?? Rows.FirstOrDefault()
|
||||||
: Rows.FirstOrDefault();
|
: Rows.FirstOrDefault();
|
||||||
@@ -173,6 +176,9 @@ public partial class ExamsOverviewViewModel : ObservableObject
|
|||||||
OnNavigateToGroup?.Invoke(SelectedRow.GroupId);
|
OnNavigateToGroup?.Invoke(SelectedRow.GroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void GoToGroups() => OnNavigateToGroups?.Invoke();
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void ToggleApproval() => ToggleDate(SelectedRow?.Exam, e => e.ApprovalGrantedAt,
|
private void ToggleApproval() => ToggleDate(SelectedRow?.Exam, e => e.ApprovalGrantedAt,
|
||||||
(e, v) => e.ApprovalGrantedAt = v);
|
(e, v) => e.ApprovalGrantedAt = v);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public partial class GlobalSearchViewModel : ObservableObject
|
|||||||
|
|
||||||
private readonly IStudentRepository _students;
|
private readonly IStudentRepository _students;
|
||||||
private readonly IGroupRepository _groups;
|
private readonly IGroupRepository _groups;
|
||||||
|
private readonly ISubjectRepository _subjects;
|
||||||
private readonly IExamRepository _exams;
|
private readonly IExamRepository _exams;
|
||||||
private readonly IWorkTaskRepository _tasks;
|
private readonly IWorkTaskRepository _tasks;
|
||||||
|
|
||||||
@@ -30,13 +31,15 @@ public partial class GlobalSearchViewModel : ObservableObject
|
|||||||
public Action<GlobalSearchResult>? OnNavigate { get; set; }
|
public Action<GlobalSearchResult>? OnNavigate { get; set; }
|
||||||
public Func<bool, Task>? OnQuickAddTask { get; set; }
|
public Func<bool, Task>? OnQuickAddTask { get; set; }
|
||||||
public Func<Task>? OnQuickAddStudent { get; set; }
|
public Func<Task>? OnQuickAddStudent { get; set; }
|
||||||
|
public Func<Task>? OnQuickAddGroupDocumentation { get; set; }
|
||||||
public Action? OnClose { get; set; }
|
public Action? OnClose { get; set; }
|
||||||
|
|
||||||
public GlobalSearchViewModel(IStudentRepository students, IGroupRepository groups,
|
public GlobalSearchViewModel(IStudentRepository students, IGroupRepository groups,
|
||||||
IExamRepository exams, IWorkTaskRepository tasks)
|
ISubjectRepository subjects, IExamRepository exams, IWorkTaskRepository tasks)
|
||||||
{
|
{
|
||||||
_students = students;
|
_students = students;
|
||||||
_groups = groups;
|
_groups = groups;
|
||||||
|
_subjects = subjects;
|
||||||
_exams = exams;
|
_exams = exams;
|
||||||
_tasks = tasks;
|
_tasks = tasks;
|
||||||
RefreshResults();
|
RefreshResults();
|
||||||
@@ -59,23 +62,34 @@ public partial class GlobalSearchViewModel : ObservableObject
|
|||||||
|
|
||||||
if (query.Length > 0)
|
if (query.Length > 0)
|
||||||
{
|
{
|
||||||
var groups = _groups.GetAll(includeInactive: true);
|
var groups = _groups.GetAll().Where(g => g.IsActive).ToList();
|
||||||
var groupNames = groups.ToDictionary(g => g.Id, g => g.Name);
|
var groupNames = groups.ToDictionary(g => g.Id, g => g.Name);
|
||||||
|
var subjects = _subjects.GetAll().ToDictionary(s => s.Id);
|
||||||
|
Subject? GroupSubject(LearningGroup group) => group.SubjectId is { } subjectId
|
||||||
|
? subjects.GetValueOrDefault(subjectId)
|
||||||
|
: null;
|
||||||
|
static string SubjectLabel(Subject? subject) => subject is null ? ""
|
||||||
|
: string.IsNullOrWhiteSpace(subject.ShortName)
|
||||||
|
|| subject.Name.Equals(subject.ShortName, StringComparison.CurrentCultureIgnoreCase)
|
||||||
|
? subject.Name
|
||||||
|
: $"{subject.ShortName} – {subject.Name}";
|
||||||
var candidates = new List<GlobalSearchResult>();
|
var candidates = new List<GlobalSearchResult>();
|
||||||
|
|
||||||
candidates.AddRange(_students.GetAll(includeInactive: true)
|
candidates.AddRange(_students.GetAll().Where(s => s.IsActive)
|
||||||
.Where(s => Matches(s.FullName, query))
|
.Where(s => Matches(s.FullName, query))
|
||||||
.Select(s => GlobalSearchResult.ForStudent(s)));
|
.Select(s => GlobalSearchResult.ForStudent(s)));
|
||||||
|
|
||||||
candidates.AddRange(groups
|
candidates.AddRange(groups
|
||||||
.Where(g => Matches($"{g.Name} {g.SchoolYear} {g.GradeLevel}", query))
|
.Where(g => Matches($"{g.Name} {GroupSubject(g)?.Name} {GroupSubject(g)?.ShortName} {g.SchoolYear} {g.GradeLevel}", query))
|
||||||
.Select(GlobalSearchResult.ForGroup));
|
.Select(g => GlobalSearchResult.ForGroup(g, SubjectLabel(GroupSubject(g)))));
|
||||||
|
|
||||||
candidates.AddRange(_exams.GetAll()
|
candidates.AddRange(_exams.GetAll()
|
||||||
|
.Where(e => groupNames.ContainsKey(e.GroupId))
|
||||||
.Where(e => Matches($"{e.Title} {groupNames.GetValueOrDefault(e.GroupId)}", query))
|
.Where(e => Matches($"{e.Title} {groupNames.GetValueOrDefault(e.GroupId)}", query))
|
||||||
.Select(e => GlobalSearchResult.ForExam(e, groupNames.GetValueOrDefault(e.GroupId) ?? "")));
|
.Select(e => GlobalSearchResult.ForExam(e, groupNames.GetValueOrDefault(e.GroupId) ?? "")));
|
||||||
|
|
||||||
candidates.AddRange(_tasks.GetAll()
|
candidates.AddRange(_tasks.GetAll()
|
||||||
|
.Where(t => t.GroupId is null || groupNames.ContainsKey(t.GroupId.Value))
|
||||||
.Where(t => Matches($"{t.Title} {t.Notes} {groupNames.GetValueOrDefault(t.GroupId ?? Guid.Empty)}", query))
|
.Where(t => Matches($"{t.Title} {t.Notes} {groupNames.GetValueOrDefault(t.GroupId ?? Guid.Empty)}", query))
|
||||||
.Select(t => GlobalSearchResult.ForTask(t, groupNames.GetValueOrDefault(t.GroupId ?? Guid.Empty) ?? "")));
|
.Select(t => GlobalSearchResult.ForTask(t, groupNames.GetValueOrDefault(t.GroupId ?? Guid.Empty) ?? "")));
|
||||||
|
|
||||||
@@ -99,6 +113,7 @@ public partial class GlobalSearchViewModel : ObservableObject
|
|||||||
GlobalSearchResult.ForAction(GlobalSearchAction.NewTask, "Aufgabe anlegen", "Mit Fälligkeit, Gruppe und Priorität", "+"),
|
GlobalSearchResult.ForAction(GlobalSearchAction.NewTask, "Aufgabe anlegen", "Mit Fälligkeit, Gruppe und Priorität", "+"),
|
||||||
GlobalSearchResult.ForAction(GlobalSearchAction.NewReminder, "Erinnerung anlegen", "Kurze Notiz ohne Zeiterfassung", "◷"),
|
GlobalSearchResult.ForAction(GlobalSearchAction.NewReminder, "Erinnerung anlegen", "Kurze Notiz ohne Zeiterfassung", "◷"),
|
||||||
GlobalSearchResult.ForAction(GlobalSearchAction.NewStudent, "Schüler anlegen", "Neue Stammdaten erfassen", "+"),
|
GlobalSearchResult.ForAction(GlobalSearchAction.NewStudent, "Schüler anlegen", "Neue Stammdaten erfassen", "+"),
|
||||||
|
GlobalSearchResult.ForAction(GlobalSearchAction.NewGroupDocumentation, "Lerngruppen-Eintrag", "Planung, Klausur oder Erinnerung dokumentieren", "N"),
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var action in actions.Where(a => query.Length == 0 || Matches(a.Title, query)))
|
foreach (var action in actions.Where(a => query.Length == 0 || Matches(a.Title, query)))
|
||||||
@@ -124,6 +139,9 @@ public partial class GlobalSearchViewModel : ObservableObject
|
|||||||
case GlobalSearchAction.NewStudent:
|
case GlobalSearchAction.NewStudent:
|
||||||
if (OnQuickAddStudent is not null) await OnQuickAddStudent();
|
if (OnQuickAddStudent is not null) await OnQuickAddStudent();
|
||||||
break;
|
break;
|
||||||
|
case GlobalSearchAction.NewGroupDocumentation:
|
||||||
|
if (OnQuickAddGroupDocumentation is not null) await OnQuickAddGroupDocumentation();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
OnNavigate?.Invoke(result);
|
OnNavigate?.Invoke(result);
|
||||||
break;
|
break;
|
||||||
@@ -134,7 +152,7 @@ public partial class GlobalSearchViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum GlobalSearchResultKind { Action, Student, Group, Exam, Task }
|
public enum GlobalSearchResultKind { Action, Student, Group, Exam, Task }
|
||||||
public enum GlobalSearchAction { None, NewTask, NewReminder, NewStudent }
|
public enum GlobalSearchAction { None, NewTask, NewReminder, NewStudent, NewGroupDocumentation }
|
||||||
|
|
||||||
public sealed class GlobalSearchResult
|
public sealed class GlobalSearchResult
|
||||||
{
|
{
|
||||||
@@ -171,10 +189,13 @@ public sealed class GlobalSearchResult
|
|||||||
Title = student.FullName, Subtitle = student.IsActive ? "Aktiv" : "Inaktiv", Icon = "P",
|
Title = student.FullName, Subtitle = student.IsActive ? "Aktiv" : "Inaktiv", Icon = "P",
|
||||||
};
|
};
|
||||||
|
|
||||||
public static GlobalSearchResult ForGroup(LearningGroup group) => new()
|
public static GlobalSearchResult ForGroup(LearningGroup group, string subjectName) => new()
|
||||||
{
|
{
|
||||||
Kind = GlobalSearchResultKind.Group, EntityId = group.Id, GroupId = group.Id,
|
Kind = GlobalSearchResultKind.Group, EntityId = group.Id, GroupId = group.Id,
|
||||||
Title = group.Name, Subtitle = $"{group.SchoolYear} · Stufe {group.GradeLevel}", Icon = "G",
|
Title = group.Name,
|
||||||
|
Subtitle = string.Join(" · ", new[] { subjectName, group.SchoolYear, $"Stufe {group.GradeLevel}" }
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x))),
|
||||||
|
Icon = "G",
|
||||||
};
|
};
|
||||||
|
|
||||||
public static GlobalSearchResult ForExam(Exam exam, string groupName) => new()
|
public static GlobalSearchResult ForExam(Exam exam, string groupName) => new()
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public partial class GroupDocumentationTabViewModel : ObservableObject
|
|||||||
private List<StudentOption> _groupStudents = [];
|
private List<StudentOption> _groupStudents = [];
|
||||||
|
|
||||||
public static readonly StudentOption AllStudentsOption = new(Guid.Empty, "Alle Schüler");
|
public static readonly StudentOption AllStudentsOption = new(Guid.Empty, "Alle Schüler");
|
||||||
|
public static readonly StudentOption WholeGroupOption = new(Guid.Empty, "Gesamte Lerngruppe");
|
||||||
|
|
||||||
public ObservableCollection<DocumentationItem> Entries { get; } = [];
|
public ObservableCollection<DocumentationItem> Entries { get; } = [];
|
||||||
public ObservableCollection<StudentOption> StudentFilterOptions { get; } = [AllStudentsOption];
|
public ObservableCollection<StudentOption> StudentFilterOptions { get; } = [AllStudentsOption];
|
||||||
@@ -62,8 +63,6 @@ public partial class GroupDocumentationTabViewModel : ObservableObject
|
|||||||
private void Load()
|
private void Load()
|
||||||
{
|
{
|
||||||
Entries.Clear();
|
Entries.Clear();
|
||||||
if (_groupStudents.Count == 0) return;
|
|
||||||
|
|
||||||
var studentNameById = _groupStudents.ToDictionary(s => s.Id, s => s.Name);
|
var studentNameById = _groupStudents.ToDictionary(s => s.Id, s => s.Name);
|
||||||
var groupNameCache = new Dictionary<Guid, string>();
|
var groupNameCache = new Dictionary<Guid, string>();
|
||||||
string GroupLabel(Guid id)
|
string GroupLabel(Guid id)
|
||||||
@@ -80,6 +79,10 @@ public partial class GroupDocumentationTabViewModel : ObservableObject
|
|||||||
|
|
||||||
var all = relevantStudentIds
|
var all = relevantStudentIds
|
||||||
.SelectMany(id => _docs.GetByStudent(id))
|
.SelectMany(id => _docs.GetByStudent(id))
|
||||||
|
.Concat(SelectedStudentFilter.Id == Guid.Empty
|
||||||
|
? _docs.GetAll().Where(d => d.StudentId == Guid.Empty && d.GroupId == _groupId)
|
||||||
|
: [])
|
||||||
|
.DistinctBy(d => d.Id)
|
||||||
.Where(d => !OnlyThisGroup || d.GroupId == _groupId)
|
.Where(d => !OnlyThisGroup || d.GroupId == _groupId)
|
||||||
.OrderByDescending(d => d.IsDraft)
|
.OrderByDescending(d => d.IsDraft)
|
||||||
.ThenByDescending(d => d.Date)
|
.ThenByDescending(d => d.Date)
|
||||||
@@ -90,7 +93,10 @@ public partial class GroupDocumentationTabViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
var isOwnGroup = d.GroupId is null || d.GroupId == _groupId;
|
var isOwnGroup = d.GroupId is null || d.GroupId == _groupId;
|
||||||
var otherGroupLabel = isOwnGroup ? "" : GroupLabel(d.GroupId!.Value);
|
var otherGroupLabel = isOwnGroup ? "" : GroupLabel(d.GroupId!.Value);
|
||||||
Entries.Add(new DocumentationItem(d, studentNameById.GetValueOrDefault(d.StudentId, ""),
|
var studentName = d.StudentId == Guid.Empty
|
||||||
|
? WholeGroupOption.Name
|
||||||
|
: studentNameById.GetValueOrDefault(d.StudentId, "");
|
||||||
|
Entries.Add(new DocumentationItem(d, studentName,
|
||||||
isOwnGroup, otherGroupLabel));
|
isOwnGroup, otherGroupLabel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +106,7 @@ public partial class GroupDocumentationTabViewModel : ObservableObject
|
|||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task AddDocumentation()
|
private async Task AddDocumentation()
|
||||||
{
|
{
|
||||||
if (OnEditDocumentation is null || _groupStudents.Count == 0) return;
|
if (OnEditDocumentation is null) return;
|
||||||
var result = await OnEditDocumentation(_groupId, _groupStudents, null);
|
var result = await OnEditDocumentation(_groupId, _groupStudents, null);
|
||||||
if (result is null) return;
|
if (result is null) return;
|
||||||
_docs.Save(result);
|
_docs.Save(result);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public record StudentOption(Guid Id, string Name);
|
|||||||
public static class DocumentationTypeDisplay
|
public static class DocumentationTypeDisplay
|
||||||
{
|
{
|
||||||
public static string[] Options { get; } =
|
public static string[] Options { get; } =
|
||||||
["Gespräch", "Vorkommnis", "Förderplan", "Fehlzeit", "Elternanruf", "Elternbrief"];
|
["Gespräch", "Vorkommnis", "Förderplan", "Fehlzeit", "Elternanruf", "Elternbrief", "Planung / Erinnerung"];
|
||||||
|
|
||||||
public static string Label(DocumentationType t) => t switch
|
public static string Label(DocumentationType t) => t switch
|
||||||
{
|
{
|
||||||
@@ -27,6 +27,7 @@ public static class DocumentationTypeDisplay
|
|||||||
DocumentationType.Absence => "Fehlzeit",
|
DocumentationType.Absence => "Fehlzeit",
|
||||||
DocumentationType.ParentCall => "Elternanruf",
|
DocumentationType.ParentCall => "Elternanruf",
|
||||||
DocumentationType.ParentLetter => "Elternbrief",
|
DocumentationType.ParentLetter => "Elternbrief",
|
||||||
|
DocumentationType.Planning => "Planung / Erinnerung",
|
||||||
_ => "",
|
_ => "",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ public static class DocumentationTypeDisplay
|
|||||||
"Fehlzeit" => DocumentationType.Absence,
|
"Fehlzeit" => DocumentationType.Absence,
|
||||||
"Elternanruf" => DocumentationType.ParentCall,
|
"Elternanruf" => DocumentationType.ParentCall,
|
||||||
"Elternbrief" => DocumentationType.ParentLetter,
|
"Elternbrief" => DocumentationType.ParentLetter,
|
||||||
|
"Planung / Erinnerung" => DocumentationType.Planning,
|
||||||
_ => DocumentationType.Conversation,
|
_ => DocumentationType.Conversation,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -301,7 +303,7 @@ public partial class DocumentationDialogViewModel : ObservableObject
|
|||||||
LetterSentDateError = ""; LetterResponseDateError = "";
|
LetterSentDateError = ""; LetterResponseDateError = "";
|
||||||
var valid = true;
|
var valid = true;
|
||||||
|
|
||||||
if (CanPickStudent && SelectedStudent is null) { StudentError = "Schüler auswählen."; valid = false; }
|
if (CanPickStudent && SelectedStudent is null) { StudentError = "Bezug auswählen."; valid = false; }
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(Title)) { TitleError = "Titel erforderlich."; valid = false; }
|
if (string.IsNullOrWhiteSpace(Title)) { TitleError = "Titel erforderlich."; valid = false; }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using LehrerApp.Core.Models;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace LehrerApp.Desktop.ViewModels.Students;
|
||||||
|
|
||||||
|
public sealed record GroupDocumentationOption(Guid Id, string DisplayName);
|
||||||
|
|
||||||
|
/// Kompakte Erfassung einer gruppenweiten Planungsnotiz aus der globalen Befehlspalette.
|
||||||
|
public partial class GroupDocumentationQuickViewModel(IEnumerable<GroupDocumentationOption> groups)
|
||||||
|
: ObservableObject
|
||||||
|
{
|
||||||
|
public List<GroupDocumentationOption> Groups { get; } = groups.ToList();
|
||||||
|
[ObservableProperty] private GroupDocumentationOption? _selectedGroup;
|
||||||
|
[ObservableProperty] private string _title = "";
|
||||||
|
[ObservableProperty] private string _content = "";
|
||||||
|
[ObservableProperty] private string _dateText = DateOnly.FromDateTime(DateTime.Today).ToString("dd.MM.yyyy");
|
||||||
|
[ObservableProperty] private string _groupError = "";
|
||||||
|
[ObservableProperty] private string _titleError = "";
|
||||||
|
[ObservableProperty] private string _dateError = "";
|
||||||
|
|
||||||
|
public Documentation? Result { get; private set; }
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void Save()
|
||||||
|
{
|
||||||
|
GroupError = TitleError = DateError = "";
|
||||||
|
if (SelectedGroup is null) GroupError = "Lerngruppe auswählen.";
|
||||||
|
if (string.IsNullOrWhiteSpace(Title)) TitleError = "Titel erforderlich.";
|
||||||
|
if (!DateOnly.TryParseExact(DateText, "dd.MM.yyyy", null, DateTimeStyles.None, out var date))
|
||||||
|
DateError = "Format TT.MM.JJJJ.";
|
||||||
|
if (GroupError.Length > 0 || TitleError.Length > 0 || DateError.Length > 0) return;
|
||||||
|
|
||||||
|
Result = new Documentation
|
||||||
|
{
|
||||||
|
StudentId = Guid.Empty,
|
||||||
|
GroupId = SelectedGroup!.Id,
|
||||||
|
Type = DocumentationType.Planning,
|
||||||
|
Date = date,
|
||||||
|
Title = Title.Trim(),
|
||||||
|
Content = Content.Trim(),
|
||||||
|
ExcludeFromWebUntisSync = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,9 +64,15 @@ public partial class StudentListViewModel : ObservableObject
|
|||||||
[ObservableProperty] private string _searchText = "";
|
[ObservableProperty] private string _searchText = "";
|
||||||
[ObservableProperty] private bool _showInactive;
|
[ObservableProperty] private bool _showInactive;
|
||||||
[ObservableProperty] private StudentListItem? _selectedStudent;
|
[ObservableProperty] private StudentListItem? _selectedStudent;
|
||||||
|
[ObservableProperty] private bool _isImporting;
|
||||||
|
|
||||||
public ObservableCollection<StudentListItem> Students { get; } = [];
|
public ObservableCollection<StudentListItem> Students { get; } = [];
|
||||||
public string CountSummary => $"{Students.Count} Schüler gesamt";
|
public string CountSummary => $"{Students.Count} Schüler gesamt";
|
||||||
|
public bool HasNoStudents => Students.Count == 0;
|
||||||
|
public bool HasStudents => !HasNoStudents;
|
||||||
|
public string EmptyListMessage => string.IsNullOrWhiteSpace(SearchText)
|
||||||
|
? ShowInactive ? "Noch keine Schüler vorhanden." : "Noch keine aktiven Schüler vorhanden."
|
||||||
|
: "Keine Schüler passen zur aktuellen Suche.";
|
||||||
|
|
||||||
public StudentListViewModel(IStudentRepository students, IGroupRepository groups,
|
public StudentListViewModel(IStudentRepository students, IGroupRepository groups,
|
||||||
IGroupMembershipRepository memberships)
|
IGroupMembershipRepository memberships)
|
||||||
@@ -105,6 +111,9 @@ public partial class StudentListViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
foreach (var s in f) Students.Add(new StudentListItem(s));
|
foreach (var s in f) Students.Add(new StudentListItem(s));
|
||||||
OnPropertyChanged(nameof(CountSummary));
|
OnPropertyChanged(nameof(CountSummary));
|
||||||
|
OnPropertyChanged(nameof(HasNoStudents));
|
||||||
|
OnPropertyChanged(nameof(HasStudents));
|
||||||
|
OnPropertyChanged(nameof(EmptyListMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<Task>? OnAddStudent { get; set; }
|
public Func<Task>? OnAddStudent { get; set; }
|
||||||
|
|||||||
@@ -20,17 +20,17 @@
|
|||||||
<StackPanel Spacing="20">
|
<StackPanel Spacing="20">
|
||||||
|
|
||||||
<!-- Begrüßung -->
|
<!-- Begrüßung -->
|
||||||
<Grid ColumnDefinitions="*,Auto">
|
<Grid RowDefinitions="Auto,Auto">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="{Binding Greeting}" FontSize="14" Opacity="0.6"/>
|
<TextBlock Text="{Binding Greeting}" FontSize="14" Opacity="0.6"/>
|
||||||
<TextBlock Text="{Binding CurrentDate}" FontSize="24" FontWeight="SemiBold"/>
|
<TextBlock Text="{Binding CurrentDate}" FontSize="24" FontWeight="SemiBold"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
<WrapPanel Grid.Row="1" Orientation="Horizontal" ItemSpacing="8" LineSpacing="8" Margin="0,10,0,0">
|
||||||
<Button Content="Klassenbuch abgleichen…" Click="OnCompareWebUntisDocumentationClick"
|
<Button Content="Klassenbuch abgleichen…" Click="OnCompareWebUntisDocumentationClick"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<Button Content="Bereiche anpassen" Command="{Binding ToggleDashboardSettingsCommand}"
|
<Button Content="Bereiche anpassen" Command="{Binding ToggleDashboardSettingsCommand}"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
</StackPanel>
|
</WrapPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Der Tagesfokus beantwortet zuerst die vier Fragen, die beim Öffnen der App zählen:
|
<!-- Der Tagesfokus beantwortet zuerst die vier Fragen, die beim Öffnen der App zählen:
|
||||||
@@ -72,9 +72,13 @@
|
|||||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
<CheckBox Content="{Binding Title}" IsChecked="{Binding IsVisible}" VerticalAlignment="Center"/>
|
<CheckBox Content="{Binding Title}" IsChecked="{Binding IsVisible}" VerticalAlignment="Center"/>
|
||||||
<Button Content="↑" Padding="6,2"
|
<Button Content="↑" Padding="6,2"
|
||||||
|
ToolTip.Tip="Bereich nach oben verschieben"
|
||||||
|
AutomationProperties.Name="Dashboard-Bereich nach oben verschieben"
|
||||||
Command="{Binding $parent[ItemsControl].((vm:DashboardViewModel)DataContext).MoveCardUpCommand}"
|
Command="{Binding $parent[ItemsControl].((vm:DashboardViewModel)DataContext).MoveCardUpCommand}"
|
||||||
CommandParameter="{Binding}"/>
|
CommandParameter="{Binding}"/>
|
||||||
<Button Content="↓" Padding="6,2"
|
<Button Content="↓" Padding="6,2"
|
||||||
|
ToolTip.Tip="Bereich nach unten verschieben"
|
||||||
|
AutomationProperties.Name="Dashboard-Bereich nach unten verschieben"
|
||||||
Command="{Binding $parent[ItemsControl].((vm:DashboardViewModel)DataContext).MoveCardDownCommand}"
|
Command="{Binding $parent[ItemsControl].((vm:DashboardViewModel)DataContext).MoveCardDownCommand}"
|
||||||
CommandParameter="{Binding}"/>
|
CommandParameter="{Binding}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -250,8 +254,10 @@
|
|||||||
Width="130" Margin="12,0"/>
|
Width="130" Margin="12,0"/>
|
||||||
<Button Grid.Column="2" Content="Heute" FontSize="11" Padding="8,2"
|
<Button Grid.Column="2" Content="Heute" FontSize="11" Padding="8,2"
|
||||||
Command="{Binding CalendarTodayCommand}" Margin="0,0,4,0"/>
|
Command="{Binding CalendarTodayCommand}" Margin="0,0,4,0"/>
|
||||||
<Button Grid.Column="3" Content="‹" Padding="8,2" Command="{Binding PrevMonthCommand}"/>
|
<Button Grid.Column="3" Content="‹" Padding="8,2" Command="{Binding PrevMonthCommand}"
|
||||||
<Button Grid.Column="4" Content="›" Padding="8,2" Command="{Binding NextMonthCommand}" Margin="4,0,0,0"/>
|
ToolTip.Tip="Vorheriger Monat" AutomationProperties.Name="Vorheriger Monat"/>
|
||||||
|
<Button Grid.Column="4" Content="›" Padding="8,2" Command="{Binding NextMonthCommand}" Margin="4,0,0,0"
|
||||||
|
ToolTip.Tip="Nächster Monat" AutomationProperties.Name="Nächster Monat"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<ItemsControl ItemsSource="{Binding CalendarWeekdayHeaders}">
|
<ItemsControl ItemsSource="{Binding CalendarWeekdayHeaders}">
|
||||||
@@ -586,10 +592,10 @@
|
|||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="MEINE LERNGRUPPEN" FontSize="11" FontWeight="Bold"
|
<TextBlock Text="MEINE LERNGRUPPEN" FontSize="11" FontWeight="Bold"
|
||||||
Opacity="0.5" Margin="0,0,0,10"/>
|
Opacity="0.5" Margin="0,0,0,10"/>
|
||||||
<ItemsControl ItemsSource="{Binding CurrentGroups}">
|
<ItemsControl ItemsSource="{Binding CurrentGroups}" HorizontalAlignment="Stretch">
|
||||||
<ItemsControl.ItemsPanel>
|
<ItemsControl.ItemsPanel>
|
||||||
<ItemsPanelTemplate>
|
<ItemsPanelTemplate>
|
||||||
<WrapPanel Orientation="Horizontal"/>
|
<WrapPanel Orientation="Horizontal" ItemSpacing="8" LineSpacing="8"/>
|
||||||
</ItemsPanelTemplate>
|
</ItemsPanelTemplate>
|
||||||
</ItemsControl.ItemsPanel>
|
</ItemsControl.ItemsPanel>
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
@@ -597,9 +603,15 @@
|
|||||||
<Button Command="{Binding $parent[ItemsControl].((vm:DashboardViewModel)DataContext).OpenGroupCommand}"
|
<Button Command="{Binding $parent[ItemsControl].((vm:DashboardViewModel)DataContext).OpenGroupCommand}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Background="{DynamicResource SystemAccentColorLight2}"
|
Background="{DynamicResource SystemAccentColorLight2}"
|
||||||
CornerRadius="6" Padding="12,6" Margin="0,0,8,8">
|
CornerRadius="6" Padding="12,6" MinWidth="150"
|
||||||
|
ToolTip.Tip="Kurs öffnen">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<Border Width="6" Height="6" CornerRadius="3"
|
||||||
|
Background="{DynamicResource SystemAccentColor}"
|
||||||
|
IsVisible="{Binding IsOnSelectedDay}" VerticalAlignment="Center"/>
|
||||||
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" FontSize="13"/>
|
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" FontSize="13"/>
|
||||||
|
</StackPanel>
|
||||||
<TextBlock Text="{Binding Subject}" FontSize="11" Opacity="0.7"
|
<TextBlock Text="{Binding Subject}" FontSize="11" Opacity="0.7"
|
||||||
IsVisible="{Binding Subject, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
IsVisible="{Binding Subject, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -182,8 +182,14 @@
|
|||||||
<!-- Liste aller Klausuren, nach Priorität sortiert (nicht nach Datum) ─────────── -->
|
<!-- Liste aller Klausuren, nach Priorität sortiert (nicht nach Datum) ─────────── -->
|
||||||
<ScrollViewer Grid.Row="2" Margin="20,0,20,16">
|
<ScrollViewer Grid.Row="2" Margin="20,0,20,16">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="{Binding EmptyHint}" Classes="emptyhint" Margin="0,20,0,0"
|
<StackPanel Margin="0,28,0,0" Spacing="10" HorizontalAlignment="Center"
|
||||||
IsVisible="{Binding EmptyHint, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
IsVisible="{Binding HasNoExams}">
|
||||||
|
<TextBlock Text="Noch keine Klausuren im aktuellen Schuljahr." Classes="emptyhint"
|
||||||
|
FontSize="15" TextAlignment="Center"/>
|
||||||
|
<TextBlock Text="Klausuren werden im jeweiligen Kurs angelegt." FontSize="12" Opacity="0.55"/>
|
||||||
|
<Button Content="Zu den Lerngruppen" Command="{Binding GoToGroupsCommand}"
|
||||||
|
HorizontalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
<ItemsControl ItemsSource="{Binding Rows}">
|
<ItemsControl ItemsSource="{Binding Rows}">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate x:DataType="vm:ExamListRowViewModel">
|
<DataTemplate x:DataType="vm:ExamListRowViewModel">
|
||||||
|
|||||||
@@ -13,9 +13,9 @@
|
|||||||
<Border Grid.Row="0" Padding="20,16"
|
<Border Grid.Row="0" Padding="20,16"
|
||||||
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
||||||
BorderThickness="0,0,0,1">
|
BorderThickness="0,0,0,1">
|
||||||
<Grid ColumnDefinitions="*,Auto">
|
<Grid RowDefinitions="Auto,Auto">
|
||||||
<shared:PageHeader Grid.Column="0" Title="{Binding GroupTitle}" Subtitle="{Binding GroupSubtitle}"/>
|
<shared:PageHeader Title="{Binding GroupTitle}" Subtitle="{Binding GroupSubtitle}"/>
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
<WrapPanel Grid.Row="1" Orientation="Horizontal" ItemSpacing="8" LineSpacing="8" Margin="0,10,0,0">
|
||||||
<TextBlock VerticalAlignment="Center" Opacity="0.6" FontSize="13">
|
<TextBlock VerticalAlignment="Center" Opacity="0.6" FontSize="13">
|
||||||
<Run Text="{Binding StudentCount}"/>
|
<Run Text="{Binding StudentCount}"/>
|
||||||
<Run Text=" Schüler"/>
|
<Run Text=" Schüler"/>
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<Button Content="Austragung zurücknehmen" Command="{Binding ReinstateStudentCommand}"
|
<Button Content="Austragung zurücknehmen" Command="{Binding ReinstateStudentCommand}"
|
||||||
IsVisible="{Binding SelectedStudent.HasExitDate}"/>
|
IsVisible="{Binding SelectedStudent.HasExitDate}"/>
|
||||||
<Button Content="+ Klausur" Command="{Binding AddExamCommand}" IsEnabled="{Binding IsEditable}"/>
|
<Button Content="+ Klausur" Command="{Binding AddExamCommand}" IsEnabled="{Binding IsEditable}"/>
|
||||||
</StackPanel>
|
</WrapPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,15 @@ public partial class GroupDocumentationTabView : UserControl
|
|||||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||||
if (owner is null) return null;
|
if (owner is null) return null;
|
||||||
|
|
||||||
|
var options = new List<StudentOption> { GroupDocumentationTabViewModel.WholeGroupOption };
|
||||||
|
options.AddRange(studentOptions);
|
||||||
var vm = new DocumentationDialogViewModel(editing?.StudentId ?? Guid.Empty, editing,
|
var vm = new DocumentationDialogViewModel(editing?.StudentId ?? Guid.Empty, editing,
|
||||||
App.Services.GetRequiredService<IAttachmentStorage>(), studentOptions, groupId);
|
App.Services.GetRequiredService<IAttachmentStorage>(), options, groupId);
|
||||||
|
if (editing is null)
|
||||||
|
{
|
||||||
|
vm.SelectedStudent = GroupDocumentationTabViewModel.WholeGroupOption;
|
||||||
|
vm.TypeName = DocumentationTypeDisplay.Label(DocumentationType.Planning);
|
||||||
|
}
|
||||||
var dialog = new DocumentationDialog { DataContext = vm };
|
var dialog = new DocumentationDialog { DataContext = vm };
|
||||||
var saved = await dialog.ShowDialog<bool>(owner);
|
var saved = await dialog.ShowDialog<bool>(owner);
|
||||||
if (!saved) vm.DiscardUnsavedAttachments();
|
if (!saved) vm.DiscardUnsavedAttachments();
|
||||||
|
|||||||
@@ -11,13 +11,14 @@
|
|||||||
<Border Grid.Row="0" Padding="20,16"
|
<Border Grid.Row="0" Padding="20,16"
|
||||||
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
||||||
BorderThickness="0,0,0,1">
|
BorderThickness="0,0,0,1">
|
||||||
<Grid ColumnDefinitions="*,Auto,Auto">
|
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto,Auto">
|
||||||
<shared:PageHeader Grid.Column="0" Title="Lerngruppen" Subtitle="{Binding ListSummary}"/>
|
<shared:PageHeader Grid.ColumnSpan="3" Title="Lerngruppen" Subtitle="{Binding ListSummary}"/>
|
||||||
<ComboBox Grid.Column="1" ItemsSource="{Binding SchoolYears}"
|
<ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding SchoolYears}"
|
||||||
SelectedItem="{Binding SelectedSchoolYear}"
|
SelectedItem="{Binding SelectedSchoolYear}"
|
||||||
Width="100" Margin="0,0,8,0" VerticalAlignment="Center"/>
|
Width="110" Margin="0,10,8,0" VerticalAlignment="Center"
|
||||||
<Button Grid.Column="2" Content="+ Neue Gruppe"
|
AutomationProperties.Name="Schuljahr auswählen"/>
|
||||||
Command="{Binding AddGroupCommand}" VerticalAlignment="Center"/>
|
<Button Grid.Row="1" Grid.Column="2" Content="+ Neue Gruppe"
|
||||||
|
Command="{Binding AddGroupCommand}" VerticalAlignment="Center" Margin="0,10,0,0"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Grid.Column="2" Content="⋯" Width="36" Height="32" Margin="0,10,10,0"
|
<Button Grid.Column="2" Content="⋯" Width="40" Height="40" Margin="0,10,10,0"
|
||||||
Padding="0" VerticalAlignment="Top"
|
Padding="0" VerticalAlignment="Top"
|
||||||
ToolTip.Tip="Lerngruppe verwalten"
|
ToolTip.Tip="Lerngruppe verwalten"
|
||||||
AutomationProperties.Name="{Binding ManageAutomationName}">
|
AutomationProperties.Name="{Binding ManageAutomationName}">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
x:DataType="vm:MainWindowViewModel"
|
x:DataType="vm:MainWindowViewModel"
|
||||||
Title="LehrerApp"
|
Title="LehrerApp"
|
||||||
Width="1280" Height="800"
|
Width="1280" Height="800"
|
||||||
MinWidth="900" MinHeight="600">
|
MinWidth="640" MinHeight="480">
|
||||||
|
|
||||||
<Panel>
|
<Panel>
|
||||||
<!--
|
<!--
|
||||||
@@ -81,14 +81,14 @@
|
|||||||
FontFamily explizit auf die farbige Emoji-Schrift gepinnt: Windows kann für
|
FontFamily explizit auf die farbige Emoji-Schrift gepinnt: Windows kann für
|
||||||
Emoji-Codepoints je nach Fallback-Auflösung sonst auf "Segoe UI Symbol"
|
Emoji-Codepoints je nach Fallback-Auflösung sonst auf "Segoe UI Symbol"
|
||||||
(einfarbig/schwarz) statt "Segoe UI Emoji" (farbig) ausweichen. -->
|
(einfarbig/schwarz) statt "Segoe UI Emoji" (farbig) ausweichen. -->
|
||||||
<Style Selector="TextBlock.navicon">
|
<Style Selector="PathIcon.navicon">
|
||||||
<Setter Property="FontSize" Value="15"/>
|
|
||||||
<Setter Property="Width" Value="24"/>
|
<Setter Property="Width" Value="24"/>
|
||||||
<Setter Property="FontFamily" Value="Segoe UI Emoji,Segoe UI Symbol,Segoe UI"/>
|
<Setter Property="Height" Value="24"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.navitem">
|
<Style Selector="Button.navitem">
|
||||||
<Setter Property="Background" Value="Transparent"/>
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
<Setter Property="Padding" Value="10,8"/>
|
<Setter Property="Padding" Value="10,8"/>
|
||||||
|
<Setter Property="MinHeight" Value="40"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.navitem.active">
|
<Style Selector="Button.navitem.active">
|
||||||
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAccentBrush}"/>
|
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAccentBrush}"/>
|
||||||
@@ -98,9 +98,9 @@
|
|||||||
<Style Selector="DockPanel.compact TextBlock.navlabel">
|
<Style Selector="DockPanel.compact TextBlock.navlabel">
|
||||||
<Setter Property="IsVisible" Value="False"/>
|
<Setter Property="IsVisible" Value="False"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="DockPanel.compact TextBlock.navicon">
|
<Style Selector="DockPanel.compact PathIcon.navicon">
|
||||||
<Setter Property="FontSize" Value="20"/>
|
|
||||||
<Setter Property="Width" Value="28"/>
|
<Setter Property="Width" Value="28"/>
|
||||||
|
<Setter Property="Height" Value="28"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="DockPanel.compact Button.navitem">
|
<Style Selector="DockPanel.compact Button.navitem">
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
ToolTip.Tip="Suchen und schnell erfassen (Strg/⌘+K)"
|
ToolTip.Tip="Suchen und schnell erfassen (Strg/⌘+K)"
|
||||||
AutomationProperties.Name="Suchen und schnell erfassen">
|
AutomationProperties.Name="Suchen und schnell erfassen">
|
||||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||||
<TextBlock Classes="navicon" Text="⌕" TextAlignment="Center"/>
|
<PathIcon Classes="navicon" Data="{StaticResource IconSearch}"/>
|
||||||
<TextBlock Grid.Column="1" Classes="navlabel" Text="Suchen / Erfassen"/>
|
<TextBlock Grid.Column="1" Classes="navlabel" Text="Suchen / Erfassen"/>
|
||||||
<TextBlock Grid.Column="2" Classes="navlabel" Text="⌘K" FontSize="10" Opacity="0.45"
|
<TextBlock Grid.Column="2" Classes="navlabel" Text="⌘K" FontSize="10" Opacity="0.45"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
@@ -169,9 +169,10 @@
|
|||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
CornerRadius="6"
|
CornerRadius="6"
|
||||||
Command="{Binding NavigateToCommand}"
|
Command="{Binding NavigateToCommand}"
|
||||||
CommandParameter="{x:Static vm:NavItem.Dashboard}">
|
CommandParameter="{x:Static vm:NavItem.Dashboard}"
|
||||||
|
ToolTip.Tip="Dashboard (Strg/⌘+1)" AutomationProperties.Name="Dashboard">
|
||||||
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
||||||
<TextBlock Classes="navicon" Text="📊" TextAlignment="Center"/>
|
<PathIcon Classes="navicon" Data="{StaticResource IconDashboard}"/>
|
||||||
<TextBlock Classes="navlabel" Text="Dashboard"/>
|
<TextBlock Classes="navlabel" Text="Dashboard"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -183,9 +184,10 @@
|
|||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
CornerRadius="6"
|
CornerRadius="6"
|
||||||
Command="{Binding NavigateToCommand}"
|
Command="{Binding NavigateToCommand}"
|
||||||
CommandParameter="{x:Static vm:NavItem.Groups}">
|
CommandParameter="{x:Static vm:NavItem.Groups}"
|
||||||
|
ToolTip.Tip="Lerngruppen (Strg/⌘+2)" AutomationProperties.Name="Lerngruppen">
|
||||||
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
||||||
<TextBlock Classes="navicon" Text="🏫" TextAlignment="Center"/>
|
<PathIcon Classes="navicon" Data="{StaticResource IconGroups}"/>
|
||||||
<TextBlock Classes="navlabel" Text="Lerngruppen"/>
|
<TextBlock Classes="navlabel" Text="Lerngruppen"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -193,9 +195,10 @@
|
|||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
CornerRadius="6"
|
CornerRadius="6"
|
||||||
Command="{Binding NavigateToCommand}"
|
Command="{Binding NavigateToCommand}"
|
||||||
CommandParameter="{x:Static vm:NavItem.Students}">
|
CommandParameter="{x:Static vm:NavItem.Students}"
|
||||||
|
ToolTip.Tip="Schüler (Strg/⌘+3)" AutomationProperties.Name="Schüler">
|
||||||
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
||||||
<TextBlock Classes="navicon" Text="👤" TextAlignment="Center"/>
|
<PathIcon Classes="navicon" Data="{StaticResource IconStudents}"/>
|
||||||
<TextBlock Classes="navlabel" Text="Schüler"/>
|
<TextBlock Classes="navlabel" Text="Schüler"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -203,9 +206,10 @@
|
|||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
CornerRadius="6"
|
CornerRadius="6"
|
||||||
Command="{Binding NavigateToCommand}"
|
Command="{Binding NavigateToCommand}"
|
||||||
CommandParameter="{x:Static vm:NavItem.Exams}">
|
CommandParameter="{x:Static vm:NavItem.Exams}"
|
||||||
|
ToolTip.Tip="Klausuren (Strg/⌘+4)" AutomationProperties.Name="Klausuren">
|
||||||
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
||||||
<TextBlock Classes="navicon" Text="📝" TextAlignment="Center"/>
|
<PathIcon Classes="navicon" Data="{StaticResource IconExams}"/>
|
||||||
<TextBlock Classes="navlabel" Text="Klausuren"/>
|
<TextBlock Classes="navlabel" Text="Klausuren"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -213,9 +217,10 @@
|
|||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
CornerRadius="6"
|
CornerRadius="6"
|
||||||
Command="{Binding NavigateToCommand}"
|
Command="{Binding NavigateToCommand}"
|
||||||
CommandParameter="{x:Static vm:NavItem.Planner}">
|
CommandParameter="{x:Static vm:NavItem.Planner}"
|
||||||
|
ToolTip.Tip="Planung (Strg/⌘+5)" AutomationProperties.Name="Planung">
|
||||||
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
||||||
<TextBlock Classes="navicon" Text="📅" TextAlignment="Center"/>
|
<PathIcon Classes="navicon" Data="{StaticResource IconCalendar}"/>
|
||||||
<TextBlock Classes="navlabel" Text="Planung"/>
|
<TextBlock Classes="navlabel" Text="Planung"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -227,9 +232,10 @@
|
|||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
CornerRadius="6"
|
CornerRadius="6"
|
||||||
Command="{Binding NavigateToCommand}"
|
Command="{Binding NavigateToCommand}"
|
||||||
CommandParameter="{x:Static vm:NavItem.Workload}">
|
CommandParameter="{x:Static vm:NavItem.Workload}"
|
||||||
|
ToolTip.Tip="Arbeitszeit (Strg/⌘+6)" AutomationProperties.Name="Arbeitszeit">
|
||||||
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
||||||
<TextBlock Classes="navicon" Text="⏱" TextAlignment="Center"/>
|
<PathIcon Classes="navicon" Data="{StaticResource IconClock}"/>
|
||||||
<TextBlock Classes="navlabel" Text="Arbeitszeit"/>
|
<TextBlock Classes="navlabel" Text="Arbeitszeit"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -237,9 +243,10 @@
|
|||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
CornerRadius="6"
|
CornerRadius="6"
|
||||||
Command="{Binding NavigateToCommand}"
|
Command="{Binding NavigateToCommand}"
|
||||||
CommandParameter="{x:Static vm:NavItem.ClassTeacher}">
|
CommandParameter="{x:Static vm:NavItem.ClassTeacher}"
|
||||||
|
ToolTip.Tip="Klassenlehrer (Strg/⌘+7)" AutomationProperties.Name="Klassenlehrer">
|
||||||
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
||||||
<TextBlock Classes="navicon" Text="🎓" TextAlignment="Center"/>
|
<PathIcon Classes="navicon" Data="{StaticResource IconClassTeacher}"/>
|
||||||
<TextBlock Classes="navlabel" Text="Klassenlehrer"/>
|
<TextBlock Classes="navlabel" Text="Klassenlehrer"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -247,9 +254,10 @@
|
|||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
CornerRadius="6"
|
CornerRadius="6"
|
||||||
Command="{Binding NavigateToCommand}"
|
Command="{Binding NavigateToCommand}"
|
||||||
CommandParameter="{x:Static vm:NavItem.Settings}">
|
CommandParameter="{x:Static vm:NavItem.Settings}"
|
||||||
|
ToolTip.Tip="Einstellungen (Strg/⌘+8)" AutomationProperties.Name="Einstellungen">
|
||||||
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
<StackPanel Classes="navcontent" Orientation="Horizontal" Spacing="10">
|
||||||
<TextBlock Classes="navicon" Text="⚙️" TextAlignment="Center"/>
|
<PathIcon Classes="navicon" Data="{StaticResource IconSettings}"/>
|
||||||
<TextBlock Classes="navlabel" Text="Einstellungen"/>
|
<TextBlock Classes="navlabel" Text="Einstellungen"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -36,6 +36,13 @@ public partial class MainWindow : Window
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (commandModifier && TryGetNavigationShortcut(e.Key, out var destination))
|
||||||
|
{
|
||||||
|
vm.NavigateToCommand.Execute(destination);
|
||||||
|
e.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!vm.IsCommandPaletteOpen) return;
|
if (!vm.IsCommandPaletteOpen) return;
|
||||||
if (e.Key == Key.Escape)
|
if (e.Key == Key.Escape)
|
||||||
{
|
{
|
||||||
@@ -54,6 +61,23 @@ public partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryGetNavigationShortcut(Key key, out NavItem destination)
|
||||||
|
{
|
||||||
|
destination = key switch
|
||||||
|
{
|
||||||
|
Key.D1 or Key.NumPad1 => NavItem.Dashboard,
|
||||||
|
Key.D2 or Key.NumPad2 => NavItem.Groups,
|
||||||
|
Key.D3 or Key.NumPad3 => NavItem.Students,
|
||||||
|
Key.D4 or Key.NumPad4 => NavItem.Exams,
|
||||||
|
Key.D5 or Key.NumPad5 => NavItem.Planner,
|
||||||
|
Key.D6 or Key.NumPad6 => NavItem.Workload,
|
||||||
|
Key.D7 or Key.NumPad7 => NavItem.ClassTeacher,
|
||||||
|
Key.D8 or Key.NumPad8 => NavItem.Settings,
|
||||||
|
_ => default,
|
||||||
|
};
|
||||||
|
return key is >= Key.D1 and <= Key.D8 or >= Key.NumPad1 and <= Key.NumPad8;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnOpenCommandPaletteClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
private void OnOpenCommandPaletteClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is MainWindowViewModel vm)
|
if (DataContext is MainWindowViewModel vm)
|
||||||
|
|||||||
@@ -154,9 +154,11 @@
|
|||||||
|
|
||||||
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto">
|
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto">
|
||||||
<Button Grid.Column="0" Content="‹" FontWeight="Bold" Padding="10,4"
|
<Button Grid.Column="0" Content="‹" FontWeight="Bold" Padding="10,4"
|
||||||
Command="{Binding PreviousWeekCommand}" ToolTip.Tip="Vorherige Woche"/>
|
Command="{Binding PreviousWeekCommand}" ToolTip.Tip="Vorherige Woche"
|
||||||
|
AutomationProperties.Name="Vorherige Woche"/>
|
||||||
<Button Grid.Column="1" Content="›" FontWeight="Bold" Padding="10,4" Margin="4,0,0,0"
|
<Button Grid.Column="1" Content="›" FontWeight="Bold" Padding="10,4" Margin="4,0,0,0"
|
||||||
Command="{Binding NextWeekCommand}" ToolTip.Tip="Nächste Woche"/>
|
Command="{Binding NextWeekCommand}" ToolTip.Tip="Nächste Woche"
|
||||||
|
AutomationProperties.Name="Nächste Woche"/>
|
||||||
<TextBlock Grid.Column="2" FontSize="16" FontWeight="SemiBold"
|
<TextBlock Grid.Column="2" FontSize="16" FontWeight="SemiBold"
|
||||||
VerticalAlignment="Center" Margin="12,0,0,0">
|
VerticalAlignment="Center" Margin="12,0,0,0">
|
||||||
<Run Text="Woche "/><Run Text="{Binding WeekRangeLabel}"/>
|
<Run Text="Woche "/><Run Text="{Binding WeekRangeLabel}"/>
|
||||||
@@ -166,7 +168,8 @@
|
|||||||
<Button Grid.Column="4" Content="Ausnahme eintragen" Margin="0,0,8,0"
|
<Button Grid.Column="4" Content="Ausnahme eintragen" Margin="0,0,8,0"
|
||||||
Command="{Binding AddSubstitutionCommand}"/>
|
Command="{Binding AddSubstitutionCommand}"/>
|
||||||
<Button Grid.Column="5" Content="⚙️" Command="{Binding OpenSettingsCommand}"
|
<Button Grid.Column="5" Content="⚙️" Command="{Binding OpenSettingsCommand}"
|
||||||
ToolTip.Tip="Einstellungen (Ferien, Aufsichten, Stundenraster)"/>
|
ToolTip.Tip="Einstellungen (Ferien, Aufsichten, Stundenraster)"
|
||||||
|
AutomationProperties.Name="Stundenplan-Einstellungen"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Border Background="#33F59E0B" BorderBrush="#F59E0B" BorderThickness="1"
|
<Border Background="#33F59E0B" BorderBrush="#F59E0B" BorderThickness="1"
|
||||||
@@ -281,7 +284,7 @@
|
|||||||
<Button Classes="weekCellMenuTrigger" Content="⋮"
|
<Button Classes="weekCellMenuTrigger" Content="⋮"
|
||||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||||
IsVisible="{Binding HasGroupId}"
|
IsVisible="{Binding HasGroupId}"
|
||||||
ToolTip.Tip="Weitere Ziele…">
|
ToolTip.Tip="Weitere Ziele…" AutomationProperties.Name="Weitere Ziele">
|
||||||
<Button.Flyout>
|
<Button.Flyout>
|
||||||
<MenuFlyout Placement="BottomEdgeAlignedRight">
|
<MenuFlyout Placement="BottomEdgeAlignedRight">
|
||||||
<MenuItem Header="▶ Unterrichtsansicht" IsVisible="{Binding HasLesson}"
|
<MenuItem Header="▶ Unterrichtsansicht" IsVisible="{Binding HasLesson}"
|
||||||
@@ -404,7 +407,8 @@
|
|||||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||||
HorizontalContentAlignment="Center" Background="Transparent"
|
HorizontalContentAlignment="Center" Background="Transparent"
|
||||||
Command="{Binding $parent[ItemsControl;1].((vm:TimetableViewModel)DataContext).EditCellCommand}"
|
Command="{Binding $parent[ItemsControl;1].((vm:TimetableViewModel)DataContext).EditCellCommand}"
|
||||||
CommandParameter="{Binding}"/>
|
CommandParameter="{Binding}"
|
||||||
|
AutomationProperties.Name="Stundenplan-Eintrag hinzufügen"/>
|
||||||
<Border Background="#D85A30" CornerRadius="8" Padding="5,1"
|
<Border Background="#D85A30" CornerRadius="8" Padding="5,1"
|
||||||
IsVisible="{Binding HasBadge}"
|
IsVisible="{Binding HasBadge}"
|
||||||
HorizontalAlignment="Right" VerticalAlignment="Top" Margin="2">
|
HorizontalAlignment="Right" VerticalAlignment="Top" Margin="2">
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
<TextBlock Text="{Binding DialogTitle}" Classes="dialogtitle"/>
|
<TextBlock Text="{Binding DialogTitle}" Classes="dialogtitle"/>
|
||||||
|
|
||||||
<StackPanel Spacing="4" IsVisible="{Binding CanPickStudent}">
|
<StackPanel Spacing="4" IsVisible="{Binding CanPickStudent}">
|
||||||
<TextBlock Text="Schüler *" FontSize="12" Opacity="0.7"/>
|
<TextBlock Text="Bezug *" FontSize="12" Opacity="0.7"/>
|
||||||
<ComboBox ItemsSource="{Binding StudentOptions}" SelectedItem="{Binding SelectedStudent}"
|
<ComboBox ItemsSource="{Binding StudentOptions}" SelectedItem="{Binding SelectedStudent}"
|
||||||
DisplayMemberBinding="{Binding Name}" HorizontalAlignment="Stretch"
|
DisplayMemberBinding="{Binding Name}" HorizontalAlignment="Stretch"
|
||||||
PlaceholderText="Schüler wählen"/>
|
PlaceholderText="Schüler oder gesamte Lerngruppe wählen"/>
|
||||||
<TextBlock Text="{Binding StudentError}" Foreground="Red" FontSize="11"
|
<TextBlock Text="{Binding StudentError}" Foreground="Red" FontSize="11"
|
||||||
IsVisible="{Binding StudentError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
IsVisible="{Binding StudentError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Students"
|
||||||
|
x:Class="LehrerApp.Desktop.Views.Students.GroupDocumentationQuickDialog"
|
||||||
|
x:DataType="vm:GroupDocumentationQuickViewModel"
|
||||||
|
Title="Lerngruppen-Eintrag" Width="480" SizeToContent="Height"
|
||||||
|
CanResize="False" WindowStartupLocation="CenterOwner">
|
||||||
|
<Grid RowDefinitions="*,Auto" Margin="24">
|
||||||
|
<StackPanel Spacing="12">
|
||||||
|
<TextBlock Text="Lerngruppen-Eintrag" Classes="dialogtitle"/>
|
||||||
|
<TextBlock Text="Planung, Klausur oder Erinnerung für eine ganze Lerngruppe festhalten."
|
||||||
|
TextWrapping="Wrap" Opacity="0.7"/>
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<TextBlock Text="Lerngruppe *" FontSize="12" Opacity="0.7"/>
|
||||||
|
<ComboBox ItemsSource="{Binding Groups}" SelectedItem="{Binding SelectedGroup}"
|
||||||
|
DisplayMemberBinding="{Binding DisplayName}" PlaceholderText="Lerngruppe wählen"/>
|
||||||
|
<TextBlock Text="{Binding GroupError}" Foreground="Red" FontSize="11"
|
||||||
|
IsVisible="{Binding GroupError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<Grid ColumnDefinitions="*,12,140">
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<TextBlock Text="Titel *" FontSize="12" Opacity="0.7"/>
|
||||||
|
<TextBox Text="{Binding Title}" PlaceholderText="z. B. Klausur ankündigen"/>
|
||||||
|
<TextBlock Text="{Binding TitleError}" Foreground="Red" FontSize="11"
|
||||||
|
IsVisible="{Binding TitleError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Column="2" Spacing="4">
|
||||||
|
<TextBlock Text="Datum *" FontSize="12" Opacity="0.7"/>
|
||||||
|
<TextBox Text="{Binding DateText}"/>
|
||||||
|
<TextBlock Text="{Binding DateError}" Foreground="Red" FontSize="11"
|
||||||
|
IsVisible="{Binding DateError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<TextBlock Text="Notiz" FontSize="12" Opacity="0.7"/>
|
||||||
|
<TextBox Text="{Binding Content}" AcceptsReturn="True" Height="90" TextWrapping="Wrap"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,20,0,0">
|
||||||
|
<Button Content="Abbrechen" Click="OnCancel"/>
|
||||||
|
<Button Content="Speichern" Classes="accent" Click="OnSave"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using LehrerApp.Desktop.ViewModels.Students;
|
||||||
|
|
||||||
|
namespace LehrerApp.Desktop.Views.Students;
|
||||||
|
|
||||||
|
public partial class GroupDocumentationQuickDialog : Window
|
||||||
|
{
|
||||||
|
public GroupDocumentationQuickDialog() => InitializeComponent();
|
||||||
|
|
||||||
|
private void OnSave(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is not GroupDocumentationQuickViewModel vm) return;
|
||||||
|
vm.SaveCommand.Execute(null);
|
||||||
|
if (vm.Result is not null) Close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCancel(object? sender, RoutedEventArgs e) => Close(false);
|
||||||
|
}
|
||||||
@@ -8,18 +8,21 @@
|
|||||||
<Border Grid.Row="0" Padding="20,16"
|
<Border Grid.Row="0" Padding="20,16"
|
||||||
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
||||||
BorderThickness="0,0,0,1">
|
BorderThickness="0,0,0,1">
|
||||||
<Grid ColumnDefinitions="*,Auto,Auto,Auto">
|
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto,Auto,Auto">
|
||||||
<shared:PageHeader Grid.Column="0" Title="Schüler" Subtitle="{Binding CountSummary}"/>
|
<shared:PageHeader Grid.ColumnSpan="4" Title="Schüler" Subtitle="{Binding CountSummary}"/>
|
||||||
<CheckBox Grid.Column="1" Content="Inaktive anzeigen"
|
<CheckBox Grid.Row="1" Grid.Column="1" Content="Inaktive anzeigen"
|
||||||
IsChecked="{Binding ShowInactive}"
|
IsChecked="{Binding ShowInactive}"
|
||||||
VerticalAlignment="Center" Margin="0,0,12,0"/>
|
VerticalAlignment="Center" Margin="0,10,12,0"/>
|
||||||
<Button Grid.Column="2" Content="⇩ Importieren…" Click="OnImportClick"
|
<Button Grid.Row="1" Grid.Column="2" Content="⇩ Importieren…" Click="OnImportClick"
|
||||||
VerticalAlignment="Center" Margin="0,0,8,0"/>
|
IsEnabled="{Binding !IsImporting}"
|
||||||
<Button Grid.Column="3" Content="+ Neuer Schüler"
|
VerticalAlignment="Center" Margin="0,10,8,0"
|
||||||
Command="{Binding AddStudentCommand}" VerticalAlignment="Center"/>
|
AutomationProperties.Name="Schüler importieren"/>
|
||||||
|
<Button Grid.Row="1" Grid.Column="3" Content="+ Neuer Schüler"
|
||||||
|
Command="{Binding AddStudentCommand}" VerticalAlignment="Center" Margin="0,10,0,0"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
<DockPanel Grid.Row="1">
|
<Grid Grid.Row="1">
|
||||||
|
<DockPanel IsVisible="{Binding HasStudents}">
|
||||||
<TextBox DockPanel.Dock="Top" Text="{Binding SearchText}"
|
<TextBox DockPanel.Dock="Top" Text="{Binding SearchText}"
|
||||||
PlaceholderText="Name oder Lerngruppe suchen…" Margin="16,10,16,4"/>
|
PlaceholderText="Name oder Lerngruppe suchen…" Margin="16,10,16,4"/>
|
||||||
<DataGrid ItemsSource="{Binding Students}"
|
<DataGrid ItemsSource="{Binding Students}"
|
||||||
@@ -45,5 +48,22 @@
|
|||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10"
|
||||||
|
IsVisible="{Binding HasNoStudents}">
|
||||||
|
<TextBlock Text="{Binding EmptyListMessage}" Classes="emptyhint" FontSize="15"
|
||||||
|
TextAlignment="Center"/>
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="8"
|
||||||
|
IsVisible="{Binding SearchText, Converter={x:Static StringConverters.IsNullOrEmpty}}">
|
||||||
|
<Button Content="+ Ersten Schüler anlegen" Command="{Binding AddStudentCommand}"/>
|
||||||
|
<Button Content="⇩ Schülerliste importieren" Click="OnImportClick"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
<Border Background="#70000000" IsVisible="{Binding IsImporting}">
|
||||||
|
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10">
|
||||||
|
<ProgressBar IsIndeterminate="True" Width="220" Height="5"/>
|
||||||
|
<TextBlock Text="Schülerliste wird analysiert …" Foreground="White"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public partial class StudentListView : UserControl
|
|||||||
});
|
});
|
||||||
if (files.Count == 0) return;
|
if (files.Count == 0) return;
|
||||||
|
|
||||||
|
list.IsImporting = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var source = await files[0].OpenReadAsync();
|
await using var source = await files[0].OpenReadAsync();
|
||||||
@@ -67,5 +68,9 @@ public partial class StudentListView : UserControl
|
|||||||
{
|
{
|
||||||
App.Services.GetRequiredService<NotificationService>().ShowError(ex.Message);
|
App.Services.GetRequiredService<NotificationService>().ShowError(ex.Message);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
list.IsImporting = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,17 @@
|
|||||||
Foreground="#D32F2F"/>
|
Foreground="#D32F2F"/>
|
||||||
<TextBlock Text="Sync angehalten" FontSize="10" Foreground="#D32F2F" Opacity="0.8"/>
|
<TextBlock Text="Sync angehalten" FontSize="10" Foreground="#D32F2F" Opacity="0.8"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Button Grid.Column="1" Content="↻" FontSize="14"
|
<Button Grid.Column="1" Classes="touchTarget"
|
||||||
Command="{Binding SyncNowCommand}"
|
Command="{Binding SyncNowCommand}"
|
||||||
IsVisible="{Binding CanAttemptSync}"
|
IsVisible="{Binding CanAttemptSync}"
|
||||||
Background="Transparent" Padding="6,4"
|
Background="Transparent" Padding="8"
|
||||||
ToolTip.Tip="Jetzt synchronisieren"/>
|
ToolTip.Tip="Jetzt synchronisieren" AutomationProperties.Name="Jetzt synchronisieren">
|
||||||
|
<Grid>
|
||||||
|
<PathIcon Data="{StaticResource IconRefresh}" Width="18" Height="18"
|
||||||
|
IsVisible="{Binding !IsSyncing}"/>
|
||||||
|
<ProgressBar Width="22" Height="4" IsIndeterminate="True"
|
||||||
|
IsVisible="{Binding IsSyncing}"/>
|
||||||
|
</Grid>
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -2077,6 +2077,13 @@ dadurch faktisch schon (Quick-Input-Dialog bzw. "Offene Entschuldigungen" im Das
|
|||||||
Auswahl gar nicht erst an. "Gespräch begleiten" (Elternanruf) und Anhänge funktionieren im
|
Auswahl gar nicht erst an. "Gespräch begleiten" (Elternanruf) und Anhänge funktionieren im
|
||||||
Gruppen-Tab identisch zum Schüler-Tab, da beide dieselbe `DocumentationItem`/
|
Gruppen-Tab identisch zum Schüler-Tab, da beide dieselbe `DocumentationItem`/
|
||||||
`DocumentationDialog`-Infrastruktur verwenden.
|
`DocumentationDialog`-Infrastruktur verwenden.
|
||||||
|
- [x] **5.1.8** Gruppenweite Planungs- und Erinnerungseinträge ohne Schülerbezug. Im
|
||||||
|
Dokumentationsdialog einer Lerngruppe steht jetzt „Gesamte Lerngruppe“ als eigener Bezug
|
||||||
|
zur Wahl; solche Einträge verwenden den angehängten Typ `DocumentationType.Planning`, die
|
||||||
|
bestehende `GroupId` und bewusst `Guid.Empty` als kompatiblen „kein Schüler“-Marker. Sie
|
||||||
|
erscheinen auch in Gruppen ohne Schüler und werden eindeutig als „Gesamte Lerngruppe“
|
||||||
|
beschriftet. Die globale Suche bietet zusätzlich die Schnellaktion „Lerngruppen-Eintrag“
|
||||||
|
mit einem kompakten Formular für Gruppe, Datum, Titel und Notiz.
|
||||||
|
|
||||||
### 5.2 Fehlzeiten (als Auswertung des bestehenden Anwesenheits-Trackings, siehe oben)
|
### 5.2 Fehlzeiten (als Auswertung des bestehenden Anwesenheits-Trackings, siehe oben)
|
||||||
- [x] **5.2.1** Schnelle Abwesenheitserfassung je Stunde — bereits vorhanden über
|
- [x] **5.2.1** Schnelle Abwesenheitserfassung je Stunde — bereits vorhanden über
|
||||||
@@ -2644,6 +2651,13 @@ Bereich automatisch), die linke Inhaltsspalte erhält mehr Breite und leere rein
|
|||||||
aktivierter Dashboard-Konfiguration automatisch ausgeblendet. Inhalte und Direktaktionen bleiben
|
aktivierter Dashboard-Konfiguration automatisch ausgeblendet. Inhalte und Direktaktionen bleiben
|
||||||
unverändert erhalten; die Seite wird bei ruhiger Datenlage lediglich deutlich kürzer.
|
unverändert erhalten; die Seite wird bei ruhiger Datenlage lediglich deutlich kürzer.
|
||||||
|
|
||||||
|
**Nachtrag Kursliste (August 2026):** Die Kachel „Meine Lerngruppen“ nutzt weiterhin ein echtes
|
||||||
|
mehrzeiliges `WrapPanel`, jetzt mit explizitem Zeilen-/Elementabstand und Mindestbreite statt einer
|
||||||
|
abschneidbaren Ein-Zeilen-Darstellung. Bei Auswahl eines Kalendertags werden Gruppen mit einer
|
||||||
|
tatsächlichen Lesson oder einem an diesem Tag aktiven Stundenplan-Slot zuerst angezeigt und mit
|
||||||
|
einem Akzentpunkt markiert; innerhalb dieser Gruppe sowie für den Rest gilt alphabetische Sortierung.
|
||||||
|
Ferien, Feiertage und vollständig ausgefallene Stunden werden dabei berücksichtigt.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. Sync & Server
|
## 10. Sync & Server
|
||||||
@@ -3500,15 +3514,21 @@ von 4.2.2) der Tab "Kürzel-Katalog" für die Von/Nach-Kürzel des Stundenverlau
|
|||||||
|
|
||||||
## 14. UX-Querschnitt
|
## 14. UX-Querschnitt
|
||||||
|
|
||||||
- [ ] **14.1** Tastaturbedienung durchgängig: alle Hauptfunktionen ohne Maus erreichbar
|
- [~] **14.1** Tastaturbedienung durchgängig: alle Hauptfunktionen ohne Maus erreichbar
|
||||||
(Vorbild: Mitarbeit-Schnelleingabe).
|
(Vorbild: Mitarbeit-Schnelleingabe). **Hauptnavigation umgesetzt:** `Strg/⌘+1…8` öffnet
|
||||||
|
Dashboard, Lerngruppen, Schüler, Klausuren, Planung, Arbeitszeit, Klassenlehrer und
|
||||||
|
Einstellungen direkt; `Strg/⌘+K`, Pfeiltasten, Enter und Escape bedienen weiterhin die
|
||||||
|
globale Suche. Die vollständige Tastaturprüfung aller Fachdialoge bleibt offen.
|
||||||
- [x] **14.2** Globale Suche (Schüler, Gruppe, Klausur) über Tastenkürzel.
|
- [x] **14.2** Globale Suche (Schüler, Gruppe, Klausur) über Tastenkürzel.
|
||||||
**Umsetzung:** `Strg+K` (Windows/Linux) bzw. `⌘K` (macOS) öffnet aus jeder Hauptansicht eine
|
**Umsetzung:** `Strg+K` (Windows/Linux) bzw. `⌘K` (macOS) öffnet aus jeder Hauptansicht eine
|
||||||
modale Befehlspalette. `GlobalSearchViewModel` durchsucht lokal und ohne zusätzlichen Index
|
modale Befehlspalette. `GlobalSearchViewModel` durchsucht lokal und ohne zusätzlichen Index
|
||||||
aktive wie inaktive Schüler/Lerngruppen sowie Klausuren und Aufgaben; Treffer springen direkt
|
ausschließlich aktive Schüler/Lerngruppen sowie deren Klausuren und Aufgaben; Treffer springen direkt
|
||||||
ins Schülerdetail, Gruppendetail, den Klausuren-Tab oder die Aufgabenverwaltung. Pfeiltasten,
|
ins Schülerdetail, Gruppendetail, den Klausuren-Tab oder die Aufgabenverwaltung. Pfeiltasten,
|
||||||
Enter und Escape bedienen die Palette vollständig ohne Maus. Bei leerer Suche stehen die
|
Enter und Escape bedienen die Palette vollständig ohne Maus. Gruppentreffer werden
|
||||||
Schnellaktionen „Aufgabe anlegen“, „Erinnerung anlegen“ und „Schüler anlegen“ bereit und
|
zusätzlich über den zugeordneten Fachnamen gefunden und zeigen diesen
|
||||||
|
im Untertitel, damit gleichnamige Klassen/Kurse eindeutig bleiben. Bei leerer Suche stehen die
|
||||||
|
Schnellaktionen „Aufgabe anlegen“, „Erinnerung anlegen“, „Schüler anlegen“ und
|
||||||
|
„Lerngruppen-Eintrag“ bereit und
|
||||||
verwenden die bereits vorhandenen Dialoge samt Validierung. Zusätzlich ist der Einstieg als
|
verwenden die bereits vorhandenen Dialoge samt Validierung. Zusätzlich ist der Einstieg als
|
||||||
zugänglich benannte Schaltfläche im Navigationsbereich sichtbar. Tests in
|
zugänglich benannte Schaltfläche im Navigationsbereich sichtbar. Tests in
|
||||||
`GlobalSearchViewModelTests` decken Ergebnisarten, Navigation und Schnellerfassung ab.
|
`GlobalSearchViewModelTests` decken Ergebnisarten, Navigation und Schnellerfassung ab.
|
||||||
@@ -3553,8 +3573,15 @@ von 4.2.2) der Tab "Kürzel-Katalog" für die Von/Nach-Kürzel des Stundenverlau
|
|||||||
[TrashTests.cs](LehrerApp.Data.Tests/TrashTests.cs) (Löschen/Wiederherstellen je
|
[TrashTests.cs](LehrerApp.Data.Tests/TrashTests.cs) (Löschen/Wiederherstellen je
|
||||||
Repository, Sortierung, Bereinigung), 6 in
|
Repository, Sortierung, Bereinigung), 6 in
|
||||||
[TrashViewModelTests.cs](LehrerApp.Desktop.Tests/TrashViewModelTests.cs).
|
[TrashViewModelTests.cs](LehrerApp.Desktop.Tests/TrashViewModelTests.cs).
|
||||||
- [ ] **14.4** Ladeanzeigen bei längeren Operationen (Import, Sync, Export).
|
- [~] **14.4** Ladeanzeigen bei längeren Operationen (Import, Sync, Export).
|
||||||
- [ ] **14.5** Leere Zustände mit Handlungsaufforderung statt leerer Tabellen.
|
Schülerimporte blockieren die Liste während der asynchronen Dateianalyse mit Fortschritts-
|
||||||
|
anzeige und Status; der Sync-Button wechselt während des Abgleichs vom Vektoricon auf eine
|
||||||
|
indeterminierte Fortschrittsanzeige. Bereits vorhandene WebUntis-/KI-Ladevorgänge bleiben
|
||||||
|
erhalten. Eine einheitliche Anzeige für sämtliche PDF-/CSV-Exporte ist noch offen.
|
||||||
|
- [~] **14.5** Leere Zustände mit Handlungsaufforderung statt leerer Tabellen. Lerngruppen hatten
|
||||||
|
bereits „Erste Lerngruppe anlegen“; Schüler bieten jetzt „Ersten Schüler anlegen“ und
|
||||||
|
„Schülerliste importieren“, die Klausuren-Hauptseite erklärt den Anlageort und springt zu
|
||||||
|
den Lerngruppen. Fachspezifische Untertabellen werden schrittweise ergänzt.
|
||||||
- [~] **14.6** Fenstergröße und Spaltenbreiten über Sitzungen hinweg merken.
|
- [~] **14.6** Fenstergröße und Spaltenbreiten über Sitzungen hinweg merken.
|
||||||
|
|
||||||
**Umsetzung (Fenstergröße):**
|
**Umsetzung (Fenstergröße):**
|
||||||
@@ -3576,14 +3603,23 @@ von 4.2.2) der Tab "Kürzel-Katalog" für die Von/Nach-Kürzel des Stundenverlau
|
|||||||
stabile Spalten-Identität, an der eine gespeicherte Breite verlässlich hängen könnte —
|
stabile Spalten-Identität, an der eine gespeicherte Breite verlässlich hängen könnte —
|
||||||
eine generische Lösung ist damit kein "billiger" Zusatz, sondern ein eigener, größerer
|
eine generische Lösung ist damit kein "billiger" Zusatz, sondern ein eigener, größerer
|
||||||
Umbau. Nicht in diesem Durchgang umgesetzt.
|
Umbau. Nicht in diesem Durchgang umgesetzt.
|
||||||
- [ ] **14.7** Bedienung auf Touch-Geräten prüfen (Tablet im Unterricht).
|
- [~] **14.7** Bedienung auf Touch-Geräten prüfen (Tablet im Unterricht). Hauptnavigation und
|
||||||
- [ ] **14.8** Responsive Layout und Windows-DPI prüfen (kleine Notebook-Auflösungen sowie
|
Sync-Aktion haben mindestens 40 px große Ziele; besonders kleine Karten-/Menüaktionen wurden
|
||||||
|
vergrößert. Ein Test auf echter Tablet-Hardware bleibt offen.
|
||||||
|
- [~] **14.8** Responsive Layout und Windows-DPI prüfen (kleine Notebook-Auflösungen sowie
|
||||||
125/150/200 % Skalierung; starre Master-Detail-Spalten bei Bedarf stapeln). Der kompakte
|
125/150/200 % Skalierung; starre Master-Detail-Spalten bei Bedarf stapeln). Der kompakte
|
||||||
Drawer berücksichtigt bereits die schmalere verfügbare Breite mit reduziertem Außen-/
|
Drawer berücksichtigt bereits die schmalere verfügbare Breite mit reduziertem Außen-/
|
||||||
Innenabstand und einer eigenen Iconfläche, damit Windows-Emoji nicht abgeschnitten werden.
|
Innenabstand und einer eigenen Iconfläche, damit Windows-Emoji nicht abgeschnitten werden.
|
||||||
- [ ] **14.9** Barrierefreiheit prüfen: Automation-Namen für Icon-Buttons, sichtbare Fokusrahmen,
|
Das Hauptfenster kann jetzt bis 640×480 verkleinert werden und wechselt dadurch tatsächlich
|
||||||
|
in den Overlay-Drawer; zuvor verhinderte `MinWidth=900` exakt diesen Zustand. Aktionsleisten
|
||||||
|
in Dashboard, Schüler-, Gruppenliste und Gruppendetail umbrechen bzw. liegen unter dem Titel.
|
||||||
|
Ein echter Windows-DPI-Test bleibt offen.
|
||||||
|
- [~] **14.9** Barrierefreiheit prüfen: Automation-Namen für Icon-Buttons, sichtbare Fokusrahmen,
|
||||||
Kontraste und Status nicht ausschließlich über Farbe/Emoji vermitteln.
|
Kontraste und Status nicht ausschließlich über Farbe/Emoji vermitteln.
|
||||||
- [ ] **14.10** Plattformübergreifend konsistentes SVG-/`PathIcon`-Set statt systemabhängiger
|
Sichtbare Akzent-Fokusrahmen sind zentral für Buttons, Textfelder und Comboboxen definiert;
|
||||||
|
Hauptnavigation, Kalender-/Dashboard-Sortierung und zentrale Stundenplan-Iconaktionen haben
|
||||||
|
sprechende Automation-Namen und Tooltips. Die vollständige Prüfung aller Dialoge bleibt offen.
|
||||||
|
- [~] **14.10** Plattformübergreifend konsistentes SVG-/`PathIcon`-Set statt systemabhängiger
|
||||||
Emoji-Darstellung einführen. **Konkreter Bericht (Nutzer-Feedback):** Drawer-Icons erscheinen
|
Emoji-Darstellung einführen. **Konkreter Bericht (Nutzer-Feedback):** Drawer-Icons erscheinen
|
||||||
auf einem Windows-PC gegen 22 Uhr einfarbig schwarz statt farbig — passend zum bekannten
|
auf einem Windows-PC gegen 22 Uhr einfarbig schwarz statt farbig — passend zum bekannten
|
||||||
Windows-Verhalten, dass Emoji-Codepunkte je nach Font-Fallback statt der farbigen
|
Windows-Verhalten, dass Emoji-Codepunkte je nach Font-Fallback statt der farbigen
|
||||||
@@ -3593,7 +3629,10 @@ von 4.2.2) der Tab "Kürzel-Katalog" für die Von/Nach-Kürzel des Stundenverlau
|
|||||||
explizit `FontFamily="Segoe UI Emoji,Segoe UI Symbol,Segoe UI"` — auf anderen Plattformen
|
explizit `FontFamily="Segoe UI Emoji,Segoe UI Symbol,Segoe UI"` — auf anderen Plattformen
|
||||||
folgenlos, da unbekannte Fontnamen einfach übersprungen werden. Nur eine Abmilderung für die
|
folgenlos, da unbekannte Fontnamen einfach übersprungen werden. Nur eine Abmilderung für die
|
||||||
Drawer-Navigation, kein Nachweis der Ursache und keine Lösung für die übrigen Emoji im Rest
|
Drawer-Navigation, kein Nachweis der Ursache und keine Lösung für die übrigen Emoji im Rest
|
||||||
der App — die eigentliche, dauerhafte Lösung bleibt dieser Punkt (echtes Icon-Set).
|
der App. **Hauptnavigation jetzt dauerhaft gelöst:** Suche und alle acht Navigationsziele
|
||||||
|
verwenden zentral hinterlegte `StreamGeometry`/`PathIcon`-Ressourcen; auch der Sync-Button
|
||||||
|
verwendet ein Vektoricon. Fachaktionen im restlichen UI enthalten teilweise weiterhin Emoji
|
||||||
|
und werden in einem späteren, separaten Austausch migriert.
|
||||||
- [x] **14.11** Aktiven Navigationspunkt in der Seitenleiste sichtbar hervorheben; Zustand wird
|
- [x] **14.11** Aktiven Navigationspunkt in der Seitenleiste sichtbar hervorheben; Zustand wird
|
||||||
über `MainWindowViewModel.ActiveNavItem` gesteuert.
|
über `MainWindowViewModel.ActiveNavItem` gesteuert.
|
||||||
- [x] **14.12** Tab "Übersicht" im Kurs (`GroupDetailView`, bislang nur Platzhaltertext) gefüllt —
|
- [x] **14.12** Tab "Übersicht" im Kurs (`GroupDetailView`, bislang nur Platzhaltertext) gefüllt —
|
||||||
|
|||||||
Reference in New Issue
Block a user