Mitarbeit bewerten begonnen, Schülerdaten, Gruppen

This commit is contained in:
2026-06-23 10:12:52 +02:00
parent fdbaa9ef4d
commit b2211270b4
32 changed files with 1968 additions and 49 deletions
@@ -0,0 +1,115 @@
<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.AddStudentDialog"
x:DataType="vm:AddStudentDialogViewModel"
Title="Neuer Schüler"
Width="500" Height="640"
CanResize="True" WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="*,Auto" Margin="24,20,24,20">
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="14">
<TextBlock Text="Neuen Schüler anlegen" FontSize="18" FontWeight="SemiBold"/>
<!-- Name -->
<Grid ColumnDefinitions="*,10,*">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Nachname *" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding LastName}" PlaceholderText="Mustermann" x:Name="LastNameBox"/>
</StackPanel>
<StackPanel Grid.Column="2" Spacing="4">
<TextBlock Text="Vorname *" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding FirstName}" PlaceholderText="Max"/>
</StackPanel>
</Grid>
<!-- Geburtsdatum + Geschlecht -->
<Grid ColumnDefinitions="*,10,*">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Geburtsdatum" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding DateOfBirthText}" PlaceholderText="TT.MM.JJJJ"/>
</StackPanel>
<StackPanel Grid.Column="2" Spacing="4">
<TextBlock Text="Geschlecht" FontSize="12" Opacity="0.7"/>
<ComboBox ItemsSource="{Binding GenderOptions}"
SelectedItem="{Binding SelectedGender}"
HorizontalAlignment="Stretch"/>
</StackPanel>
</Grid>
<!-- Kontakte -->
<StackPanel Spacing="6">
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0" Text="Kontakte" FontSize="14" FontWeight="SemiBold"
VerticalAlignment="Center"/>
<Button Grid.Column="1" Content=" Kontakt" Command="{Binding AddContactCommand}"
FontSize="12" Padding="8,4"/>
</Grid>
<ItemsControl ItemsSource="{Binding Contacts}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:ContactEntryViewModel">
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
CornerRadius="6" Padding="10,8" Margin="0,0,0,6">
<StackPanel Spacing="6">
<!-- Zeile 1: Name + Beziehung + Löschen -->
<Grid ColumnDefinitions="*,10,*,10,Auto">
<TextBox Grid.Column="0" Text="{Binding Name}"
PlaceholderText="Name" FontSize="12"/>
<ComboBox Grid.Column="2"
ItemsSource="{Binding $parent[Window].DataContext.RelationPresets}"
SelectedItem="{Binding Relation}"
HorizontalAlignment="Stretch" FontSize="12"/>
<Button Grid.Column="4" Content="×" Padding="6,2"
Command="{Binding RemoveCommand}" FontSize="14"/>
</Grid>
<!-- Zeile 2: Telefon + E-Mail -->
<Grid ColumnDefinitions="*,10,*">
<TextBox Grid.Column="0" Text="{Binding Phone}"
PlaceholderText="Telefon / Handy" FontSize="12"/>
<TextBox Grid.Column="2" Text="{Binding Email}"
PlaceholderText="E-Mail" FontSize="12"/>
</Grid>
<!-- Zeile 3: Adresse -->
<TextBox Text="{Binding Street}" PlaceholderText="Straße und Hausnummer" FontSize="12"/>
<Grid ColumnDefinitions="100,10,*">
<TextBox Grid.Column="0" Text="{Binding PostalCode}"
PlaceholderText="PLZ" FontSize="12"/>
<TextBox Grid.Column="2" Text="{Binding City}"
PlaceholderText="Ort" FontSize="12"/>
</Grid>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="Noch keine Kontakte. Über Kontakt hinzufügen."
Opacity="0.35" FontSize="12"
IsVisible="{Binding !Contacts.Count}"/>
</StackPanel>
<!-- Notizen -->
<StackPanel Spacing="4">
<TextBlock Text="Notizen" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding Notes}" PlaceholderText="Interne Anmerkungen …"
AcceptsReturn="True" Height="70" TextWrapping="Wrap"/>
</StackPanel>
<!-- Validierungsfehler -->
<TextBlock Text="{Binding ValidationMessage}" Foreground="Red" FontSize="12"
IsVisible="{Binding ValidationMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</StackPanel>
</ScrollViewer>
<!-- Buttons -->
<Grid Grid.Row="1" ColumnDefinitions="*,10,*" Margin="0,16,0,0">
<Button Grid.Column="0" Content="Abbrechen" HorizontalAlignment="Stretch" Click="OnCancel"/>
<Button Grid.Column="2" Content="Anlegen" HorizontalAlignment="Stretch" Click="OnSave"/>
</Grid>
</Grid>
</Window>
@@ -0,0 +1,27 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using LehrerApp.Desktop.ViewModels.Students;
namespace LehrerApp.Desktop.Views.Students;
public partial class AddStudentDialog : Window
{
public AddStudentDialog() => InitializeComponent();
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
this.FindControl<TextBox>("LastNameBox")?.Focus();
}
private void OnSave(object? s, RoutedEventArgs e)
{
if (DataContext is AddStudentDialogViewModel vm && vm.SaveCommand.CanExecute(null))
{
vm.SaveCommand.Execute(null);
if (vm.Result is not null) Close(true);
}
}
private void OnCancel(object? s, RoutedEventArgs e) => Close(false);
}
@@ -0,0 +1,32 @@
<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.AddressViewerWindow"
x:DataType="vm:ContactItem"
Title="Adresse"
Width="340" Height="230"
MinWidth="280" MinHeight="180"
CanResize="True"
ShowInTaskbar="False"
WindowStartupLocation="Manual">
<Grid RowDefinitions="Auto,*,Auto" Margin="18">
<StackPanel Grid.Row="0" Spacing="3">
<TextBlock Text="{Binding Name}" FontSize="16" FontWeight="SemiBold"/>
<TextBlock Text="{Binding Relation}" FontSize="12" Opacity="0.55"/>
</StackPanel>
<Border Grid.Row="1" Margin="0,14" Padding="12"
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
CornerRadius="7">
<TextBox Text="{Binding Address}" IsReadOnly="True"
AcceptsReturn="True" TextWrapping="Wrap"
BorderThickness="0" Background="Transparent"
VerticalContentAlignment="Center"/>
</Border>
<TextBlock Grid.Row="2"
Text="Das Fenster kann verschoben und parallel weiter geöffnet bleiben."
FontSize="10" Opacity="0.45" TextWrapping="Wrap"/>
</Grid>
</Window>
@@ -0,0 +1,18 @@
using Avalonia;
using Avalonia.Controls;
namespace LehrerApp.Desktop.Views.Students;
public partial class AddressViewerWindow : Window
{
public AddressViewerWindow() => InitializeComponent();
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
if (Owner is not Window owner) return;
Position = new PixelPoint(
owner.Position.X + Math.Max(20, (int)owner.Bounds.Width - (int)Width - 24),
owner.Position.Y + Math.Max(20, (int)owner.Bounds.Height - (int)Height - 48));
}
}
@@ -0,0 +1,87 @@
<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.ContactEditDialog"
x:DataType="vm:ContactEditDialogViewModel"
Title="{Binding DialogTitle}"
Width="520" Height="650"
MinWidth="460" MinHeight="560"
CanResize="True" WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="*,Auto" Margin="24,20">
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="16">
<TextBlock Text="{Binding DialogTitle}" FontSize="18" FontWeight="SemiBold"/>
<Grid ColumnDefinitions="*,10,*">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Name *" FontSize="12" Opacity="0.7"/>
<TextBox x:Name="NameBox" Text="{Binding Name}" PlaceholderText="Name"/>
</StackPanel>
<StackPanel Grid.Column="2" Spacing="4">
<TextBlock Text="Beziehung" FontSize="12" Opacity="0.7"/>
<ComboBox ItemsSource="{Binding RelationPresets}"
SelectedItem="{Binding Relation}"
HorizontalAlignment="Stretch"/>
</StackPanel>
</Grid>
<Grid ColumnDefinitions="*,10,*">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Telefon" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding Phone}" PlaceholderText="Telefon / Handy"/>
</StackPanel>
<StackPanel Grid.Column="2" Spacing="4">
<TextBlock Text="E-Mail" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding Email}" PlaceholderText="E-Mail"/>
</StackPanel>
</Grid>
<StackPanel Spacing="8">
<TextBlock Text="Adresse" FontSize="14" FontWeight="SemiBold"/>
<TextBox Text="{Binding Street}" PlaceholderText="Straße und Hausnummer"/>
<Grid ColumnDefinitions="110,10,*">
<TextBox Grid.Column="0" Text="{Binding PostalCode}" PlaceholderText="PLZ"/>
<TextBox Grid.Column="2" Text="{Binding City}" PlaceholderText="Ort"/>
</Grid>
</StackPanel>
<Separator/>
<CheckBox Content="Dieser Kontakt gilt nicht mehr"
IsChecked="{Binding IsInvalid}"/>
<StackPanel Spacing="10" IsVisible="{Binding IsInvalid}">
<Grid ColumnDefinitions="*,10,*">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Ungültig seit *" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding InvalidSinceText}" PlaceholderText="TT.MM.JJJJ"/>
</StackPanel>
<StackPanel Grid.Column="2" Spacing="4">
<TextBlock Text="Grund" FontSize="12" Opacity="0.7"/>
<ComboBox ItemsSource="{Binding InvalidReasonOptions}"
SelectedItem="{Binding SelectedInvalidReason}"
HorizontalAlignment="Stretch"/>
</StackPanel>
</Grid>
<StackPanel Spacing="4">
<TextBlock Text="Ergänzung zum Grund" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding InvalidReasonDetails}"
PlaceholderText="Optionaler Hinweis"
AcceptsReturn="True" Height="60" TextWrapping="Wrap"/>
</StackPanel>
</StackPanel>
<TextBlock Text="{Binding ValidationMessage}" Foreground="Red" FontSize="12"
IsVisible="{Binding ValidationMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</StackPanel>
</ScrollViewer>
<Grid Grid.Row="1" ColumnDefinitions="*,10,*" Margin="0,18,0,0">
<Button Grid.Column="0" Content="Abbrechen" HorizontalAlignment="Stretch"
Click="OnCancel"/>
<Button Grid.Column="2" Content="Speichern" HorizontalAlignment="Stretch"
Click="OnSave"/>
</Grid>
</Grid>
</Window>
@@ -0,0 +1,25 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using LehrerApp.Desktop.ViewModels.Students;
namespace LehrerApp.Desktop.Views.Students;
public partial class ContactEditDialog : Window
{
public ContactEditDialog() => InitializeComponent();
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
this.FindControl<TextBox>("NameBox")?.Focus();
}
private void OnSave(object? sender, RoutedEventArgs e)
{
if (DataContext is not ContactEditDialogViewModel vm) return;
vm.SaveCommand.Execute(null);
if (vm.Result is not null) Close(true);
}
private void OnCancel(object? sender, RoutedEventArgs e) => Close(false);
}
@@ -60,6 +60,74 @@
</ScrollViewer>
</ContentPage>
<ContentPage Header="Kontakte">
<Grid RowDefinitions="Auto,*" Margin="20">
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto,Auto" Margin="0,0,0,10">
<TextBlock Grid.Column="0" Text="Kontaktdaten" FontSize="15"
FontWeight="SemiBold" VerticalAlignment="Center"/>
<Button Grid.Column="1" Content=" Neu" Command="{Binding AddContactCommand}"
Margin="0,0,8,0"/>
<Button Grid.Column="2" Content="Bearbeiten" Command="{Binding EditContactCommand}"
Margin="0,0,8,0"/>
<Button Grid.Column="3" Content="Adresse anzeigen"
Command="{Binding ViewSelectedAddressCommand}"/>
</Grid>
<ListBox Grid.Row="1" x:Name="ContactList"
ItemsSource="{Binding Contacts}"
SelectedItem="{Binding SelectedContact}"
DoubleTapped="OnContactDoubleTapped">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:ContactItem">
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
CornerRadius="8" Padding="14,10" Margin="2,3">
<Grid ColumnDefinitions="220,*,Auto" RowDefinitions="Auto,Auto">
<StackPanel Grid.Column="0" Grid.RowSpan="2" Spacing="4">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" FontSize="14"
TextWrapping="Wrap"/>
<Border Background="{DynamicResource SystemControlForegroundBaseLowBrush}"
CornerRadius="4" Padding="6,2" HorizontalAlignment="Left">
<TextBlock Text="{Binding Relation}" FontSize="11" Opacity="0.7"/>
</Border>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="0" Orientation="Horizontal" Spacing="8">
<Button Content="{Binding PhoneDisplay}"
Command="{Binding CallCommand}"
IsVisible="{Binding HasPhone}"
FontSize="12" Padding="8,4"/>
<Button Content="{Binding EmailDisplay}"
Command="{Binding MailCommand}"
IsVisible="{Binding HasEmail}"
FontSize="12" Padding="8,4"/>
</StackPanel>
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding Address}"
IsVisible="{Binding HasAddress}" FontSize="12" Opacity="0.65"
TextWrapping="Wrap" Margin="4,4,12,0"/>
<Border Grid.Column="2" Grid.RowSpan="2" CornerRadius="4" Padding="7,3"
VerticalAlignment="Top"
Background="{DynamicResource SystemControlBackgroundAltHighBrush}">
<StackPanel>
<TextBlock Text="{Binding StatusText}" FontSize="11"
IsVisible="{Binding IsInvalid}" Foreground="IndianRed"/>
<TextBlock Text="Aktuell" FontSize="11" Opacity="0.5"
IsVisible="{Binding IsValid}"/>
</StackPanel>
</Border>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Text="Keine Kontakte erfasst." Opacity="0.4"
HorizontalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding HasNoContacts}"/>
</Grid>
</ContentPage>
<ContentPage Header="Noten">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Notenübersicht" FontSize="16" Opacity="0.4" HorizontalAlignment="Center"/>
@@ -1,3 +1,48 @@
using Avalonia.Controls;
using Avalonia.Input;
using LehrerApp.Core.Models;
using LehrerApp.Desktop.ViewModels.Students;
namespace LehrerApp.Desktop.Views.Students;
public partial class StudentDetailView : UserControl { public StudentDetailView() => InitializeComponent(); }
public partial class StudentDetailView : UserControl
{
public StudentDetailView() => InitializeComponent();
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is not StudentDetailViewModel vm) return;
vm.OnEditContact = ShowContactDialog;
vm.OnViewAddress = ShowAddressViewer;
}
private async Task<Contact?> ShowContactDialog(Contact? contact)
{
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is null) return null;
var vm = new ContactEditDialogViewModel(contact);
var dialog = new ContactEditDialog { DataContext = vm };
var saved = await dialog.ShowDialog<bool>(owner);
return saved ? vm.Result : null;
}
private void ShowAddressViewer(ContactItem contact)
{
if (!contact.HasAddress) return;
var owner = TopLevel.GetTopLevel(this) as Window;
var viewer = new AddressViewerWindow { DataContext = contact };
if (owner is not null)
viewer.Show(owner);
else
viewer.Show();
}
private void OnContactDoubleTapped(object? sender, TappedEventArgs e)
{
if (DataContext is StudentDetailViewModel vm &&
vm.ViewSelectedAddressCommand.CanExecute(null))
vm.ViewSelectedAddressCommand.Execute(null);
}
}