UI Improvements: Planungsdialog, Fehlzeitendarstellung, Vorauswahl der Einheit
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
This commit is contained in:
@@ -357,6 +357,20 @@ public class UnitRepository(LiteDbContext db) : IUnitRepository
|
|||||||
public void Save(Unit u)
|
public void Save(Unit u)
|
||||||
{
|
{
|
||||||
ArchivedGroupWriteGuard.EnsureActive(db, u.GroupId);
|
ArchivedGroupWriteGuard.EnsureActive(db, u.GroupId);
|
||||||
|
// Persistenz-Invariante: auch Importe und künftig hinzukommende Aufrufer dürfen keine
|
||||||
|
// zweite laufende Einheit in derselben Lerngruppe hinterlassen. Der Einheiten-Dialog
|
||||||
|
// kündigt diese automatische Ablösung vorher sichtbar an.
|
||||||
|
if (u.Status == UnitStatus.Active)
|
||||||
|
{
|
||||||
|
foreach (var previous in db.Units.Find(x => x.GroupId == u.GroupId &&
|
||||||
|
x.Status == UnitStatus.Active && x.Id != u.Id))
|
||||||
|
{
|
||||||
|
previous.Status = UnitStatus.Completed;
|
||||||
|
previous.UpdatedAt = DateTime.UtcNow;
|
||||||
|
db.Units.Upsert(previous);
|
||||||
|
db.OnChange?.Invoke(nameof(Unit), previous.Id.ToString(), "Save", previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
u.UpdatedAt = DateTime.UtcNow;
|
u.UpdatedAt = DateTime.UtcNow;
|
||||||
db.Units.Upsert(u);
|
db.Units.Upsert(u);
|
||||||
db.OnChange?.Invoke(nameof(Unit), u.Id.ToString(), "Save", u);
|
db.OnChange?.Invoke(nameof(Unit), u.Id.ToString(), "Save", u);
|
||||||
|
|||||||
@@ -8,6 +8,37 @@ namespace LehrerApp.Desktop.Tests;
|
|||||||
|
|
||||||
public sealed class ClassTeacherViewModelsTests
|
public sealed class ClassTeacherViewModelsTests
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Monatskalender_KodiertFehlzeitenUndBlendetHausaufgabenStandardmaessigAus()
|
||||||
|
{
|
||||||
|
var month = new DateOnly(2026, 9, 1);
|
||||||
|
var absences = new[]
|
||||||
|
{
|
||||||
|
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 9, 2), "Müller Ada", 1, 0, 12,
|
||||||
|
["Deu"], [1], ["entsch."], ["Verspätung"], null, null, false),
|
||||||
|
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 9, 3), "Schmidt Ben", 2, 2, 90,
|
||||||
|
["Mathe"], [1, 2], ["nicht entsch."], ["Absent"], null, null, false),
|
||||||
|
};
|
||||||
|
var register = new[]
|
||||||
|
{
|
||||||
|
new ClassTeacherClassRegisterRow(new DateOnly(2026, 9, 3), "Deu", "Schmidt Ben", "test",
|
||||||
|
"Störung", "Negativ", "Unterricht massiv gestört"),
|
||||||
|
new ClassTeacherClassRegisterRow(new DateOnly(2026, 9, 4), "Mathe", "Müller Ada", "test",
|
||||||
|
"Hausaufgaben", "Negativ", "Hausaufgaben fehlen"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var days = ClassTeacherDetailsViewModel.BuildCalendarDays(month, absences, register, includeHomework: false);
|
||||||
|
|
||||||
|
Assert.Equal(0, days.Count % 5);
|
||||||
|
Assert.Contains(days.SelectMany(d => d.Events), e => e.Code == "V");
|
||||||
|
Assert.Contains(days.SelectMany(d => d.Events), e => e.Code == "U");
|
||||||
|
Assert.Contains(days.SelectMany(d => d.Events), e => e.Code == "!");
|
||||||
|
Assert.DoesNotContain(days.SelectMany(d => d.Events), e => e.Code == "H");
|
||||||
|
|
||||||
|
var withHomework = ClassTeacherDetailsViewModel.BuildCalendarDays(month, absences, register, includeHomework: true);
|
||||||
|
Assert.Contains(withHomework.SelectMany(d => d.Events), e => e.Code == "H");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GroupByStudentAndDay_FasstFehlstundenProSchuelerUndTagZusammen()
|
public void GroupByStudentAndDay_FasstFehlstundenProSchuelerUndTagZusammen()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -45,6 +45,21 @@ public class PlanningTabViewModelTests
|
|||||||
Assert.Contains("2 / 3", summary.ProgressText);
|
Assert.Contains("2 / 3", summary.ProgressText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Initialize_WaehltLaufendeEinheitAlsArbeitskontext()
|
||||||
|
{
|
||||||
|
var (vm, units, _, groupId) = BuildScenario();
|
||||||
|
units.Add(new Unit { GroupId = groupId, Title = "Früher", Status = UnitStatus.Completed,
|
||||||
|
StartDate = new DateOnly(2025, 8, 1) });
|
||||||
|
var active = new Unit { GroupId = groupId, Title = "Aktuell", Status = UnitStatus.Active,
|
||||||
|
StartDate = new DateOnly(2025, 9, 1) };
|
||||||
|
units.Add(active);
|
||||||
|
|
||||||
|
vm.Initialize(groupId);
|
||||||
|
|
||||||
|
Assert.Equal(active.Id, vm.SelectedUnit?.Id);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task MoveLesson_MitNachrueckenVerschiebtNurGeplanteFolgestundenNachDemStichtag()
|
public async Task MoveLesson_MitNachrueckenVerschiebtNurGeplanteFolgestundenNachDemStichtag()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,4 +25,28 @@ public sealed class UnitDialogViewModelTests
|
|||||||
|
|
||||||
Assert.Equal("10c · kein Fach hinterlegt (siehe Lerngruppe)", vm.GroupSubjectDisplay);
|
Assert.Equal("10c · kein Fach hinterlegt (siehe Lerngruppe)", vm.GroupSubjectDisplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Laufend_SetztBisherigeLaufendeEinheitAufAbgeschlossenUndWarntVorher()
|
||||||
|
{
|
||||||
|
var groupId = Guid.NewGuid();
|
||||||
|
var units = new FakeUnits();
|
||||||
|
var previous = new LehrerApp.Core.Models.Unit
|
||||||
|
{
|
||||||
|
GroupId = groupId, Title = "Bruchrechnung", Status = LehrerApp.Core.Models.UnitStatus.Active,
|
||||||
|
};
|
||||||
|
units.Add(previous);
|
||||||
|
var vm = new UnitDialogViewModel(units, new FakeCompetencyDomains(),
|
||||||
|
groupId, null, 6, "6a", "Mathematik", editingUnit: null)
|
||||||
|
{
|
||||||
|
Title = "Prozentrechnung",
|
||||||
|
StatusName = "Laufend",
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.Contains("Bruchrechnung", vm.StatusNotice);
|
||||||
|
vm.SaveCommand.Execute(null);
|
||||||
|
|
||||||
|
Assert.Equal(LehrerApp.Core.Models.UnitStatus.Completed, previous.Status);
|
||||||
|
Assert.Single(units.GetByGroup(groupId), u => u.Status == LehrerApp.Core.Models.UnitStatus.Active);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,24 @@ public sealed record ClassAbsenceDaySummaryRow(DateOnly Date, string StudentName
|
|||||||
DateOnly.TryParseExact(value.ToString(), "yyyyMMdd", out date);
|
DateOnly.TryParseExact(value.ToString(), "yyyyMMdd", out date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ClassTeacherCalendarEventKind { Late, Absent, Unexcused, Excused, ClassRegister, Homework }
|
||||||
|
|
||||||
|
/// <summary>Kompakter, farblich codierter Marker in der Klassenlehrer-Monatsansicht.</summary>
|
||||||
|
public sealed record ClassTeacherCalendarEvent(string StudentName, string Code, string Label,
|
||||||
|
string ColorHex, ClassTeacherCalendarEventKind Kind)
|
||||||
|
{
|
||||||
|
public string Tooltip => $"{StudentName}: {Label}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Ein Werktag im 5-Spalten-Monatsraster; Tage außerhalb des Monats sind Platzhalter.</summary>
|
||||||
|
public sealed record ClassTeacherCalendarDay(DateOnly? Date, IReadOnlyList<ClassTeacherCalendarEvent> Events)
|
||||||
|
{
|
||||||
|
public bool IsPlaceholder => Date is null;
|
||||||
|
public string DayLabel => Date?.ToString("dd.") ?? "";
|
||||||
|
public string WeekdayLabel => Date?.ToString("ddd", System.Globalization.CultureInfo.GetCultureInfo("de-DE")) ?? "";
|
||||||
|
public bool HasEvents => Events.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Details-Ansicht des Klassenlehrer-Bereichs: Klassenbucheinträge, die andere Lehrkräfte zu
|
/// Details-Ansicht des Klassenlehrer-Bereichs: Klassenbucheinträge, die andere Lehrkräfte zu
|
||||||
/// Schülern der Klasse angelegt haben (WebUntis "-alle-"-Bericht, gefiltert auf fremde statt der
|
/// Schülern der Klasse angelegt haben (WebUntis "-alle-"-Bericht, gefiltert auf fremde statt der
|
||||||
@@ -149,6 +167,7 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
|||||||
public ObservableCollection<ClassAbsenceDaySummaryRow> AbsenceEntries { get; } = [];
|
public ObservableCollection<ClassAbsenceDaySummaryRow> AbsenceEntries { get; } = [];
|
||||||
public ObservableCollection<ClassTeacherCategoryAggregateRow> CategoryAggregates { get; } = [];
|
public ObservableCollection<ClassTeacherCategoryAggregateRow> CategoryAggregates { get; } = [];
|
||||||
public ObservableCollection<DocumentationItem> OwnDocumentationEntries { get; } = [];
|
public ObservableCollection<DocumentationItem> OwnDocumentationEntries { get; } = [];
|
||||||
|
public ObservableCollection<ClassTeacherCalendarDay> CalendarDays { get; } = [];
|
||||||
|
|
||||||
/// Für das Kontextmenü "→ An Vorgang anheften" (DataGrid.SelectedItem, zweigleisig gebunden).
|
/// Für das Kontextmenü "→ An Vorgang anheften" (DataGrid.SelectedItem, zweigleisig gebunden).
|
||||||
[ObservableProperty] private ClassTeacherClassRegisterRow? _selectedEntry;
|
[ObservableProperty] private ClassTeacherClassRegisterRow? _selectedEntry;
|
||||||
@@ -160,6 +179,9 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
|||||||
/// Von der Übersicht gesetzt (Klick auf eine Roster-Zeile) - leer zeigt alle Schüler*innen.
|
/// Von der Übersicht gesetzt (Klick auf eine Roster-Zeile) - leer zeigt alle Schüler*innen.
|
||||||
[ObservableProperty] private string _studentFilter = "";
|
[ObservableProperty] private string _studentFilter = "";
|
||||||
[ObservableProperty] private int _quickRangeIndex = 1;
|
[ObservableProperty] private int _quickRangeIndex = 1;
|
||||||
|
[ObservableProperty] private bool _showMonthlyCalendar;
|
||||||
|
[ObservableProperty] private bool _includeHomeworkInCalendar;
|
||||||
|
[ObservableProperty] private DateOnly _calendarMonth = new(DateTime.Today.Year, DateTime.Today.Month, 1);
|
||||||
/// Umschalter Klassenbuch (WebUntis, andere Lehrkräfte) ↔ eigene Dokumentation.
|
/// Umschalter Klassenbuch (WebUntis, andere Lehrkräfte) ↔ eigene Dokumentation.
|
||||||
[ObservableProperty] private bool _showOwnDocumentation;
|
[ObservableProperty] private bool _showOwnDocumentation;
|
||||||
[ObservableProperty] private int _ownDocumentationCount;
|
[ObservableProperty] private int _ownDocumentationCount;
|
||||||
@@ -174,6 +196,7 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
|||||||
|
|
||||||
public bool HasEntries => Entries.Count > 0;
|
public bool HasEntries => Entries.Count > 0;
|
||||||
public bool HasAbsenceEntries => AbsenceEntries.Count > 0;
|
public bool HasAbsenceEntries => AbsenceEntries.Count > 0;
|
||||||
|
public bool ShowAbsenceListEmpty => !ShowMonthlyCalendar && !HasAbsenceEntries;
|
||||||
public bool HasCategoryAggregates => CategoryAggregates.Count > 0;
|
public bool HasCategoryAggregates => CategoryAggregates.Count > 0;
|
||||||
public bool HasOwnDocumentationEntries => OwnDocumentationEntries.Count > 0;
|
public bool HasOwnDocumentationEntries => OwnDocumentationEntries.Count > 0;
|
||||||
/// Die Kategorien-Chipreihe fasst nur WebUntis-Kategorien zusammen (<see cref="CategoryAggregates"/>)
|
/// Die Kategorien-Chipreihe fasst nur WebUntis-Kategorien zusammen (<see cref="CategoryAggregates"/>)
|
||||||
@@ -182,6 +205,8 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
|||||||
public int UntisCriticalCount => Entries.Count(e => e.IsDangerStatus);
|
public int UntisCriticalCount => Entries.Count(e => e.IsDangerStatus);
|
||||||
public string ActiveFilterLabel => string.IsNullOrWhiteSpace(StudentFilter)
|
public string ActiveFilterLabel => string.IsNullOrWhiteSpace(StudentFilter)
|
||||||
? "Alle Schüler*innen" : StudentFilter;
|
? "Alle Schüler*innen" : StudentFilter;
|
||||||
|
public string CalendarMonthLabel => CalendarMonth.ToString("MMMM yyyy",
|
||||||
|
System.Globalization.CultureInfo.GetCultureInfo("de-DE"));
|
||||||
|
|
||||||
public ClassTeacherDetailsViewModel(UntisReportCacheService cache, IDocumentationRepository documentation,
|
public ClassTeacherDetailsViewModel(UntisReportCacheService cache, IDocumentationRepository documentation,
|
||||||
IStudentRepository students, ClassTeacherCasesViewModel cases)
|
IStudentRepository students, ClassTeacherCasesViewModel cases)
|
||||||
@@ -198,6 +223,7 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
|||||||
StudentFilter = "";
|
StudentFilter = "";
|
||||||
Entries.Clear();
|
Entries.Clear();
|
||||||
AbsenceEntries.Clear();
|
AbsenceEntries.Clear();
|
||||||
|
CalendarDays.Clear();
|
||||||
CategoryAggregates.Clear();
|
CategoryAggregates.Clear();
|
||||||
OwnDocumentationEntries.Clear();
|
OwnDocumentationEntries.Clear();
|
||||||
_rosterMatches = [];
|
_rosterMatches = [];
|
||||||
@@ -208,6 +234,9 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
|||||||
partial void OnStudentFilterChanged(string value) => OnPropertyChanged(nameof(ActiveFilterLabel));
|
partial void OnStudentFilterChanged(string value) => OnPropertyChanged(nameof(ActiveFilterLabel));
|
||||||
|
|
||||||
partial void OnShowOwnDocumentationChanged(bool value) => OnPropertyChanged(nameof(ShowCategoryAggregates));
|
partial void OnShowOwnDocumentationChanged(bool value) => OnPropertyChanged(nameof(ShowCategoryAggregates));
|
||||||
|
partial void OnShowMonthlyCalendarChanged(bool value) => OnPropertyChanged(nameof(ShowAbsenceListEmpty));
|
||||||
|
partial void OnIncludeHomeworkInCalendarChanged(bool value) => BuildCalendar();
|
||||||
|
partial void OnCalendarMonthChanged(DateOnly value) => OnPropertyChanged(nameof(CalendarMonthLabel));
|
||||||
|
|
||||||
partial void OnQuickRangeIndexChanged(int value)
|
partial void OnQuickRangeIndexChanged(int value)
|
||||||
{
|
{
|
||||||
@@ -230,6 +259,37 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
|||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private Task ApplyFilter() => LoadInternal(forceRefresh: false);
|
private Task ApplyFilter() => LoadInternal(forceRefresh: false);
|
||||||
|
|
||||||
|
[RelayCommand] private void ShowAbsenceList() => ShowMonthlyCalendar = false;
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ShowCalendar()
|
||||||
|
{
|
||||||
|
ShowMonthlyCalendar = true;
|
||||||
|
await LoadCalendarMonth(forceRefresh: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task PreviousCalendarMonth()
|
||||||
|
{
|
||||||
|
CalendarMonth = CalendarMonth.AddMonths(-1);
|
||||||
|
await LoadCalendarMonth(forceRefresh: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task NextCalendarMonth()
|
||||||
|
{
|
||||||
|
CalendarMonth = CalendarMonth.AddMonths(1);
|
||||||
|
await LoadCalendarMonth(forceRefresh: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task LoadCalendarMonth(bool forceRefresh)
|
||||||
|
{
|
||||||
|
StartDate = new DateTimeOffset(CalendarMonth.ToDateTime(TimeOnly.MinValue));
|
||||||
|
var end = CalendarMonth.AddMonths(1).AddDays(-1);
|
||||||
|
EndDate = new DateTimeOffset(end.ToDateTime(TimeOnly.MinValue));
|
||||||
|
return LoadInternal(forceRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task ClearStudentFilter()
|
private async Task ClearStudentFilter()
|
||||||
{
|
{
|
||||||
@@ -270,6 +330,8 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
|||||||
foreach (var row in ClassAbsenceDaySummaryRow.GroupByStudentAndDay(absences))
|
foreach (var row in ClassAbsenceDaySummaryRow.GroupByStudentAndDay(absences))
|
||||||
AbsenceEntries.Add(row);
|
AbsenceEntries.Add(row);
|
||||||
|
|
||||||
|
BuildCalendar();
|
||||||
|
|
||||||
var localStudents = _students.GetAll();
|
var localStudents = _students.GetAll();
|
||||||
_rosterMatches = rosterTask.Result
|
_rosterMatches = rosterTask.Result
|
||||||
.Select(r => (Roster: r, Student: ClassTeacherOverviewViewModel.MatchStudent(r.DisplayName, localStudents)))
|
.Select(r => (Roster: r, Student: ClassTeacherOverviewViewModel.MatchStudent(r.DisplayName, localStudents)))
|
||||||
@@ -289,6 +351,67 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
|||||||
finally { Busy = false; NotifyListState(); }
|
finally { Busy = false; NotifyListState(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BuildCalendar()
|
||||||
|
{
|
||||||
|
CalendarDays.Clear();
|
||||||
|
foreach (var day in BuildCalendarDays(CalendarMonth, AbsenceEntries, Entries, IncludeHomeworkInCalendar))
|
||||||
|
CalendarDays.Add(day);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Baut ein Montag-bis-Freitag-Raster aus den ohnehin geladenen Berichten. Negative
|
||||||
|
/// Klassenbucheinträge erscheinen als !; Hausaufgaben sind eine bewusst optionale Sonderlage.</summary>
|
||||||
|
public static IReadOnlyList<ClassTeacherCalendarDay> BuildCalendarDays(DateOnly month,
|
||||||
|
IReadOnlyList<ClassAbsenceDaySummaryRow> absences,
|
||||||
|
IReadOnlyList<ClassTeacherClassRegisterRow> registerEntries, bool includeHomework)
|
||||||
|
{
|
||||||
|
var first = new DateOnly(month.Year, month.Month, 1);
|
||||||
|
var last = first.AddMonths(1).AddDays(-1);
|
||||||
|
var gridStart = first.AddDays(-(((int)first.DayOfWeek + 6) % 7));
|
||||||
|
var gridEnd = last.AddDays((7 - (int)last.DayOfWeek) % 7);
|
||||||
|
var result = new List<ClassTeacherCalendarDay>();
|
||||||
|
|
||||||
|
for (var date = gridStart; date <= gridEnd; date = date.AddDays(1))
|
||||||
|
{
|
||||||
|
if (date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday) continue;
|
||||||
|
if (date.Month != first.Month)
|
||||||
|
{
|
||||||
|
result.Add(new ClassTeacherCalendarDay(null, []));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var events = new List<ClassTeacherCalendarEvent>();
|
||||||
|
foreach (var absence in absences.Where(a => a.Date == date).OrderBy(a => a.StudentDisplayName))
|
||||||
|
{
|
||||||
|
var (code, label, color, kind) = absence.IsUnexcused
|
||||||
|
? ("U", "Unentschuldigt gefehlt", "#C62828", ClassTeacherCalendarEventKind.Unexcused)
|
||||||
|
: absence.IsLate
|
||||||
|
? ("V", "Verspätet", "#EF6C00", ClassTeacherCalendarEventKind.Late)
|
||||||
|
: absence.FriendlyStatusLabel.Contains("Entschuldigt", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? ("E", "Entschuldigt gefehlt", "#2E7D32", ClassTeacherCalendarEventKind.Excused)
|
||||||
|
: ("A", "Abwesend", "#1565C0", ClassTeacherCalendarEventKind.Absent);
|
||||||
|
events.Add(new ClassTeacherCalendarEvent(absence.StudentDisplayName, code, label, color, kind));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entry in registerEntries.Where(e => e.Date == date).OrderBy(e => e.StudentDisplayName))
|
||||||
|
{
|
||||||
|
var isHomework = ContainsHomework(entry);
|
||||||
|
if (isHomework && !includeHomework) continue;
|
||||||
|
if (!entry.IsDangerStatus && !isHomework) continue;
|
||||||
|
events.Add(new ClassTeacherCalendarEvent(entry.StudentDisplayName,
|
||||||
|
isHomework ? "H" : "!", isHomework ? "Hausaufgaben" : "Negativer Klassenbucheintrag",
|
||||||
|
isHomework ? "#6A1B9A" : "#8E24AA",
|
||||||
|
isHomework ? ClassTeacherCalendarEventKind.Homework : ClassTeacherCalendarEventKind.ClassRegister));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(new ClassTeacherCalendarDay(date, events));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ContainsHomework(ClassTeacherClassRegisterRow entry) =>
|
||||||
|
entry.CategoryName?.Contains("Hausauf", StringComparison.OrdinalIgnoreCase) == true ||
|
||||||
|
entry.Text?.Contains("Hausauf", StringComparison.OrdinalIgnoreCase) == true;
|
||||||
|
|
||||||
/// Baut <see cref="OwnDocumentationEntries"/> aus <see cref="_rosterMatches"/> und dem zuletzt
|
/// Baut <see cref="OwnDocumentationEntries"/> aus <see cref="_rosterMatches"/> und dem zuletzt
|
||||||
/// geladenen Zeitraum neu auf — separat von <see cref="LoadInternal"/>, damit Anlegen/Bearbeiten/
|
/// geladenen Zeitraum neu auf — separat von <see cref="LoadInternal"/>, damit Anlegen/Bearbeiten/
|
||||||
/// Löschen eines eigenen Eintrags nicht auch die WebUntis-Berichte neu abruft. Die eigentliche
|
/// Löschen eines eigenen Eintrags nicht auch die WebUntis-Berichte neu abruft. Die eigentliche
|
||||||
@@ -419,6 +542,7 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(HasEntries));
|
OnPropertyChanged(nameof(HasEntries));
|
||||||
OnPropertyChanged(nameof(HasAbsenceEntries));
|
OnPropertyChanged(nameof(HasAbsenceEntries));
|
||||||
|
OnPropertyChanged(nameof(ShowAbsenceListEmpty));
|
||||||
OnPropertyChanged(nameof(HasCategoryAggregates));
|
OnPropertyChanged(nameof(HasCategoryAggregates));
|
||||||
OnPropertyChanged(nameof(ShowCategoryAggregates));
|
OnPropertyChanged(nameof(ShowCategoryAggregates));
|
||||||
OnPropertyChanged(nameof(HasOwnDocumentationEntries));
|
OnPropertyChanged(nameof(HasOwnDocumentationEntries));
|
||||||
|
|||||||
@@ -102,10 +102,10 @@ public partial class PlanningTabViewModel : ObservableObject
|
|||||||
GradeLevel = group?.GradeLevel ?? 0;
|
GradeLevel = group?.GradeLevel ?? 0;
|
||||||
SubjectName = SubjectId is Guid sid ? _subjects.GetById(sid)?.Name ?? "" : "";
|
SubjectName = SubjectId is Guid sid ? _subjects.GetById(sid)?.Name ?? "" : "";
|
||||||
GroupLabel = group?.Name ?? "";
|
GroupLabel = group?.Name ?? "";
|
||||||
LoadUnits();
|
LoadUnits(preferActive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadUnits()
|
private void LoadUnits(bool preferActive = false)
|
||||||
{
|
{
|
||||||
var selectedId = SelectedUnit?.Id;
|
var selectedId = SelectedUnit?.Id;
|
||||||
Units.Clear();
|
Units.Clear();
|
||||||
@@ -128,7 +128,11 @@ public partial class PlanningTabViewModel : ObservableObject
|
|||||||
KnownMaterials = materials.OrderBy(m => m, StringComparer.CurrentCultureIgnoreCase).ToList();
|
KnownMaterials = materials.OrderBy(m => m, StringComparer.CurrentCultureIgnoreCase).ToList();
|
||||||
KnownShorthands = shorthands.OrderBy(s => s, StringComparer.CurrentCultureIgnoreCase).ToList();
|
KnownShorthands = shorthands.OrderBy(s => s, StringComparer.CurrentCultureIgnoreCase).ToList();
|
||||||
|
|
||||||
SelectedUnit = Units.FirstOrDefault(u => u.Id == selectedId) ?? Units.FirstOrDefault();
|
// Beim ersten Öffnen bzw. beim Wechsel aus dem Stundenplan steht die laufende Einheit im
|
||||||
|
// Fokus. Eine bewusste Auswahl innerhalb derselben Gruppe bleibt bei Refreshes erhalten.
|
||||||
|
SelectedUnit = !preferActive && selectedId is not null
|
||||||
|
? Units.FirstOrDefault(u => u.Id == selectedId) ?? Units.FirstOrDefault(u => u.Status == UnitStatus.Active) ?? Units.FirstOrDefault()
|
||||||
|
: Units.FirstOrDefault(u => u.Status == UnitStatus.Active) ?? Units.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSelectedUnitChanged(UnitSummary? value)
|
partial void OnSelectedUnitChanged(UnitSummary? value)
|
||||||
@@ -574,6 +578,7 @@ public partial class UnitDialogViewModel : ObservableObject
|
|||||||
[ObservableProperty] private string _startDateText = "";
|
[ObservableProperty] private string _startDateText = "";
|
||||||
[ObservableProperty] private string _endDateText = "";
|
[ObservableProperty] private string _endDateText = "";
|
||||||
[ObservableProperty] private string _statusName = UnitStatusDisplay.Options[0];
|
[ObservableProperty] private string _statusName = UnitStatusDisplay.Options[0];
|
||||||
|
[ObservableProperty] private string _statusNotice = "";
|
||||||
[ObservableProperty] private string _notes = "";
|
[ObservableProperty] private string _notes = "";
|
||||||
[ObservableProperty] private bool _isCompetencyPanelOpen;
|
[ObservableProperty] private bool _isCompetencyPanelOpen;
|
||||||
[ObservableProperty] private string _titleError = "";
|
[ObservableProperty] private string _titleError = "";
|
||||||
@@ -594,6 +599,20 @@ public partial class UnitDialogViewModel : ObservableObject
|
|||||||
public string DialogTitle => _editingUnit is null ? "Neue Einheit anlegen" : "Einheit bearbeiten";
|
public string DialogTitle => _editingUnit is null ? "Neue Einheit anlegen" : "Einheit bearbeiten";
|
||||||
public string SaveButtonText => _editingUnit is null ? "Anlegen" : "Speichern";
|
public string SaveButtonText => _editingUnit is null ? "Anlegen" : "Speichern";
|
||||||
|
|
||||||
|
partial void OnStatusNameChanged(string value)
|
||||||
|
{
|
||||||
|
if (UnitStatusDisplay.FromName(value) != UnitStatus.Active)
|
||||||
|
{
|
||||||
|
StatusNotice = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var previous = _units.GetByGroup(_groupId)
|
||||||
|
.FirstOrDefault(u => u.Status == UnitStatus.Active && u.Id != _editingUnit?.Id);
|
||||||
|
StatusNotice = previous is null ? ""
|
||||||
|
: $"„{previous.Title}“ wird beim Speichern automatisch abgeschlossen.";
|
||||||
|
}
|
||||||
|
|
||||||
public UnitDialogViewModel(IUnitRepository units, ICompetencyDomainRepository competencyDomains,
|
public UnitDialogViewModel(IUnitRepository units, ICompetencyDomainRepository competencyDomains,
|
||||||
Guid groupId, Guid? subjectId, int gradeLevel, string groupName, string subjectName, Unit? editingUnit)
|
Guid groupId, Guid? subjectId, int gradeLevel, string groupName, string subjectName, Unit? editingUnit)
|
||||||
{
|
{
|
||||||
@@ -679,6 +698,17 @@ public partial class UnitDialogViewModel : ObservableObject
|
|||||||
Result.Status = UnitStatusDisplay.FromName(StatusName);
|
Result.Status = UnitStatusDisplay.FromName(StatusName);
|
||||||
Result.Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes.Trim();
|
Result.Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes.Trim();
|
||||||
Result.Competencies = _competencyCodes;
|
Result.Competencies = _competencyCodes;
|
||||||
|
if (Result.Status == UnitStatus.Active)
|
||||||
|
{
|
||||||
|
// Pro Lerngruppe gibt es genau einen aktuellen Arbeitskontext. Frühere laufende
|
||||||
|
// Einheiten werden nicht verworfen, sondern fachlich sauber abgeschlossen.
|
||||||
|
foreach (var previous in _units.GetByGroup(_groupId)
|
||||||
|
.Where(u => u.Status == UnitStatus.Active && u.Id != Result.Id))
|
||||||
|
{
|
||||||
|
previous.Status = UnitStatus.Completed;
|
||||||
|
_units.Save(previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
_units.Save(Result);
|
_units.Save(Result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,20 @@
|
|||||||
<Style Selector="TextBlock.status.danger">
|
<Style Selector="TextBlock.status.danger">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource AppStatusDangerBrush}"/>
|
<Setter Property="Foreground" Value="{DynamicResource AppStatusDangerBrush}"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="Button.viewMode">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource AppFilterBorderBrush}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Padding" Value="12,6"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.viewMode.active">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource AppFilterActiveBackgroundBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource AppFilterActiveBorderBrush}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource AppFilterActiveForegroundBrush}"/>
|
||||||
|
</Style>
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
|
|
||||||
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="16" RowSpacing="10">
|
<Grid RowDefinitions="Auto,Auto,Auto,*,Auto" Margin="16" RowSpacing="10">
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto">
|
<Grid Grid.Row="0" ColumnDefinitions="*,Auto">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="Fehlzeiten" FontSize="22" FontWeight="SemiBold"/>
|
<TextBlock Text="Fehlzeiten" FontSize="22" FontWeight="SemiBold"/>
|
||||||
@@ -39,7 +50,20 @@
|
|||||||
IsEnabled="{Binding !Busy}" Background="Transparent" Foreground="{DynamicResource AppAccentTextBrush}" VerticalAlignment="Center"/>
|
IsEnabled="{Binding !Busy}" Background="Transparent" Foreground="{DynamicResource AppAccentTextBrush}" VerticalAlignment="Center"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Border Grid.Row="1" Classes="filterCard">
|
<Grid Grid.Row="1" ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto" ColumnSpacing="6">
|
||||||
|
<Button Grid.Column="0" Content="Liste" Classes="viewMode" Classes.active="{Binding !ShowMonthlyCalendar}"
|
||||||
|
Command="{Binding ShowAbsenceListCommand}"/>
|
||||||
|
<Button Grid.Column="1" Content="Monatsübersicht" Classes="viewMode" Classes.active="{Binding ShowMonthlyCalendar}"
|
||||||
|
Command="{Binding ShowCalendarCommand}"/>
|
||||||
|
<Button Grid.Column="3" Content="‹" Width="36" Command="{Binding PreviousCalendarMonthCommand}"
|
||||||
|
IsVisible="{Binding ShowMonthlyCalendar}" AutomationProperties.Name="Vorheriger Monat"/>
|
||||||
|
<TextBlock Grid.Column="4" Text="{Binding CalendarMonthLabel}" FontSize="15" FontWeight="SemiBold"
|
||||||
|
VerticalAlignment="Center" Margin="8,0" IsVisible="{Binding ShowMonthlyCalendar}"/>
|
||||||
|
<Button Grid.Column="5" Content="›" Width="36" Command="{Binding NextCalendarMonthCommand}"
|
||||||
|
IsVisible="{Binding ShowMonthlyCalendar}" AutomationProperties.Name="Nächster Monat"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Border Grid.Row="2" Classes="filterCard" IsVisible="{Binding !ShowMonthlyCalendar}">
|
||||||
<Grid RowDefinitions="Auto,Auto" RowSpacing="8">
|
<Grid RowDefinitions="Auto,Auto" RowSpacing="8">
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="155,Auto,*,Auto,*" ColumnSpacing="8">
|
<Grid Grid.Row="0" ColumnDefinitions="155,Auto,*,Auto,*" ColumnSpacing="8">
|
||||||
<ComboBox Grid.Column="0" SelectedIndex="{Binding QuickRangeIndex, Mode=TwoWay}">
|
<ComboBox Grid.Column="0" SelectedIndex="{Binding QuickRangeIndex, Mode=TwoWay}">
|
||||||
@@ -59,10 +83,11 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<Grid Grid.Row="2">
|
<Grid Grid.Row="3">
|
||||||
<DataGrid ItemsSource="{Binding AbsenceEntries}" AutoGenerateColumns="False" IsReadOnly="True"
|
<DataGrid ItemsSource="{Binding AbsenceEntries}" AutoGenerateColumns="False" IsReadOnly="True"
|
||||||
GridLinesVisibility="Horizontal" BorderBrush="{DynamicResource AppCardBorderBrush}" BorderThickness="1"
|
GridLinesVisibility="Horizontal" BorderBrush="{DynamicResource AppCardBorderBrush}" BorderThickness="1"
|
||||||
CanUserResizeColumns="True" CanUserSortColumns="True" RowHeight="48">
|
CanUserResizeColumns="True" CanUserSortColumns="True" RowHeight="48"
|
||||||
|
IsVisible="{Binding !ShowMonthlyCalendar}">
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn Header="Datum" Binding="{Binding DateLabel}" Width="105"/>
|
<DataGridTextColumn Header="Datum" Binding="{Binding DateLabel}" Width="105"/>
|
||||||
<DataGridTextColumn Header="Schüler*in" Binding="{Binding StudentDisplayName}" Width="1.3*"/>
|
<DataGridTextColumn Header="Schüler*in" Binding="{Binding StudentDisplayName}" Width="1.3*"/>
|
||||||
@@ -92,11 +117,61 @@
|
|||||||
<DataGridTextColumn Header="Grund / Notiz" Binding="{Binding DetailLabel}" Width="2*"/>
|
<DataGridTextColumn Header="Grund / Notiz" Binding="{Binding DetailLabel}" Width="2*"/>
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
<StackPanel IsVisible="{Binding !HasAbsenceEntries}" HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="5">
|
<StackPanel IsVisible="{Binding ShowAbsenceListEmpty}" HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="5">
|
||||||
<TextBlock Text="Keine Fehlzeiten im gewählten Zeitraum" FontWeight="SemiBold"/>
|
<TextBlock Text="Keine Fehlzeiten im gewählten Zeitraum" FontWeight="SemiBold"/>
|
||||||
<TextBlock Text="Passe Zeitraum oder Schülerfilter an." FontSize="12" Opacity="0.55"/>
|
<TextBlock Text="Passe Zeitraum oder Schülerfilter an." FontSize="12" Opacity="0.55"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid IsVisible="{Binding ShowMonthlyCalendar}" RowDefinitions="Auto,Auto,*" RowSpacing="8">
|
||||||
|
<WrapPanel Grid.Row="0" ItemSpacing="12" LineSpacing="6">
|
||||||
|
<TextBlock Text="V verspätet" Foreground="#EF6C00" FontSize="11" FontWeight="SemiBold"/>
|
||||||
|
<TextBlock Text="A abwesend" Foreground="#1565C0" FontSize="11" FontWeight="SemiBold"/>
|
||||||
|
<TextBlock Text="U unentschuldigt" Foreground="#C62828" FontSize="11" FontWeight="SemiBold"/>
|
||||||
|
<TextBlock Text="E entschuldigt" Foreground="#2E7D32" FontSize="11" FontWeight="SemiBold"/>
|
||||||
|
<TextBlock Text="! negativer Klassenbucheintrag" Foreground="#8E24AA" FontSize="11" FontWeight="SemiBold"/>
|
||||||
|
<CheckBox Content="Hausaufgaben (H) einblenden" IsChecked="{Binding IncludeHomeworkInCalendar}"
|
||||||
|
FontSize="11" VerticalAlignment="Center"/>
|
||||||
|
</WrapPanel>
|
||||||
|
<Grid Grid.Row="1" ColumnDefinitions="*,*,*,*,*" Margin="0,2,0,0">
|
||||||
|
<TextBlock Grid.Column="0" Text="Montag" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||||
|
<TextBlock Grid.Column="1" Text="Dienstag" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||||
|
<TextBlock Grid.Column="2" Text="Mittwoch" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||||
|
<TextBlock Grid.Column="3" Text="Donnerstag" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||||
|
<TextBlock Grid.Column="4" Text="Freitag" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
<ScrollViewer Grid.Row="2">
|
||||||
|
<ItemsControl ItemsSource="{Binding CalendarDays}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate><UniformGrid Columns="5"/></ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:ClassTeacherCalendarDay">
|
||||||
|
<Border BorderBrush="{DynamicResource AppCardBorderBrush}" BorderThickness="1"
|
||||||
|
Background="{DynamicResource AppCardBackgroundBrush}" MinHeight="112" Padding="7" Margin="2">
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<TextBlock Text="{Binding DayLabel}" FontWeight="SemiBold" HorizontalAlignment="Right"/>
|
||||||
|
<ItemsControl ItemsSource="{Binding Events}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:ClassTeacherCalendarEvent">
|
||||||
|
<Grid ColumnDefinitions="22,*" Margin="0,1" ToolTip.Tip="{Binding Tooltip}">
|
||||||
|
<Border Width="18" Height="18" CornerRadius="9" Background="{Binding ColorHex}">
|
||||||
|
<TextBlock Text="{Binding Code}" Foreground="White" FontSize="10" FontWeight="Bold"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<TextBlock Grid.Column="1" Text="{Binding StudentName}" FontSize="10"
|
||||||
|
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<TextBlock Grid.Row="3" Text="{Binding Status}" FontSize="11" Opacity="0.6"/>
|
<TextBlock Grid.Row="4" Text="{Binding Status}" FontSize="11" Opacity="0.6"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -6,9 +6,10 @@
|
|||||||
|
|
||||||
<Grid RowDefinitions="Auto,2*,Auto,Auto,2*" Margin="16">
|
<Grid RowDefinitions="Auto,2*,Auto,Auto,2*" Margin="16">
|
||||||
|
|
||||||
<!-- Einheiten-Toolbar -->
|
<!-- Einheiten-Toolbar: Aktionen bewusst unter der Überschrift und in umbruchfähigen
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto" Margin="0,0,0,8">
|
Gruppen. So kollidieren Beschriftung und Buttons auch in schmaleren Fenstern nicht. -->
|
||||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
<Grid Grid.Row="0" RowDefinitions="Auto,Auto" Margin="0,0,0,8" RowSpacing="6">
|
||||||
|
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||||
<TextBlock Text="Unterrichtseinheiten" FontSize="14" FontWeight="SemiBold"
|
<TextBlock Text="Unterrichtseinheiten" FontSize="14" FontWeight="SemiBold"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<Button Content="↕ JSON" FontSize="11" Padding="7,3"
|
<Button Content="↕ JSON" FontSize="11" Padding="7,3"
|
||||||
@@ -30,13 +31,16 @@
|
|||||||
</Button.Flyout>
|
</Button.Flyout>
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
<WrapPanel Grid.Row="1" ItemSpacing="6" LineSpacing="6">
|
||||||
<Button Content="+ Einheit" Command="{Binding AddUnitCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
<Button Content="+ Einheit" Command="{Binding AddUnitCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
||||||
<Button Content="Bearbeiten" Command="{Binding EditUnitCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
<Button Content="✎" Width="38" Command="{Binding EditUnitCommand}" IsEnabled="{Binding !IsReadOnly}"
|
||||||
<Button Content="Als Vorlage kopieren" Command="{Binding CopyUnitCommand}"/>
|
ToolTip.Tip="Ausgewählte Einheit bearbeiten" AutomationProperties.Name="Einheit bearbeiten"/>
|
||||||
<Button Content="Löschen" Command="{Binding DeleteUnitCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
<Button Content="⧉" Width="38" Command="{Binding CopyUnitCommand}"
|
||||||
<Button Content="🤖 KI-Unterstützung" Command="{Binding AiAssistCommand}"/>
|
ToolTip.Tip="Als Vorlage in eine andere Lerngruppe kopieren" AutomationProperties.Name="Einheit als Vorlage kopieren"/>
|
||||||
</StackPanel>
|
<Button Content="⌫" Width="38" Command="{Binding DeleteUnitCommand}" IsEnabled="{Binding !IsReadOnly}"
|
||||||
|
ToolTip.Tip="Ausgewählte Einheit löschen" AutomationProperties.Name="Einheit löschen"/>
|
||||||
|
<Button Content="✦ KI-Unterstützung" Command="{Binding AiAssistCommand}"/>
|
||||||
|
</WrapPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Einheiten-Tabelle -->
|
<!-- Einheiten-Tabelle -->
|
||||||
@@ -76,30 +80,34 @@
|
|||||||
|
|
||||||
<Separator Grid.Row="2" Margin="0,0,0,8"/>
|
<Separator Grid.Row="2" Margin="0,0,0,8"/>
|
||||||
|
|
||||||
<!-- Stunden-Toolbar -->
|
<!-- Stunden-Toolbar: häufige Aktionen bleiben beschriftet, sekundäre Aktionen sind als
|
||||||
<Grid Grid.Row="3" ColumnDefinitions="*,Auto" Margin="0,0,0,8">
|
kompakte Symbolbuttons gruppiert. Das WrapPanel darf bei Bedarf umbrechen. -->
|
||||||
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center">
|
<Grid Grid.Row="3" RowDefinitions="Auto,Auto" Margin="0,0,0,8" RowSpacing="6">
|
||||||
|
<StackPanel Grid.Row="0" Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
<TextBlock Text="Stunden" FontSize="14" FontWeight="SemiBold"/>
|
<TextBlock Text="Stunden" FontSize="14" FontWeight="SemiBold"/>
|
||||||
<TextBlock Text="{Binding SelectedUnitTitleSuffix}" FontSize="14" FontWeight="SemiBold"/>
|
<TextBlock Text="{Binding SelectedUnitTitleSuffix}" FontSize="14" FontWeight="SemiBold"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
<WrapPanel Grid.Row="1" ItemSpacing="6" LineSpacing="6">
|
||||||
<Button Content="+ Stunde" Command="{Binding AddLessonCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
<Button Content="+ Stunde" Command="{Binding AddLessonCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
||||||
<Button Content="Serie erzeugen" Command="{Binding GenerateLessonSeriesCommand}"
|
<Button Content="+ Serie" Command="{Binding GenerateLessonSeriesCommand}"
|
||||||
IsEnabled="{Binding !IsReadOnly}"
|
IsEnabled="{Binding !IsReadOnly}"
|
||||||
ToolTip.Tip="Stunden für alle Termine aus dem Stundenplan im gewählten Zeitraum anlegen."/>
|
ToolTip.Tip="Stunden für alle Termine aus dem Stundenplan im gewählten Zeitraum anlegen."/>
|
||||||
<Button Content="Anzeigen" Command="{Binding ShowLessonCommand}"
|
<Button Content="Anzeigen" Command="{Binding ShowLessonCommand}"
|
||||||
ToolTip.Tip="Verlaufsplan schreibgeschützt und größer anzeigen — zum Mitnehmen in den Unterricht."/>
|
ToolTip.Tip="Verlaufsplan schreibgeschützt und größer anzeigen — zum Mitnehmen in den Unterricht."/>
|
||||||
<Button Content="Bearbeiten" Command="{Binding EditLessonCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
<Button Content="✎" Width="38" Command="{Binding EditLessonCommand}" IsEnabled="{Binding !IsReadOnly}"
|
||||||
<Button Content="Verschieben" Command="{Binding MoveLessonCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
ToolTip.Tip="Ausgewählte Stunde bearbeiten" AutomationProperties.Name="Stunde bearbeiten"/>
|
||||||
<Button Content="Einheit wechseln" Command="{Binding ChangeLessonUnitCommand}" IsEnabled="{Binding !IsReadOnly}"
|
<Button Content="↔" Width="38" Command="{Binding MoveLessonCommand}" IsEnabled="{Binding !IsReadOnly}"
|
||||||
|
ToolTip.Tip="Ausgewählte Stunde verschieben" AutomationProperties.Name="Stunde verschieben"/>
|
||||||
|
<Button Content="⇄" Width="38" Command="{Binding ChangeLessonUnitCommand}" IsEnabled="{Binding !IsReadOnly}"
|
||||||
ToolTip.Tip="Ordnet die ausgewählte Stunde einer anderen Einheit derselben Gruppe zu."/>
|
ToolTip.Tip="Ordnet die ausgewählte Stunde einer anderen Einheit derselben Gruppe zu."/>
|
||||||
<Button Content="Status weiter" Command="{Binding AdvanceLessonStatusCommand}" IsEnabled="{Binding !IsReadOnly}"
|
<Button Content="Status ›" Command="{Binding AdvanceLessonStatusCommand}" IsEnabled="{Binding !IsReadOnly}"
|
||||||
ToolTip.Tip="Entwurf/Geplant → Bereit → Durchgeführt"/>
|
ToolTip.Tip="Entwurf/Geplant → Bereit → Durchgeführt"/>
|
||||||
<Button Content="Sitzung erzeugen" Command="{Binding CreateParticipationSessionCommand}"
|
<Button Content="+ Sitzung" Command="{Binding CreateParticipationSessionCommand}"
|
||||||
IsEnabled="{Binding !IsReadOnly}"
|
IsEnabled="{Binding !IsReadOnly}"
|
||||||
ToolTip.Tip="Legt eine Mitarbeitssitzung mit Datum und Thema dieser Stunde an."/>
|
ToolTip.Tip="Legt eine Mitarbeitssitzung mit Datum und Thema dieser Stunde an."/>
|
||||||
<Button Content="Löschen" Command="{Binding DeleteLessonCommand}" IsEnabled="{Binding !IsReadOnly}"/>
|
<Button Content="⌫" Width="38" Command="{Binding DeleteLessonCommand}" IsEnabled="{Binding !IsReadOnly}"
|
||||||
</StackPanel>
|
ToolTip.Tip="Ausgewählte Stunde löschen" AutomationProperties.Name="Stunde löschen"/>
|
||||||
|
</WrapPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Stunden-Tabelle -->
|
<!-- Stunden-Tabelle -->
|
||||||
|
|||||||
@@ -41,6 +41,10 @@
|
|||||||
<TextBlock Text="Status" FontSize="12" Opacity="0.7"/>
|
<TextBlock Text="Status" FontSize="12" Opacity="0.7"/>
|
||||||
<ComboBox ItemsSource="{Binding StatusOptions}" SelectedItem="{Binding StatusName}"
|
<ComboBox ItemsSource="{Binding StatusOptions}" SelectedItem="{Binding StatusName}"
|
||||||
HorizontalAlignment="Stretch"/>
|
HorizontalAlignment="Stretch"/>
|
||||||
|
<Border Background="#FFF3CD" BorderBrush="#D97706" BorderThickness="1" CornerRadius="5"
|
||||||
|
Padding="9,6" IsVisible="{Binding StatusNotice, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||||
|
<TextBlock Text="{Binding StatusNotice}" Foreground="#92400E" FontSize="11" TextWrapping="Wrap"/>
|
||||||
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
|
|||||||
Reference in New Issue
Block a user