ContextWindow: Use Task.Run to load data outside main thread
All checks were successful
Test / Run tests (push) Successful in 2m29s

This commit is contained in:
2026-04-02 14:18:06 +02:00
parent 1f4fe2129d
commit e593175e72
18 changed files with 241 additions and 186 deletions

View File

@@ -318,18 +318,13 @@ namespace Elwig.Services {
AddToolTipCell(grid, $"{weight * 100.0 / total2:N1} %", row, 4, 1, bold, true); AddToolTipCell(grid, $"{weight * 100.0 / total2:N1} %", row, 4, 1, bold, true);
} }
public static async Task<(string, Grid)> GenerateToolTip(IQueryable<DeliveryAncmt> deliveryAncmts) { public static async Task<(string Text, (string?, string?, int, int?, int)[] Grid)> GenerateToolTipData(IQueryable<DeliveryAncmt> deliveryAncmts) {
var grid = new Grid(); var grid = new List<(string?, string?, int, int?, int)>();
grid.ColumnDefinitions.Add(new() { Width = new(10) });
grid.ColumnDefinitions.Add(new() { Width = new(60) });
grid.ColumnDefinitions.Add(new() { Width = new(80) });
grid.ColumnDefinitions.Add(new() { Width = new(50) });
grid.ColumnDefinitions.Add(new() { Width = new(50) });
var text = "-"; var text = "-";
var weight = await deliveryAncmts.SumAsync(p => p.Weight); var weight = await deliveryAncmts.SumAsync(p => p.Weight);
text = $"{weight:N0} kg"; text = $"{weight:N0} kg";
AddToolTipRow(grid, 0, "Menge", null, weight, null, weight); grid.Add(("Menge", null, weight, null, weight));
if (await deliveryAncmts.AnyAsync()) { if (await deliveryAncmts.AnyAsync()) {
var attrGroups = await deliveryAncmts var attrGroups = await deliveryAncmts
@@ -370,13 +365,11 @@ namespace Elwig.Services {
.ThenBy(g => g.SortId) .ThenBy(g => g.SortId)
.ToListAsync(); .ToListAsync();
int rowNum = 1;
foreach (var attrG in attrGroups) { foreach (var attrG in attrGroups) {
rowNum++;
var name = attrG.Attr == null && attrG.Cult == null ? null : attrG.Attr + (attrG.Attr != null && attrG.Cult != null ? " / " : "") + attrG.Cult; var name = attrG.Attr == null && attrG.Cult == null ? null : attrG.Attr + (attrG.Attr != null && attrG.Cult != null ? " / " : "") + attrG.Cult;
AddToolTipRow(grid, rowNum++, name, null, attrG.Weight, attrG.Weight, weight); grid.Add((name, null, attrG.Weight, attrG.Weight, weight));
foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Weight).ThenBy(g => g.SortId)) { foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Weight).ThenBy(g => g.SortId)) {
AddToolTipRow(grid, rowNum++, null, g.SortId, g.Weight, attrG.Weight, weight); grid.Add((null, g.SortId, g.Weight, attrG.Weight, weight));
} }
} }
@@ -395,7 +388,22 @@ namespace Elwig.Services {
} }
} }
return (text, grid); return (text, grid.ToArray());
}
public static Grid GenerateToolTip((string?, string?, int, int?, int)[] data) {
var grid = new Grid();
grid.ColumnDefinitions.Add(new() { Width = new(10) });
grid.ColumnDefinitions.Add(new() { Width = new(60) });
grid.ColumnDefinitions.Add(new() { Width = new(80) });
grid.ColumnDefinitions.Add(new() { Width = new(50) });
grid.ColumnDefinitions.Add(new() { Width = new(50) });
int rowNum = 0;
foreach (var row in data) {
if (rowNum != 0 && row.Item2 == null) rowNum++;
AddToolTipRow(grid, rowNum++, row.Item1, row.Item2, row.Item3, row.Item4, row.Item5);
}
return grid;
} }
} }
} }

View File

@@ -7,8 +7,7 @@
xmlns:local="clr-namespace:Elwig.Windows" xmlns:local="clr-namespace:Elwig.Windows"
xmlns:ctrl="clr-namespace:Elwig.Controls" xmlns:ctrl="clr-namespace:Elwig.Controls"
xmlns:vm="clr-namespace:Elwig.ViewModels" xmlns:vm="clr-namespace:Elwig.ViewModels"
Title="{Binding Title}" Height="600" MinHeight="450" Width="1000" MinWidth="860" Title="{Binding Title}" Height="600" MinHeight="450" Width="1000" MinWidth="860">
Loaded="Window_Loaded">
<Window.DataContext> <Window.DataContext>
<vm:AreaComAdminViewModel/> <vm:AreaComAdminViewModel/>
</Window.DataContext> </Window.DataContext>

View File

@@ -37,10 +37,10 @@ namespace Elwig.Windows {
ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged); ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged; SearchInput.TextChanged -= SearchInput_TextChanged;
ActiveAreaCommitmentInput.Content = ((string)ActiveAreaCommitmentInput.Content).Replace("2020", $"{Utils.CurrentLastSeason}"); ActiveAreaCommitmentInput.Content = ((string)ActiveAreaCommitmentInput.Content).Replace("2020", $"{Utils.CurrentLastSeason}");
ActiveAreaCommitmentInput.IsChecked = true;
} }
private void Window_Loaded(object sender, RoutedEventArgs e) { protected override async Task OnInit(AppDbContext ctx) {
ActiveAreaCommitmentInput.IsChecked = true;
LockInputs(); LockInputs();
} }
@@ -52,35 +52,47 @@ namespace Elwig.Windows {
} }
private async Task RefreshList(bool updateSort = false) { private async Task RefreshList(bool updateSort = false) {
using var ctx = new AppDbContext(); var vm = ViewModel;
var (_, areaComQuery, filter) = await ViewModel.GetFilters(ctx); var cursor = Mouse.OverrideCursor != null;
var areaComs = await areaComQuery if (!cursor) Mouse.OverrideCursor = Cursors.Wait;
.Include(a => a.Kg.AtKg) var query = (vm.SearchQuery, vm.ShowOnlyActiveAreaComs);
.Include(a => a.Rd!.Kg.AtKg) var (filter, areaComs, areaComCount, stat) = await Task.Run(async () => {
.Include(a => a.WineCult) using var ctx = new AppDbContext();
.Include(a => a.AreaComType.WineAttr) var (_, areaComQuery, filter) = await vm.GetFilters(ctx);
.Include(a => a.AreaComType.WineVar) var areaComs = await areaComQuery
.ToListAsync(); .Include(a => a.Kg.AtKg)
.Include(a => a.Rd!.Kg.AtKg)
.Include(a => a.WineCult)
.Include(a => a.AreaComType.WineAttr)
.Include(a => a.AreaComType.WineVar)
.ToListAsync();
if (filter.Count > 0 && areaComs.Count > 0) { if (filter.Count > 0 && areaComs.Count > 0) {
var dict = areaComs.AsParallel() var dict = areaComs.AsParallel()
.ToDictionary(d => d, d => d.SearchScore(ViewModel.TextFilter)) .ToDictionary(d => d, d => d.SearchScore(vm.TextFilter))
.OrderByDescending(c => c.Value); .OrderByDescending(c => c.Value);
var threshold = dict.Select(a => a.Value).Max() * 3 / 4; var threshold = dict.Max(a => a.Value) * 3 / 4;
areaComs = dict areaComs = [.. dict
.Where(a => a.Value > threshold) .Where(a => a.Value > threshold)
.Select(a => a.Key) .Select(a => a.Key)];
.ToList(); }
}
var areaComCount = await areaComQuery.CountAsync();
var season = await ctx.Seasons.FindAsync(await ctx.Seasons.MaxAsync(s => s.Year));
var stat = await AreaComService.GenerateToolTipData(areaComQuery, season?.MaxKgPerHa ?? 10_000);
return (filter, areaComs, areaComCount, stat);
});
if (!cursor) Mouse.OverrideCursor = null;
if (query != (ViewModel.SearchQuery, ViewModel.ShowOnlyActiveAreaComs)) return;
ControlUtils.RenewItemsSource(AreaCommitmentList, areaComs, ControlUtils.RenewItemsSource(AreaCommitmentList, areaComs,
AreaCommitmentList_SelectionChanged, filter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort); AreaCommitmentList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
RefreshInputs(); RefreshInputs();
if (filter.Count == 0) { if (filter.Count == 0) {
ViewModel.StatusAreaCommitments = $"{await areaComQuery.CountAsync():N0}"; ViewModel.StatusAreaCommitments = $"{areaComCount:N0}";
var s = await ctx.Seasons.FindAsync(await ctx.Seasons.MaxAsync(s => s.Year)); var (text, gridData) = stat;
var (text, gridData) = await AreaComService.GenerateToolTipData(areaComQuery, s?.MaxKgPerHa ?? 10_000);
ViewModel.StatusArea = text; ViewModel.StatusArea = text;
ViewModel.StatusAreaToolTip = AreaComService.GenerateToolTip(gridData); ViewModel.StatusAreaToolTip = AreaComService.GenerateToolTip(gridData);
} else { } else {

View File

@@ -8,8 +8,7 @@
xmlns:local="clr-namespace:Elwig.Windows" xmlns:local="clr-namespace:Elwig.Windows"
xmlns:ctrl="clr-namespace:Elwig.Controls" xmlns:ctrl="clr-namespace:Elwig.Controls"
mc:Ignorable="d" mc:Ignorable="d"
Title="Stammdaten - Elwig" Height="520" MinHeight="400" Width="860" MinWidth="810" Title="Stammdaten - Elwig" Height="520" MinHeight="400" Width="860" MinWidth="810">
Loaded="Window_Loaded">
<Window.Resources> <Window.Resources>
<Style TargetType="Label"> <Style TargetType="Label">
<Setter Property="HorizontalAlignment" Value="Left"/> <Setter Property="HorizontalAlignment" Value="Left"/>

View File

@@ -153,7 +153,7 @@ namespace Elwig.Windows {
ParameterExportEbicsAddress.IsEnabled = true; ParameterExportEbicsAddress.IsEnabled = true;
} }
private void Window_Loaded(object sender, RoutedEventArgs evt) { protected override async Task OnInit(AppDbContext ctx) {
LockInputs(); LockInputs();
} }

View File

@@ -9,7 +9,6 @@
xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF" xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
mc:Ignorable="d" mc:Ignorable="d"
Title="Auszahlung - Elwig" Height="700" Width="1500" MinWidth="1000" MinHeight="500" Title="Auszahlung - Elwig" Height="700" Width="1500" MinWidth="1000" MinHeight="500"
Loaded="Window_Loaded"
Closing="Window_Closing"> Closing="Window_Closing">
<Window.Resources> <Window.Resources>

View File

@@ -80,9 +80,6 @@ namespace Elwig.Windows {
LockContext = true; LockContext = true;
} }
private void Window_Loaded(object sender, RoutedEventArgs evt) {
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
if (HasChanged) { if (HasChanged) {
var r = MessageBox.Show("Soll das Fenster wirklich geschlossen werden? Nicht gespeicherte Änderungen werden NICHT übernommen!", "Schließen bestätigen", var r = MessageBox.Show("Soll das Fenster wirklich geschlossen werden? Nicht gespeicherte Änderungen werden NICHT übernommen!", "Schließen bestätigen",

View File

@@ -40,8 +40,11 @@ namespace Elwig.Windows {
} }
protected async void OnLoaded(object? sender, RoutedEventArgs? evt) { protected async void OnLoaded(object? sender, RoutedEventArgs? evt) {
Mouse.OverrideCursor = Cursors.AppStarting;
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
await OnRenewContext(ctx); await OnRenewContext(ctx);
await OnInit(ctx);
Mouse.OverrideCursor = null;
} }
protected async Task EnsureContextRenewed() { protected async Task EnsureContextRenewed() {
@@ -51,6 +54,8 @@ namespace Elwig.Windows {
_renewPending = false; _renewPending = false;
} }
virtual protected async Task OnInit(AppDbContext ctx) { }
abstract protected Task OnRenewContext(AppDbContext ctx); abstract protected Task OnRenewContext(AppDbContext ctx);
} }
} }

View File

@@ -6,8 +6,7 @@
xmlns:local="clr-namespace:Elwig.Windows" xmlns:local="clr-namespace:Elwig.Windows"
xmlns:vm="clr-namespace:Elwig.ViewModels" xmlns:vm="clr-namespace:Elwig.ViewModels"
xmlns:ctrl="clr-namespace:Elwig.Controls" xmlns:ctrl="clr-namespace:Elwig.Controls"
Title="{Binding Title}" Height="720" Width="1150" MinHeight="720" MinWidth="1000" Title="{Binding Title}" Height="720" Width="1150" MinHeight="720" MinWidth="1000">
Loaded="Window_Loaded">
<Window.DataContext> <Window.DataContext>
<vm:DeliveryAdminViewModel/> <vm:DeliveryAdminViewModel/>
</Window.DataContext> </Window.DataContext>

View File

@@ -128,14 +128,14 @@ namespace Elwig.Windows {
ViewModel.EnableAllSeasons = true; ViewModel.EnableAllSeasons = true;
} }
private void Window_Loaded(object sender, RoutedEventArgs evt) { protected override async Task OnInit(AppDbContext ctx) {
await base.OnInit(ctx);
OnSecondPassed(null, null); OnSecondPassed(null, null);
SecondsTimer.Start(); SecondsTimer.Start();
LockInputs(); LockInputs();
if (ViewModel.IsReceipt) { if (ViewModel.IsReceipt) {
NewDeliveryButton_Click(null, null); NewDeliveryButton_Click(null, null);
using var ctx = new AppDbContext(); if (await ctx.Seasons.FindAsync(Utils.CurrentYear) == null) {
if (ctx.Seasons.Find(Utils.CurrentYear) == null) {
MessageBox.Show("Die Saison für das aktuelle Jahr wurde noch nicht erstellt. Neue Lieferungen können nicht abgespeichert werden.\n\n(Stammdaten -> Saisons -> Neu anlegen...)", MessageBox.Show("Die Saison für das aktuelle Jahr wurde noch nicht erstellt. Neue Lieferungen können nicht abgespeichert werden.\n\n(Stammdaten -> Saisons -> Neu anlegen...)",
"Saison noch nicht erstellt", MessageBoxButton.OK, MessageBoxImage.Warning); "Saison noch nicht erstellt", MessageBoxButton.OK, MessageBoxImage.Warning);
} }
@@ -420,33 +420,47 @@ namespace Elwig.Windows {
} }
private async Task RefreshList(bool updateSort = false) { private async Task RefreshList(bool updateSort = false) {
using var ctx = new AppDbContext(); var vm = ViewModel;
var (_, deliveryQuery, deliveryPartsQuery, predicate, filter) = await ViewModel.GetFilters(ctx); var cursor = Mouse.OverrideCursor != null;
var deliveries = await deliveryQuery if (!cursor) Mouse.OverrideCursor = Cursors.Wait;
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier) var query = (vm.SearchQuery, vm.FilterSeason, vm.FilterAllSeasons, vm.FilterTodayOnly);
.Include(d => d.Parts).ThenInclude(p => p.Attribute) var (filter, deliveries, deliveryPartsNum, varieties, stat) = await Task.Run(async () => {
.Include(d => d.Parts).ThenInclude(p => p.Cultivation) using var ctx = new AppDbContext();
.Include(d => d.Parts).ThenInclude(p => p.Variety) var (_, deliveryQuery, deliveryPartsQuery, predicate, filter) = await vm.GetFilters(ctx);
.Include(d => d.Member.EmailAddresses) var deliveries = await deliveryQuery
.AsSplitQuery() .Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
.ToListAsync(); .Include(d => d.Parts).ThenInclude(p => p.Attribute)
deliveries.Reverse(); .Include(d => d.Parts).ThenInclude(p => p.Cultivation)
.Include(d => d.Parts).ThenInclude(p => p.Variety)
.Include(d => d.Member.EmailAddresses)
.AsSplitQuery()
.ToListAsync();
deliveries.Reverse();
if (filter.Count > 0 && deliveries.Count > 0) { if (filter.Count > 0 && deliveries.Count > 0) {
var dict = deliveries.AsParallel() var dict = deliveries.AsParallel()
.ToDictionary(d => d, d => d.SearchScore(ViewModel.TextFilter)) .ToDictionary(d => d, d => d.SearchScore(vm.TextFilter))
.OrderByDescending(a => a.Value) .OrderByDescending(a => a.Value)
.ThenBy(a => a.Key.DateTime); .ThenBy(a => a.Key.DateTime);
var threshold = dict.Select(a => a.Value).Max() * 3 / 4; var threshold = dict.Select(a => a.Value).Max() * 3 / 4;
deliveries = dict deliveries = [.. dict
.Where(a => a.Value > threshold) .Where(a => a.Value > threshold)
.Select(a => a.Key) .Select(a => a.Key)];
.ToList(); }
}
deliveries.ForEach(d => { d.PartFilter = predicate; });
var deliveryPartsNum = await deliveryPartsQuery.CountAsync();
var varieties = await deliveryPartsQuery.Select(d => d.SortId).Distinct().ToListAsync();
var stat = await DeliveryService.GenerateToolTipData(deliveryPartsQuery);
return (filter, deliveries, deliveryPartsNum, varieties, stat);
});
if (!cursor) Mouse.OverrideCursor = null;
if (query != (ViewModel.SearchQuery, ViewModel.FilterSeason, ViewModel.FilterAllSeasons, ViewModel.FilterTodayOnly)) return;
deliveries.ForEach(d => { d.PartFilter = predicate; });
ControlUtils.RenewItemsSource(DeliveryList, deliveries, ControlUtils.RenewItemsSource(DeliveryList, deliveries,
DeliveryList_SelectionChanged, filter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort); DeliveryList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
await RefreshDeliveryParts(); await RefreshDeliveryParts();
var members = deliveries.Select(d => d.Member).DistinctBy(m => m.MgNr).ToList(); var members = deliveries.Select(d => d.Member).DistinctBy(m => m.MgNr).ToList();
@@ -454,11 +468,9 @@ namespace Elwig.Windows {
ViewModel.StatusDeliveries = $"{deliveries.Count:N0}"; ViewModel.StatusDeliveries = $"{deliveries.Count:N0}";
if (filter.Count == 0) { if (filter.Count == 0) {
var deliveryParts = deliveryPartsQuery; ViewModel.StatusDeliveries = $"{deliveries.Count:N0} ({deliveryPartsNum:N0})";
ViewModel.StatusDeliveries = $"{deliveries.Count:N0} ({await deliveryParts.CountAsync():N0})";
var varieties = await deliveryParts.Select(d => d.SortId).Distinct().ToListAsync();
ViewModel.StatusVarieties = $"{varieties.Count:N0}" + (varieties.Count > 0 && varieties.Count <= 10 ? $" ({string.Join(", ", varieties)})" : ""); ViewModel.StatusVarieties = $"{varieties.Count:N0}" + (varieties.Count > 0 && varieties.Count <= 10 ? $" ({string.Join(", ", varieties)})" : "");
var (wText, wData, gText, gData) = await DeliveryService.GenerateToolTipData(deliveryParts); var (wText, wData, gText, gData) = stat;
ViewModel.StatusWeight = wText; ViewModel.StatusWeight = wText;
ViewModel.StatusGradation = gText; ViewModel.StatusGradation = gText;
(ViewModel.StatusWeightToolTip, ViewModel.StatusGradationToolTip) = DeliveryService.GenerateToolTip(wData, gData); (ViewModel.StatusWeightToolTip, ViewModel.StatusGradationToolTip) = DeliveryService.GenerateToolTip(wData, gData);

View File

@@ -8,8 +8,7 @@
xmlns:local="clr-namespace:Elwig.Windows" xmlns:local="clr-namespace:Elwig.Windows"
xmlns:vm="clr-namespace:Elwig.ViewModels" xmlns:vm="clr-namespace:Elwig.ViewModels"
xmlns:ctrl="clr-namespace:Elwig.Controls" xmlns:ctrl="clr-namespace:Elwig.Controls"
Title="Anmeldungen - Elwig" Height="700" Width="980" MinWidth="600" MinHeight="400" Title="Anmeldungen - Elwig" Height="700" Width="980" MinWidth="600" MinHeight="400">
Loaded="Window_Loaded">
<Window.DataContext> <Window.DataContext>
<vm:DeliveryAncmtAdminViewModel/> <vm:DeliveryAncmtAdminViewModel/>
</Window.DataContext> </Window.DataContext>

View File

@@ -36,10 +36,10 @@ namespace Elwig.Windows {
ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged); ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged; SearchInput.TextChanged -= SearchInput_TextChanged;
ViewModel.FilterSeason = Utils.CurrentLastSeason; ViewModel.FilterSeason = Utils.CurrentLastSeason;
ViewModel.FilterOnlyUpcoming = true;
} }
private void Window_Loaded(object sender, RoutedEventArgs evt) { protected override async Task OnInit(AppDbContext ctx) {
ViewModel.FilterOnlyUpcoming = true;
LockInputs(); LockInputs();
} }
@@ -103,36 +103,46 @@ namespace Elwig.Windows {
} }
private async Task RefreshList(bool updateSort = false) { private async Task RefreshList(bool updateSort = false) {
using var ctx = new AppDbContext(); var vm = ViewModel;
var (_, deliveryAncmtQuery, filter) = await ViewModel.GetFilters(ctx); var cursor = Mouse.OverrideCursor != null;
var deliveryAncmts = await deliveryAncmtQuery if (!cursor) Mouse.OverrideCursor = Cursors.Wait;
.Include(a => a.Member.BillingAddress) var query = (vm.SearchQuery, vm.FilterSeason, vm.FilterOnlyUpcoming, vm.FilterOnlyUpcoming);
.Include(a => a.Schedule) var (filter, deliveryAncmts, stat) = await Task.Run(async () => {
.Include(a => a.Variety) using var ctx = new AppDbContext();
.AsSplitQuery() var (_, deliveryAncmtQuery, filter) = await vm.GetFilters(ctx);
.ToListAsync(); var deliveryAncmts = await deliveryAncmtQuery
.Include(a => a.Member.BillingAddress)
.Include(a => a.Schedule)
.Include(a => a.Variety)
.AsSplitQuery()
.ToListAsync();
if (filter.Count > 0 && deliveryAncmts.Count > 0) { if (filter.Count > 0 && deliveryAncmts.Count > 0) {
var dict = deliveryAncmts.AsParallel() var dict = deliveryAncmts.AsParallel()
.ToDictionary(a => a, a => a.SearchScore(filter)) .ToDictionary(a => a, a => a.SearchScore(filter))
.OrderByDescending(a => a.Value) .OrderByDescending(a => a.Value)
.ThenBy(a => a.Key.Schedule.DateString) .ThenBy(a => a.Key.Schedule.DateString)
.ThenBy(a => a.Key.Member.Name) .ThenBy(a => a.Key.Member.Name)
.ThenBy(a => a.Key.Member.GivenName) .ThenBy(a => a.Key.Member.GivenName)
.ThenBy(a => a.Key.Member.MgNr); .ThenBy(a => a.Key.Member.MgNr);
var threshold = dict.Select(a => a.Value).Max() * 3 / 4; var threshold = dict.Max(a => a.Value) * 3 / 4;
deliveryAncmts = dict deliveryAncmts = [.. dict
.Where(a => a.Value > threshold) .Where(a => a.Value > threshold)
.Select(a => a.Key) .Select(a => a.Key)];
.ToList(); } else {
} else { deliveryAncmts = [.. deliveryAncmts
deliveryAncmts = deliveryAncmts .OrderBy(a => a.Schedule.DateString)
.OrderBy(a => a.Schedule.DateString) .ThenBy(a => a.Member.Name)
.ThenBy(a => a.Member.Name) .ThenBy(a => a.Member.GivenName)
.ThenBy(a => a.Member.GivenName) .ThenBy(a => a.Member.MgNr)];
.ThenBy(a => a.Member.MgNr) }
.ToList();
} var stat = await DeliveryAncmtService.GenerateToolTipData(deliveryAncmtQuery);
return (filter, deliveryAncmts, stat);
});
if (!cursor) Mouse.OverrideCursor = null;
if (query != (ViewModel.SearchQuery, ViewModel.FilterSeason, ViewModel.FilterOnlyUpcoming, ViewModel.FilterOnlyUpcoming)) return;
ControlUtils.RenewItemsSource(DeliveryAncmtList, deliveryAncmts, ControlUtils.RenewItemsSource(DeliveryAncmtList, deliveryAncmts,
DeliveryAncmtList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort); DeliveryAncmtList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
@@ -141,9 +151,9 @@ namespace Elwig.Windows {
ViewModel.StatusAncmts = $"{deliveryAncmts.Count:N0}"; ViewModel.StatusAncmts = $"{deliveryAncmts.Count:N0}";
if (filter.Count == 0) { if (filter.Count == 0) {
var (text, grid) = await DeliveryAncmtService.GenerateToolTip(deliveryAncmtQuery); var (text, data) = stat;
ViewModel.StatusWeight = text; ViewModel.StatusWeight = text;
ViewModel.StatusWeightToolTip = grid; ViewModel.StatusWeightToolTip = DeliveryAncmtService.GenerateToolTip(data);
} else { } else {
ViewModel.StatusWeight = $"{deliveryAncmts.Sum(a => a.Weight):N0} kg"; ViewModel.StatusWeight = $"{deliveryAncmts.Sum(a => a.Weight):N0} kg";
ViewModel.StatusWeightToolTip = null; ViewModel.StatusWeightToolTip = null;

View File

@@ -7,8 +7,7 @@
xmlns:local="clr-namespace:Elwig.Windows" xmlns:local="clr-namespace:Elwig.Windows"
xmlns:vm="clr-namespace:Elwig.ViewModels" xmlns:vm="clr-namespace:Elwig.ViewModels"
xmlns:ctrl="clr-namespace:Elwig.Controls" xmlns:ctrl="clr-namespace:Elwig.Controls"
Title="Leseplanung - Elwig" Height="600" Width="850" MinHeight="450" MinWidth="800" Title="Leseplanung - Elwig" Height="600" Width="850" MinHeight="450" MinWidth="800">
Loaded="Window_Loaded">
<Window.DataContext> <Window.DataContext>
<vm:DeliveryScheduleAdminViewModel/> <vm:DeliveryScheduleAdminViewModel/>
</Window.DataContext> </Window.DataContext>

View File

@@ -30,41 +30,49 @@ namespace Elwig.Windows {
ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged); ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged; SearchInput.TextChanged -= SearchInput_TextChanged;
ViewModel.FilterSeason = Utils.CurrentLastSeason; ViewModel.FilterSeason = Utils.CurrentLastSeason;
ViewModel.FilterOnlyUpcoming = true;
} }
private void Window_Loaded(object sender, RoutedEventArgs evt) { protected override async Task OnInit(AppDbContext ctx) {
ViewModel.FilterOnlyUpcoming = true;
LockInputs(); LockInputs();
} }
private async Task RefreshList(bool updateSort = false) { private async Task RefreshList(bool updateSort = false) {
using var ctx = new AppDbContext(); var vm = ViewModel;
var (_, deliveryScheduleQuery, filter) = await ViewModel.GetFilters(ctx); var cursor = Mouse.OverrideCursor != null;
var deliverySchedules = await deliveryScheduleQuery if (!cursor) Mouse.OverrideCursor = Cursors.Wait;
.Include(s => s.Varieties) var query = (vm.SearchQuery, vm.FilterSeason, vm.FilterOnlyUpcoming);
.Include(s => s.Branch) var deliverySchedules = await Task.Run(async () => {
.AsSplitQuery() using var ctx = new AppDbContext();
.ToListAsync(); var (_, deliveryScheduleQuery, filter) = await vm.GetFilters(ctx);
var deliverySchedules = await deliveryScheduleQuery
.Include(s => s.Varieties)
.Include(s => s.Branch)
.AsSplitQuery()
.ToListAsync();
if (filter.Count > 0 && deliverySchedules.Count > 0) { if (filter.Count > 0 && deliverySchedules.Count > 0) {
var dict = deliverySchedules.AsParallel() var dict = deliverySchedules.AsParallel()
.ToDictionary(s => s, s => s.SearchScore(filter)) .ToDictionary(s => s, s => s.SearchScore(filter))
.OrderByDescending(a => a.Value) .OrderByDescending(a => a.Value)
.ThenBy(a => a.Key.DateString) .ThenBy(a => a.Key.DateString)
.ThenBy(a => a.Key.Branch.Name) .ThenBy(a => a.Key.Branch.Name)
.ThenBy(a => a.Key.Description); .ThenBy(a => a.Key.Description);
var threshold = dict.Select(a => a.Value).Max() * 3 / 4; var threshold = dict.Max(a => a.Value) * 3 / 4;
deliverySchedules = dict deliverySchedules = [.. dict
.Where(a => a.Value > threshold) .Where(a => a.Value > threshold)
.Select(a => a.Key) .Select(a => a.Key)];
.ToList(); } else {
} else { deliverySchedules = [.. deliverySchedules
deliverySchedules = deliverySchedules .OrderBy(s => s.DateString)
.OrderBy(s => s.DateString) .ThenBy(s => s.Branch.Name)
.ThenBy(s => s.Branch.Name) .ThenBy(s => s.Description)];
.ThenBy(s => s.Description) }
.ToList();
} return deliverySchedules;
});
if (!cursor) Mouse.OverrideCursor = null;
if (query != ((ViewModel.SearchQuery, ViewModel.FilterSeason, ViewModel.FilterOnlyUpcoming))) return;
ControlUtils.RenewItemsSource(DeliveryScheduleList, deliverySchedules, ControlUtils.RenewItemsSource(DeliveryScheduleList, deliverySchedules,
DeliveryScheduleList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort); DeliveryScheduleList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);

View File

@@ -5,7 +5,7 @@
xmlns:local="clr-namespace:Elwig.Windows" xmlns:local="clr-namespace:Elwig.Windows"
xmlns:ctrl="clr-namespace:Elwig.Controls" xmlns:ctrl="clr-namespace:Elwig.Controls"
Title="Elwig" Height="390" Width="520" ResizeMode="CanMinimize" Title="Elwig" Height="390" Width="520" ResizeMode="CanMinimize"
Loaded="Window_Loaded" Closing="Window_Closing"> Closing="Window_Closing">
<Window.Resources> <Window.Resources>
<Style TargetType="Button"> <Style TargetType="Button">
<Setter Property="VerticalAlignment" Value="Top"/> <Setter Property="VerticalAlignment" Value="Top"/>

View File

@@ -37,11 +37,10 @@ namespace Elwig.Windows {
SyncButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden; SyncButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden;
Menu_Database_Upload.IsEnabled = App.Config.SyncUrl != null; Menu_Database_Upload.IsEnabled = App.Config.SyncUrl != null;
Menu_Database_Download.IsEnabled = App.Config.SyncUrl != null; Menu_Database_Download.IsEnabled = App.Config.SyncUrl != null;
SeasonInput.Value = Utils.CurrentLastSeason;
} }
private void Window_Loaded(object sender, RoutedEventArgs evt) { protected override async Task OnInit(AppDbContext ctx) {
SeasonInput.Value = Utils.CurrentLastSeason;
if (Utils.HasInternetConnectivity()) { if (Utils.HasInternetConnectivity()) {
CheckSync(200); CheckSync(200);
} }

View File

@@ -5,8 +5,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Elwig.Windows" xmlns:local="clr-namespace:Elwig.Windows"
xmlns:vm="clr-namespace:Elwig.ViewModels" xmlns:vm="clr-namespace:Elwig.ViewModels"
Title="Mitglieder - Elwig" Height="700" Width="1250" MinHeight="650" MinWidth="1150" Title="Mitglieder - Elwig" Height="700" Width="1250" MinHeight="650" MinWidth="1150">
Loaded="Window_Loaded">
<Window.DataContext> <Window.DataContext>
<vm:MemberAdminViewModel/> <vm:MemberAdminViewModel/>
</Window.DataContext> </Window.DataContext>

View File

@@ -82,10 +82,10 @@ namespace Elwig.Windows {
Menu_Export_UploadFilters.IsEnabled = App.Config.SyncUrl != null; Menu_Export_UploadFilters.IsEnabled = App.Config.SyncUrl != null;
Menu_Export_UploadAll.IsEnabled = App.Config.SyncUrl != null; Menu_Export_UploadAll.IsEnabled = App.Config.SyncUrl != null;
ViewModel.ShowOnlyActiveMembers = true;
} }
private void Window_Loaded(object sender, RoutedEventArgs evt) { protected override async Task OnInit(AppDbContext ctx) {
ViewModel.ShowOnlyActiveMembers = true;
UpdateContactInfoVisibility(); UpdateContactInfoVisibility();
LockInputs(); LockInputs();
} }
@@ -113,46 +113,57 @@ namespace Elwig.Windows {
} }
private async Task RefreshList(bool updateSort = false) { private async Task RefreshList(bool updateSort = false) {
using var ctx = new AppDbContext(); var vm = ViewModel;
var (_, memberQuery, filter) = await ViewModel.GetFilters(ctx); var cursor = Mouse.OverrideCursor != null;
var members = await memberQuery if (!cursor) Mouse.OverrideCursor = Cursors.Wait;
.Include(m => m.Branch) var query = (vm.SearchQuery, vm.ShowOnlyActiveMembers);
.Include(m => m.DefaultWbKg!.AtKg) var (members, totalMemberCount, totalBusinessShares) = await Task.Run(async () => {
.Include(m => m.EmailAddresses) using var ctx = new AppDbContext();
.Include(m => m.TelephoneNumbers) var (_, memberQuery, filter) = await vm.GetFilters(ctx);
.Include(m => m.PostalDest.AtPlz!.Ort) var members = await memberQuery
.Include(m => m.PostalDest.AtPlz!.Country) .Include(m => m.Branch)
.Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort) .Include(m => m.DefaultWbKg!.AtKg)
.Include(m => m.BillingAddress!.PostalDest.AtPlz!.Country) .Include(m => m.EmailAddresses)
.ToListAsync(); .Include(m => m.TelephoneNumbers)
.Include(m => m.PostalDest.AtPlz!.Ort)
.Include(m => m.PostalDest.AtPlz!.Country)
.Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
.Include(m => m.BillingAddress!.PostalDest.AtPlz!.Country)
.ToListAsync();
if (filter.Count > 0 && members.Count > 0) { if (filter.Count > 0 && members.Count > 0) {
var dict = members.AsParallel() var dict = members.AsParallel()
.ToDictionary(m => m, m => m.SearchScore(filter)) .ToDictionary(m => m, m => m.SearchScore(filter))
.OrderByDescending(a => a.Value) .OrderByDescending(a => a.Value)
.ThenBy(a => a.Key.Name) .ThenBy(a => a.Key.Name)
.ThenBy(a => a.Key.GivenName) .ThenBy(a => a.Key.GivenName)
.ThenBy(a => a.Key.MgNr); .ThenBy(a => a.Key.MgNr);
var threshold = dict.Select(a => a.Value).Max() * 3 / 4; var threshold = dict.Select(a => a.Value).Max() * 3 / 4;
members = dict members = [.. dict
.Where(a => a.Value > threshold) .Where(a => a.Value > threshold)
.Select(a => a.Key) .Select(a => a.Key)];
.ToList(); } else {
} else { members = [.. members
members = members .OrderBy(m => m.Name)
.OrderBy(m => m.Name) .ThenBy(m => m.GivenName)
.ThenBy(m => m.GivenName) .ThenBy(m => m.MgNr)];
.ThenBy(m => m.MgNr) }
.ToList();
} var totalMemberCount = await ctx.Members.CountAsync();
var totalBusinessShares = await ctx.Members.SumAsync(m => m.BusinessShares);
return (members, totalMemberCount, totalBusinessShares);
});
if (!cursor) Mouse.OverrideCursor = null;
if (query != (ViewModel.SearchQuery, ViewModel.ShowOnlyActiveMembers)) return;
ControlUtils.RenewItemsSource(MemberList, members, ControlUtils.RenewItemsSource(MemberList, members,
MemberList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort); MemberList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
if (updateSort && MemberList.SelectedItem != null) if (updateSort && MemberList.SelectedItem != null)
MemberList.ScrollIntoView(MemberList.SelectedItem); MemberList.ScrollIntoView(MemberList.SelectedItem);
ViewModel.StatusMembers = $"{members.Count:N0} ({await ctx.Members.CountAsync():N0})"; ViewModel.StatusMembers = $"{members.Count:N0} ({totalMemberCount:N0})";
ViewModel.StatusBusinessShares = $"{members.Sum(m => m.BusinessShares):N0} ({await ctx.Members.SumAsync(m => m.BusinessShares):N0})"; ViewModel.StatusBusinessShares = $"{members.Sum(m => m.BusinessShares):N0} ({totalBusinessShares:N0})";
} }
private void RefreshInputs(bool validate = false) { private void RefreshInputs(bool validate = false) {