This commit is contained in:
@@ -22,10 +22,27 @@ public static class LetterDialogs
|
||||
App.Services.GetRequiredService<IGroupRepository>(),
|
||||
options => App.Services.GetRequiredService<StudentAttendanceCalendarService>().Build(student, options),
|
||||
options => App.Services.GetRequiredService<StudentAttendanceCalendarService>()
|
||||
.BuildAbsenceDayList(student, options));
|
||||
.BuildAbsenceDayList(student, options),
|
||||
RefreshAttendanceDataAsync);
|
||||
var dialog = new CreateLetterDialog { DataContext = vm };
|
||||
var path = await dialog.ShowDialog<string?>(owner);
|
||||
if (!string.IsNullOrEmpty(path) && File.Exists(path))
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
/// <summary>Holt genau den im Anwesenheitskalender-Dialog gewählten Zeitraum gezielt per
|
||||
/// WebUntis nach (ausgelöst durch den expliziten "Konfigurieren…"-Klick, kein Hintergrundabruf
|
||||
/// beim bloßen Öffnen des Briefdialogs). Damit sieht <see cref="StudentAttendanceCalendarService"/>
|
||||
/// anschließend frische Daten im lokalen Cache, statt stillschweigend "keine Fehltage" zu
|
||||
/// melden, nur weil die Klassenlehrer-Übersicht für diesen Zeitraum noch nie geöffnet wurde.</summary>
|
||||
private static async Task RefreshAttendanceDataAsync(AttendanceCalendarOptions options, CancellationToken token)
|
||||
{
|
||||
var className = App.Services.GetRequiredService<WebUntisSettingsService>().HomeroomClassName;
|
||||
if (string.IsNullOrWhiteSpace(className)) return;
|
||||
var cache = App.Services.GetRequiredService<UntisReportCacheService>();
|
||||
var start = options.NormalizedStartMonth;
|
||||
var end = start.AddMonths(options.NormalizedMonthCount).AddDays(-1);
|
||||
await cache.GetAbsencesAsync(className, start, end, token: token);
|
||||
await cache.GetClassRegisterEventsAsync(className, start, end, token: token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ public static class LetterPlaceholderBuilder
|
||||
["Empfaenger"] = new TextValue(contact?.Name ?? ""), ["Anrede"] = new TextValue(contact?.LetterSalutation ?? ""),
|
||||
["Brieftext"] = new MultilineValue(letterText), ["LehrerName"] = new TextValue(teacherName),
|
||||
["Student.FirstName"] = new TextValue(student.FirstName), ["Student.LastName"] = new TextValue(student.LastName),
|
||||
["Student.Name"] = new TextValue(student.FullName),
|
||||
["Contact.Name"] = new TextValue(contact?.Name ?? ""), ["Contact.Address"] = new MultilineValue(address),
|
||||
["Contact.Street"] = new TextValue(contact?.Street ?? ""), ["Contact.PostalCode"] = new TextValue(contact?.PostalCode ?? ""),
|
||||
["Contact.City"] = new TextValue(contact?.City ?? ""), ["Letter.Salutation"] = new TextValue(contact?.LetterSalutation ?? ""),
|
||||
|
||||
@@ -43,15 +43,15 @@ public static class StudentAttendanceCalendarDrawingBuilder
|
||||
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 commands = new List<DrawingCommand>();
|
||||
var y = 0f;
|
||||
commands.Add(new DrawStringEx(0, y, 14 * scale, contentWidth, "Anwesenheit",
|
||||
DrawingTextAlignment.AlignLeft, 11 * scale, Color: "#1F2937", Bold: true));
|
||||
y += 14 * scale;
|
||||
commands.Add(new DrawStringEx(0, y, 10 * scale, contentWidth, studentName,
|
||||
DrawingTextAlignment.AlignLeft, 8 * scale, Color: "#6B7280"));
|
||||
y += 10 * scale + 2 * scale;
|
||||
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);
|
||||
@@ -59,14 +59,14 @@ public static class StudentAttendanceCalendarDrawingBuilder
|
||||
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,
|
||||
commands.Add(new DrawStringEx(0, y, 11 * scale, contentWidth,
|
||||
current.ToString("MMMM yyyy", CultureInfo.GetCultureInfo("de-DE")),
|
||||
DrawingTextAlignment.AlignLeft, 9 * scale, Color: "#374151", Bold: true));
|
||||
y += 7 * scale;
|
||||
y += 11 * scale;
|
||||
for (var column = 0; column < 7; column++)
|
||||
commands.Add(new DrawStringEx(column * cellWidth, y, 6 * scale, cellWidth, weekdays[column],
|
||||
commands.Add(new DrawStringEx(column * cellWidth, y, 9 * scale, cellWidth, weekdays[column],
|
||||
DrawingTextAlignment.AlignCenter, 7 * scale, Color: "#6B7280", Bold: true));
|
||||
y += 7 * scale;
|
||||
y += 9 * scale;
|
||||
|
||||
foreach (var day in days)
|
||||
{
|
||||
@@ -115,20 +115,20 @@ public static class StudentAbsenceDayListDrawingBuilder
|
||||
UntisNameMatching.NamesMatch(a.StudentName, studentName))
|
||||
.OrderBy(a => a.Date)
|
||||
.ToList();
|
||||
var commands = new List<DrawingCommand>
|
||||
{
|
||||
new DrawStringEx(0, 0, 7 * scale, contentWidth, "Fehltage",
|
||||
DrawingTextAlignment.AlignLeft, 11 * scale, Color: "#1F2937", Bold: true),
|
||||
new DrawStringEx(0, 7 * scale, 6 * scale, contentWidth, studentName,
|
||||
DrawingTextAlignment.AlignLeft, 8 * scale, Color: "#6B7280"),
|
||||
};
|
||||
var y = 16 * scale;
|
||||
var commands = new List<DrawingCommand>();
|
||||
var y = 0f;
|
||||
commands.Add(new DrawStringEx(0, y, 14 * scale, contentWidth, "Fehltage",
|
||||
DrawingTextAlignment.AlignLeft, 11 * scale, Color: "#1F2937", Bold: true));
|
||||
y += 14 * scale;
|
||||
commands.Add(new DrawStringEx(0, y, 10 * scale, contentWidth, studentName,
|
||||
DrawingTextAlignment.AlignLeft, 8 * scale, Color: "#6B7280"));
|
||||
y += 10 * scale + 2 * scale;
|
||||
commands.Add(new DrawRectangle(0, y, contentWidth, rowHeight, "#CBD5E1", .4f, "#F3F4F6"));
|
||||
commands.Add(new DrawStringEx(2 * scale, y + scale, rowHeight - 2 * scale, 31 * scale, "Datum",
|
||||
commands.Add(new DrawStringEx(2 * scale, y + scale, rowHeight - scale, 31 * scale, "Datum",
|
||||
DrawingTextAlignment.AlignLeft, 7.5f * scale, Color: "#374151", Bold: true));
|
||||
commands.Add(new DrawStringEx(36 * scale, y + scale, rowHeight - 2 * scale, 75 * scale, "Umfang",
|
||||
commands.Add(new DrawStringEx(36 * scale, y + scale, rowHeight - scale, 75 * scale, "Umfang",
|
||||
DrawingTextAlignment.AlignLeft, 7.5f * scale, Color: "#374151", Bold: true));
|
||||
commands.Add(new DrawStringEx(113 * scale, y + scale, rowHeight - 2 * scale, 55 * scale, "Status",
|
||||
commands.Add(new DrawStringEx(113 * scale, y + scale, rowHeight - scale, 55 * scale, "Status",
|
||||
DrawingTextAlignment.AlignLeft, 7.5f * scale, Color: "#374151", Bold: true));
|
||||
y += rowHeight;
|
||||
|
||||
@@ -157,12 +157,12 @@ public static class StudentAbsenceDayListDrawingBuilder
|
||||
: row.FriendlyStatusLabel;
|
||||
var statusColor = row.IsUnexcused ? "#C62828" : "#2E7D32";
|
||||
commands.Add(new DrawRectangle(0, y, contentWidth, rowHeight, "#E5E7EB", .3f, fill));
|
||||
commands.Add(new DrawStringEx(2 * scale, y + scale, rowHeight - 2 * scale, 31 * scale,
|
||||
commands.Add(new DrawStringEx(2 * scale, y + scale, rowHeight - scale, 31 * scale,
|
||||
row.Date.ToString("dd.MM.yyyy"), DrawingTextAlignment.AlignLeft, 7.5f * scale,
|
||||
Color: "#374151"));
|
||||
commands.Add(new DrawStringEx(36 * scale, y + scale, rowHeight - 2 * scale, 75 * scale,
|
||||
commands.Add(new DrawStringEx(36 * scale, y + scale, rowHeight - scale, 75 * scale,
|
||||
extent, DrawingTextAlignment.AlignLeft, 7.5f * scale, Color: "#374151"));
|
||||
commands.Add(new DrawStringEx(113 * scale, y + scale, rowHeight - 2 * scale, 55 * scale,
|
||||
commands.Add(new DrawStringEx(113 * scale, y + scale, rowHeight - scale, 55 * scale,
|
||||
status, DrawingTextAlignment.AlignLeft, 7.5f * scale, Color: statusColor,
|
||||
Bold: row.IsUnexcused));
|
||||
y += rowHeight;
|
||||
|
||||
@@ -14,6 +14,7 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
||||
private readonly ITemplateRenderer _renderer;
|
||||
private readonly Func<AttendanceCalendarOptions, DrawingValue>? _attendanceCalendarFactory;
|
||||
private readonly Func<AttendanceCalendarOptions, DrawingValue>? _absenceDayListFactory;
|
||||
private readonly Func<AttendanceCalendarOptions, CancellationToken, Task>? _attendanceDataRefresher;
|
||||
private AttendanceCalendarOptions _attendanceCalendarOptions = new(
|
||||
new DateOnly(DateTime.Today.Year, DateTime.Today.Month, 1), 1);
|
||||
|
||||
@@ -21,6 +22,7 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
||||
[ObservableProperty] private LetterContactChoice? _selectedContact;
|
||||
[ObservableProperty] private LetterGroupChoice? _selectedGroup;
|
||||
[ObservableProperty] private DateTimeOffset? _letterDate = DateTimeOffset.Now;
|
||||
[ObservableProperty] private string _anrede = "";
|
||||
[ObservableProperty] private string _letterText = "";
|
||||
[ObservableProperty] private string _teacherName = "";
|
||||
[ObservableProperty] private string _generationError = "";
|
||||
@@ -29,6 +31,8 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
||||
[ObservableProperty] private bool _usesAbsenceDayList;
|
||||
[ObservableProperty] private bool _attendanceCalendarConfigured;
|
||||
[ObservableProperty] private string _attendanceCalendarSummary = "1 Monat · Standardgröße";
|
||||
[ObservableProperty] private bool _isRefreshingAttendanceData;
|
||||
[ObservableProperty] private string _attendanceRefreshError = "";
|
||||
|
||||
public string StudentName => _student.FullName;
|
||||
public ObservableCollection<LetterTemplateChoice> Templates { get; } = [];
|
||||
@@ -47,11 +51,13 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
||||
public CreateLetterDialogViewModel(Student student, TemplateStore templates, ITemplateRenderer renderer,
|
||||
IGroupMembershipRepository memberships, IGroupRepository groups,
|
||||
Func<AttendanceCalendarOptions, DrawingValue>? attendanceCalendarFactory = null,
|
||||
Func<AttendanceCalendarOptions, DrawingValue>? absenceDayListFactory = null)
|
||||
Func<AttendanceCalendarOptions, DrawingValue>? absenceDayListFactory = null,
|
||||
Func<AttendanceCalendarOptions, CancellationToken, Task>? attendanceDataRefresher = null)
|
||||
{
|
||||
_student = student; _templates = templates; _renderer = renderer;
|
||||
_attendanceCalendarFactory = attendanceCalendarFactory;
|
||||
_absenceDayListFactory = absenceDayListFactory;
|
||||
_attendanceDataRefresher = attendanceDataRefresher;
|
||||
foreach (var template in templates.GetTemplates()) Templates.Add(new(template));
|
||||
foreach (var contact in student.Contacts.Where(c => !c.InvalidSince.HasValue).OrderBy(c => c.Name)) Contacts.Add(new(contact));
|
||||
foreach (var membership in memberships.GetByStudent(student.Id))
|
||||
@@ -73,7 +79,12 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
||||
RebuildCustomPlaceholders(value);
|
||||
RefreshValidation();
|
||||
}
|
||||
partial void OnSelectedContactChanged(LetterContactChoice? value) => RefreshValidation();
|
||||
partial void OnSelectedContactChanged(LetterContactChoice? value)
|
||||
{
|
||||
Anrede = value?.Model.LetterSalutation ?? "";
|
||||
RefreshValidation();
|
||||
}
|
||||
partial void OnAnredeChanged(string value) => RefreshValidation();
|
||||
partial void OnSelectedGroupChanged(LetterGroupChoice? value) => RefreshValidation();
|
||||
partial void OnLetterDateChanged(DateTimeOffset? value)
|
||||
{
|
||||
@@ -127,6 +138,7 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
||||
DateOnly.FromDateTime((LetterDate ?? DateTimeOffset.Now).LocalDateTime), LetterText, TeacherName,
|
||||
UsesAttendanceCalendar ? _attendanceCalendarFactory?.Invoke(_attendanceCalendarOptions) : null,
|
||||
UsesAbsenceDayList ? _absenceDayListFactory?.Invoke(_attendanceCalendarOptions) : null);
|
||||
values["Anrede"] = new TextValue(Anrede); values["Letter.Salutation"] = new TextValue(Anrede);
|
||||
foreach (var custom in CustomPlaceholders) values[custom.Name] = custom.ToPlaceholderValue();
|
||||
return values;
|
||||
}
|
||||
@@ -159,19 +171,28 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
||||
private static readonly HashSet<string> StandardPlaceholderNames = new(StringComparer.Ordinal)
|
||||
{
|
||||
"Datum", "CurrentDate", "Empfaenger", "Anrede", "Brieftext", "LehrerName",
|
||||
"Student.FirstName", "Student.LastName", "Contact.Name", "Contact.Address", "Contact.Street",
|
||||
"Student.FirstName", "Student.LastName", "Student.Name", "Contact.Name", "Contact.Address", "Contact.Street",
|
||||
"Contact.PostalCode", "Contact.City", "Letter.Salutation", "Group.Name", "SchoolYear",
|
||||
StudentAttendanceCalendarDrawingBuilder.PlaceholderName, StudentAbsenceDayListDrawingBuilder.PlaceholderName,
|
||||
};
|
||||
|
||||
public AttendanceCalendarOptions GetAttendanceCalendarOptions() => _attendanceCalendarOptions;
|
||||
|
||||
public void SetAttendanceCalendarOptions(AttendanceCalendarOptions options)
|
||||
public async Task SetAttendanceCalendarOptionsAsync(AttendanceCalendarOptions options, CancellationToken token = default)
|
||||
{
|
||||
_attendanceCalendarOptions = options with { StartMonth = options.NormalizedStartMonth,
|
||||
MonthCount = options.NormalizedMonthCount };
|
||||
AttendanceCalendarConfigured = true;
|
||||
AttendanceCalendarSummary = FormatAttendanceCalendarSummary(_attendanceCalendarOptions);
|
||||
AttendanceRefreshError = "";
|
||||
if (_attendanceDataRefresher is not null)
|
||||
{
|
||||
IsRefreshingAttendanceData = true;
|
||||
try { await _attendanceDataRefresher(_attendanceCalendarOptions, token); }
|
||||
catch (WebUntisIntegrationException ex)
|
||||
{ AttendanceRefreshError = $"WebUntis-Daten konnten nicht aktualisiert werden: {ex.Message}"; }
|
||||
finally { IsRefreshingAttendanceData = false; }
|
||||
}
|
||||
RefreshValidation();
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
<TextBlock Text="Kein aktueller Kontakt vorhanden." Classes="emptyhint"
|
||||
IsVisible="{Binding HasNoContacts}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Anrede *" FontSize="12" Opacity="0.7"/>
|
||||
<TextBox Text="{Binding Anrede}" PlaceholderText="z.B. Sehr geehrte Frau Mustermann,"/>
|
||||
</StackPanel>
|
||||
<Grid ColumnDefinitions="*,12,*">
|
||||
<StackPanel Grid.Column="0" Spacing="4">
|
||||
<TextBlock Text="Lerngruppe" FontSize="12" Opacity="0.7"/>
|
||||
@@ -46,14 +50,20 @@
|
||||
</Grid>
|
||||
<Border IsVisible="{Binding UsesAttendanceAdvancedContent}" BorderBrush="{DynamicResource AppCardBorderBrush}"
|
||||
BorderThickness="1" CornerRadius="6" Padding="12">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Spacing="3">
|
||||
<TextBlock Text="Anwesenheitsdaten im Brief" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding AttendanceCalendarSummary}" FontSize="12" Opacity="0.65"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Content="Konfigurieren …" Click="OnConfigureAttendanceCalendar"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<StackPanel Spacing="6">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Spacing="3">
|
||||
<TextBlock Text="Anwesenheitsdaten im Brief" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding AttendanceCalendarSummary}" FontSize="12" Opacity="0.65"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Content="Konfigurieren …" Click="OnConfigureAttendanceCalendar"
|
||||
IsEnabled="{Binding !IsRefreshingAttendanceData}" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<TextBlock Text="Aktualisiere WebUntis-Daten für den gewählten Zeitraum …" FontSize="12"
|
||||
Foreground="#6B7280" IsVisible="{Binding IsRefreshingAttendanceData}"/>
|
||||
<TextBlock Text="{Binding AttendanceRefreshError}" FontSize="12" Foreground="#D97706" TextWrapping="Wrap"
|
||||
IsVisible="{Binding AttendanceRefreshError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Brieftext" FontSize="12" Opacity="0.7"/>
|
||||
|
||||
@@ -35,7 +35,7 @@ public partial class CreateLetterDialog : Window
|
||||
var configVm = new AttendanceCalendarConfigurationViewModel(vm.GetAttendanceCalendarOptions());
|
||||
var dialog = new AttendanceCalendarConfigurationDialog { DataContext = configVm };
|
||||
if (!await dialog.ShowDialog<bool>(this)) return false;
|
||||
vm.SetAttendanceCalendarOptions(configVm.BuildResult());
|
||||
await vm.SetAttendanceCalendarOptionsAsync(configVm.BuildResult());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user