feat: Fehlzeiten Calender
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
using System.Globalization;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Desktop.ViewModels.ClassTeacher;
|
||||
using LehrerApp.Templating;
|
||||
|
||||
namespace LehrerApp.Desktop.Services;
|
||||
|
||||
public enum AttendanceCalendarSize { Small, Medium, Large }
|
||||
|
||||
public sealed record AttendanceCalendarOptions(DateOnly StartMonth, int MonthCount,
|
||||
AttendanceCalendarSize Size = AttendanceCalendarSize.Medium)
|
||||
{
|
||||
public DateOnly NormalizedStartMonth => new(StartMonth.Year, StartMonth.Month, 1);
|
||||
public int NormalizedMonthCount => Math.Clamp(MonthCount, 1, 3);
|
||||
}
|
||||
|
||||
/// <summary>Erzeugt den portablen Advanced-Content-Platzhalter für Elternbriefe aus derselben
|
||||
/// priorisierten Monatsansicht, die im Klassenlehrer-Sidebar-Widget verwendet wird.</summary>
|
||||
public static class StudentAttendanceCalendarDrawingBuilder
|
||||
{
|
||||
public const string PlaceholderName = "Student.AttendanceCalendar";
|
||||
|
||||
public static DrawingValue Build(string studentName, DateOnly month,
|
||||
IReadOnlyList<ClassAbsenceDaySummaryRow> absences,
|
||||
IReadOnlyList<UntisForeignClassRegisterEventDto> registerEntries) =>
|
||||
Build(studentName, new AttendanceCalendarOptions(month, 1), absences, registerEntries);
|
||||
|
||||
public static DrawingValue Build(string studentName, AttendanceCalendarOptions options,
|
||||
IReadOnlyList<ClassAbsenceDaySummaryRow> absences,
|
||||
IReadOnlyList<UntisForeignClassRegisterEventDto> registerEntries)
|
||||
{
|
||||
const float width = 170;
|
||||
var scale = options.Size switch
|
||||
{
|
||||
AttendanceCalendarSize.Small => .7f,
|
||||
AttendanceCalendarSize.Large => 1f,
|
||||
_ => .85f,
|
||||
};
|
||||
var contentWidth = width * scale;
|
||||
var cellWidth = contentWidth / 7;
|
||||
var cellHeight = 10 * scale;
|
||||
var monthGap = 6 * scale;
|
||||
var first = options.NormalizedStartMonth;
|
||||
var monthCount = options.NormalizedMonthCount;
|
||||
var commands = new List<DrawingCommand>
|
||||
{
|
||||
new DrawStringEx(0, 0, 7 * scale, contentWidth, "Anwesenheit",
|
||||
DrawingTextAlignment.AlignLeft, 11 * scale, Color: "#1F2937", Bold: true),
|
||||
new DrawStringEx(0, 7 * scale, 6 * scale, contentWidth, studentName,
|
||||
DrawingTextAlignment.AlignLeft, 8 * scale, Color: "#6B7280"),
|
||||
};
|
||||
var weekdays = new[] { "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So" };
|
||||
var y = 15 * scale;
|
||||
for (var monthIndex = 0; monthIndex < monthCount; monthIndex++)
|
||||
{
|
||||
var current = first.AddMonths(monthIndex);
|
||||
var days = ClassTeacherOverviewViewModel.BuildCompactMonthDays(current, absences, registerEntries,
|
||||
DateOnly.FromDateTime(DateTime.Today), studentName);
|
||||
var offset = ((int)current.DayOfWeek + 6) % 7;
|
||||
var weeks = (int)Math.Ceiling((offset + days.Count) / 7d);
|
||||
commands.Add(new DrawStringEx(0, y, 7 * scale, contentWidth,
|
||||
current.ToString("MMMM yyyy", CultureInfo.GetCultureInfo("de-DE")),
|
||||
DrawingTextAlignment.AlignLeft, 9 * scale, Color: "#374151", Bold: true));
|
||||
y += 7 * scale;
|
||||
for (var column = 0; column < 7; column++)
|
||||
commands.Add(new DrawStringEx(column * cellWidth, y, 6 * scale, cellWidth, weekdays[column],
|
||||
DrawingTextAlignment.AlignCenter, 7 * scale, Color: "#6B7280", Bold: true));
|
||||
y += 7 * scale;
|
||||
|
||||
foreach (var day in days)
|
||||
{
|
||||
var index = offset + day.Date.Day - 1;
|
||||
var column = index % 7;
|
||||
var row = index / 7;
|
||||
var x = column * cellWidth;
|
||||
var cellY = y + row * cellHeight;
|
||||
commands.Add(new DrawRectangle(x + scale, cellY, cellWidth - 2 * scale, cellHeight - scale,
|
||||
"#D1D5DB", .35f, day.HasSignal ? day.SignalColorHex : "#FFFFFF"));
|
||||
commands.Add(new DrawStringEx(x, cellY + scale, cellHeight - 2 * scale, cellWidth,
|
||||
day.HasSignal ? day.SignalCode : day.DayNumber, DrawingTextAlignment.AlignCenter, 7 * scale,
|
||||
Color: day.HasSignal ? "#FFFFFF" : "#374151", Bold: day.HasSignal));
|
||||
}
|
||||
y += weeks * cellHeight + monthGap;
|
||||
}
|
||||
|
||||
commands.Add(new DrawStringEx(0, y, 8 * scale, contentWidth,
|
||||
"U unentschuldigt · A abwesend · V verspätet · E entschuldigt · ! Klassenbuch",
|
||||
DrawingTextAlignment.AlignLeft, 6.5f * scale, Color: "#6B7280"));
|
||||
return new DrawingValue(commands, y + 9 * scale);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Liest ausschließlich den lokalen WebUntis-Cache. Das Öffnen oder Rendern eines
|
||||
/// Elternbriefs löst dadurch keinen unerwarteten Netzwerkabruf aus.</summary>
|
||||
public sealed class StudentAttendanceCalendarService(
|
||||
WebUntisSettingsService settings,
|
||||
IUntisAbsenceCacheRepository absenceCache,
|
||||
IUntisClassRegisterCacheRepository registerCache,
|
||||
IUntisStudentRosterCacheRepository rosterCache)
|
||||
{
|
||||
public DrawingValue Build(Student student, DateOnly month) =>
|
||||
Build(student, new AttendanceCalendarOptions(month, 1));
|
||||
|
||||
public DrawingValue Build(Student student, AttendanceCalendarOptions options)
|
||||
{
|
||||
var className = settings.HomeroomClassName;
|
||||
if (string.IsNullOrWhiteSpace(className)) return new DrawingValue([], 0);
|
||||
|
||||
var first = options.NormalizedStartMonth;
|
||||
var last = first.AddMonths(options.NormalizedMonthCount).AddDays(-1);
|
||||
var rosterName = rosterCache.GetByClass(className)
|
||||
.FirstOrDefault(r => UntisNameMatching.NamesMatch(r.DisplayName, student.FullName))?.DisplayName
|
||||
?? student.FullName;
|
||||
var start = first.Year * 10000 + first.Month * 100 + first.Day;
|
||||
var end = last.Year * 10000 + last.Month * 100 + last.Day;
|
||||
var absences = absenceCache.GetByClassAndRange(className, start, end)
|
||||
.Select(e => new UntisClassAbsenceEntryDto(e.StudentName, e.ExternKey, e.ClassName, e.Date,
|
||||
e.AbsentPeriods, e.AbsentMinutes, e.TeacherUsernames, e.Subject, e.AbsenceReason, e.Note,
|
||||
e.EntryId, e.HandledOn, e.Counts, e.ExcuseNote, e.PeriodNumber, e.Status, e.CountsAsFullDay))
|
||||
.ToList();
|
||||
var register = registerCache.GetByClassAndRange(className, start, end)
|
||||
.Select(e => new UntisForeignClassRegisterEventDto(e.ClassName, e.Date, e.Subject, e.StudentName,
|
||||
e.TeacherUsername, e.CategoryName, e.CategoryGroup, e.Text))
|
||||
.ToList();
|
||||
return StudentAttendanceCalendarDrawingBuilder.Build(rosterName, options,
|
||||
ClassAbsenceDaySummaryRow.GroupByStudentAndDay(absences), register);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user