feat: Komfort-Features für Unterrichtsplanung, Zeiterfassung und Dashboard
- Neue-Stunde-Dialog: Datum wird beim Anlegen anhand des Stundenplans
und der letzten Stunde der Einheit vorbelegt statt auf "heute"
(behebt eine falsch erkannte Doppelstunde, wenn "heute" nicht auf
den passenden Wochentag fiel).
- Zeiterfassung: Button "Unterrichtszeit heute übernehmen" schlägt
Start/Ende aus dem heutigen Stundenplan inkl. Puffer davor/danach vor.
- Dashboard: neue Kachel "Ungeplante Stunden" erinnert an Stunden ohne
Thema für heute/morgen, mit Opt-out je Gruppe ("Benötigt
Unterrichtsplanung"), Doppelstunden-Erkennung (keine doppelte Meldung
für die zweite Periode) und Berücksichtigung von Stundenausfall.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,7 @@ public static class TaskCategoryDisplay
|
||||
TaskCategory.Admin => "Verwaltung",
|
||||
TaskCategory.Meeting => "Besprechung",
|
||||
TaskCategory.Other => "Sonstiges",
|
||||
TaskCategory.Teaching => "Unterricht",
|
||||
_ => c.ToString(),
|
||||
};
|
||||
|
||||
@@ -333,6 +334,15 @@ public partial class TimeTrackingViewModel : ObservableObject
|
||||
{
|
||||
private readonly ITimeEntryRepository _entries;
|
||||
private readonly IWorkTaskRepository _tasks;
|
||||
private readonly ITimetableSlotRepository _timetableSlots;
|
||||
private readonly PeriodScheduleService _periodSchedule;
|
||||
|
||||
// Nutzer-Feedback: "man beginnt ja auch vermutlich vor 7:50" (erste Stunde) und "wird auch
|
||||
// nicht aus dem Unterricht nach Hause rennen" (nach der letzten) - grobe, aber plausible
|
||||
// Puffer für den Unterrichtszeit-Vorschlag. Der Vorschlag füllt nur den Nacherfassen-Dialog
|
||||
// vor, gespeichert wird erst nach ausdrücklicher Bestätigung dort (siehe SuggestTeachingTime).
|
||||
private const int BufferBeforeFirstPeriodMinutes = 15;
|
||||
private const int BufferAfterLastPeriodMinutes = 10;
|
||||
|
||||
public const string NoTaskOption = "Keine Aufgabe";
|
||||
|
||||
@@ -351,12 +361,24 @@ public partial class TimeTrackingViewModel : ObservableObject
|
||||
public ObservableCollection<CategoryTimeSummary> CategorySummaries { get; } = [];
|
||||
public string TotalWeekMinutesDisplay => $"{WeekEntries.Sum(e => e.Model.DurationMinutes)} min diese Woche";
|
||||
|
||||
public Func<Task<TimeEntry?>>? OnAddEntry { get; set; }
|
||||
/// Ob heute laut Stundenplan überhaupt Unterricht ansteht - steuert, ob der
|
||||
/// "Unterrichtszeit übernehmen"-Button überhaupt sichtbar ist.
|
||||
public bool HasTeachingTimeSuggestionToday => ComputeTodaysTeachingWindow() is not null;
|
||||
|
||||
public TimeTrackingViewModel(ITimeEntryRepository entries, IWorkTaskRepository tasks)
|
||||
public Func<Task<TimeEntry?>>? OnAddEntry { get; set; }
|
||||
/// Wie OnAddEntry, aber öffnet den Dialog mit vorbefüllter Kategorie "Unterricht" und den
|
||||
/// laut Stundenplan/Stundenraster vorgeschlagenen Beginn-/Ende-Zeiten - eine echte Bestätigung
|
||||
/// im Dialog bleibt aber immer nötig, nichts wird automatisch gespeichert (siehe Puffer-
|
||||
/// Konstanten oben).
|
||||
public Func<TimeOnly, TimeOnly, Task<TimeEntry?>>? OnSuggestTeachingTime { get; set; }
|
||||
|
||||
public TimeTrackingViewModel(ITimeEntryRepository entries, IWorkTaskRepository tasks,
|
||||
ITimetableSlotRepository timetableSlots, PeriodScheduleService periodSchedule)
|
||||
{
|
||||
_entries = entries;
|
||||
_tasks = tasks;
|
||||
_timetableSlots = timetableSlots;
|
||||
_periodSchedule = periodSchedule;
|
||||
Load();
|
||||
}
|
||||
|
||||
@@ -442,6 +464,38 @@ public partial class TimeTrackingViewModel : ObservableObject
|
||||
Refresh();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SuggestTeachingTime()
|
||||
{
|
||||
if (OnSuggestTeachingTime is null || ComputeTodaysTeachingWindow() is not var (start, end)) return;
|
||||
var result = await OnSuggestTeachingTime(start, end);
|
||||
if (result is null) return;
|
||||
_entries.Save(result);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frühester Beginn / spätestes Ende aller heutigen Stundenplan-Perioden (alle Gruppen, nicht
|
||||
/// auf eine einzelne beschränkt - der Unterrichtstag als Ganzes), je um die oben definierten
|
||||
/// Puffer erweitert. Ohne Stundenplan-Eintrag heute oder ohne im Stundenraster hinterlegte
|
||||
/// Zeiten gibt es keinen Vorschlag (null) statt einer erfundenen Zeit.
|
||||
/// </summary>
|
||||
private (TimeOnly Start, TimeOnly End)? ComputeTodaysTeachingWindow()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today).DayOfWeek;
|
||||
var periodTimes = _timetableSlots.GetAll()
|
||||
.Where(s => s.Weekday == today)
|
||||
.Select(s => _periodSchedule.GetTimes(s.PeriodNumber))
|
||||
.Where(t => t is not null)
|
||||
.Select(t => t!.Value)
|
||||
.ToList();
|
||||
if (periodTimes.Count == 0) return null;
|
||||
|
||||
var start = periodTimes.Min(t => t.Start).AddMinutes(-BufferBeforeFirstPeriodMinutes);
|
||||
var end = periodTimes.Max(t => t.End).AddMinutes(BufferAfterLastPeriodMinutes);
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void DeleteEntry(TimeEntryListItem? item)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user