Add utility classes for theme management, waiting, watched products, and notifications
- Implement ThemeManager for dynamic light/dark theme switching in the application. - Create Wait class for cancellable delays without exceptions for smoother user experience. - Introduce WatchedProductsStore to manage and persist watched products in JSON format. - Add WindowsNotifier for system notifications to inform users of important events. - Develop ProductViewModel to encapsulate product data and manage UI interactions effectively.
This commit is contained in:
@@ -4,11 +4,10 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:AutoBidder.Controls"
|
||||
Title="Aggiungi Asta" Height="320" Width="700"
|
||||
Background="#0a0a0a" Foreground="#FFFFFF"
|
||||
Background="{DynamicResource Brush.Bg}" Foreground="{DynamicResource Brush.Text}"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="pack://application:,,,/Icon/favicon.ico"
|
||||
ResizeMode="NoResize">
|
||||
<Border Background="#1a1a1a" CornerRadius="8" Padding="16" Margin="8">
|
||||
<Border Background="{DynamicResource Brush.Bg}" CornerRadius="8" Padding="16" Margin="8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -17,11 +16,11 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="Inserire URL dell'asta (uno o più)" Foreground="#CCCCCC" FontSize="14" Margin="0,0,0,6" />
|
||||
<TextBlock Grid.Row="1" Text="Puoi aggiungere più link separandoli con 'a capo', 'spazio' o ';'" Foreground="#999999" FontSize="12" Margin="0,0,0,10" />
|
||||
<TextBlock Text="Inserire URL dell'asta (uno o più)" Foreground="{DynamicResource Brush.Text}" FontSize="14" Margin="0,0,0,6" />
|
||||
<TextBlock Grid.Row="1" Text="Puoi aggiungere più link separandoli con 'a capo', 'spazio' o ';'" Foreground="{DynamicResource Brush.TextMuted}" FontSize="12" Margin="0,0,0,10" />
|
||||
|
||||
<TextBox x:Name="AuctionUrlBox" Grid.Row="2" MinWidth="560" Margin="0,0,0,8"
|
||||
Background="#181818" Foreground="#00CCFF" BorderBrush="#444" BorderThickness="1"
|
||||
Background="{DynamicResource Brush.Bg}" Foreground="{DynamicResource Brush.Info}" BorderBrush="{DynamicResource Brush.Border}" BorderThickness="1"
|
||||
Padding="8" FontSize="13" ToolTip="Inserisci uno o più URL/ID dell'asta. Separali con a capo, spazio o ';'"
|
||||
AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" Height="160" />
|
||||
|
||||
@@ -29,9 +28,9 @@
|
||||
<controls:SimpleToolbar.RightContent>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Content="OK" Width="110" Margin="6" Padding="10,8"
|
||||
Style="{StaticResource SmallButtonStyle}" Background="#00CC66" Foreground="White" Click="OkButton_Click" />
|
||||
Style="{StaticResource SmallButtonStyle}" Background="{DynamicResource Brush.Success}" Foreground="{DynamicResource Brush.TextOnAccent}" Click="OkButton_Click" />
|
||||
<Button Content="Annulla" Width="110" Margin="6" Padding="10,8"
|
||||
Style="{StaticResource SmallButtonStyle}" Background="#666" Foreground="White" Click="CancelButton_Click" />
|
||||
Style="{StaticResource SmallButtonStyle}" Background="{DynamicResource Brush.SurfaceAlt}" Foreground="{DynamicResource Brush.Text}" Click="CancelButton_Click" />
|
||||
</StackPanel>
|
||||
</controls:SimpleToolbar.RightContent>
|
||||
</controls:SimpleToolbar>
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
<Window x:Class="AutoBidder.Dialogs.ClosedAuctionsWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Aste Chiuse - Estrazione" Height="600" Width="1000"
|
||||
Background="#0a0a0a" Foreground="#FFFFFF" WindowStartupLocation="CenterOwner">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="8" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Background="#1a1a1a" Padding="10" CornerRadius="6" Grid.Row="0" BorderBrush="#333333" BorderThickness="1">
|
||||
<DockPanel>
|
||||
<TextBlock Text="Estrazione Aste Chiuse" FontSize="16" FontWeight="Bold" Foreground="#00CC66" VerticalAlignment="Center" DockPanel.Dock="Left" />
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" DockPanel.Dock="Right">
|
||||
<Button x:Name="StartExtractButton" Content="Avvia Estrazione" Click="StartExtractButton_Click" Width="140" Height="36" Margin="8,0,0,0" Background="#00CC66" Style="{StaticResource SmallButtonStyle}"/>
|
||||
<Button x:Name="ExportStatsButton" Content="Esporta Statistiche" Click="ExportStatsButton_Click" Width="160" Height="36" Margin="8,0,0,0" Background="#8B5CF6" Style="{StaticResource SmallButtonStyle}"/>
|
||||
<Button x:Name="CloseButton" Content="Chiudi" Click="CloseButton_Click" Width="80" Height="36" Margin="8,0,0,0" Background="#666" Style="{StaticResource SmallButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="2" Background="#1a1a1a" Padding="8" CornerRadius="6" BorderBrush="#333333" BorderThickness="1">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="8" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Products grid: styled like MainWindow -->
|
||||
<DataGrid x:Name="ProductsGrid" AutoGenerateColumns="False" Grid.Column="0" Background="#1a1a1a" Foreground="#FFFFFF" BorderBrush="#333333" BorderThickness="1"
|
||||
RowBackground="#1a1a1a" AlternatingRowBackground="#222222" GridLinesVisibility="Horizontal" HorizontalGridLinesBrush="#333333"
|
||||
IsReadOnly="True" SelectionUnit="CellOrRowHeader" SelectionMode="Extended" ClipboardCopyMode="IncludeHeader" CanUserAddRows="False" CanUserDeleteRows="False">
|
||||
<DataGrid.Resources>
|
||||
<Style TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Background" Value="#2a2a2a" />
|
||||
<Setter Property="Foreground" Value="#FFFFFF" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Padding" Value="10,8" />
|
||||
<Setter Property="BorderThickness" Value="0,0,0,2" />
|
||||
<Setter Property="BorderBrush" Value="#00CC66" />
|
||||
</Style>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="Height" Value="36" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="#0099FF" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style TargetType="DataGridCell">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="10,6" />
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Command="ApplicationCommands.Copy" Header="Copia" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Asta URL" Binding="{Binding AuctionUrl}" Width="2*" />
|
||||
<DataGridTextColumn Header="Nome" Binding="{Binding ProductName}" Width="3*"/>
|
||||
<DataGridTextColumn Header="Prezzo" Binding="{Binding FinalPrice}" Width="80"/>
|
||||
<DataGridTextColumn Header="Vincitore" Binding="{Binding Winner}" Width="120"/>
|
||||
<DataGridTextColumn Header="Puntate Usate" Binding="{Binding BidsUsed}" Width="100"/>
|
||||
<DataGridTextColumn Header="Scraped At" Binding="{Binding ScrapedAt}" Width="140"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<GridSplitter Grid.Column="1" Width="8" />
|
||||
|
||||
<!-- Log area styled like main window log -->
|
||||
<Border Grid.Column="2" Background="#0f0f0f" Padding="8" CornerRadius="6" BorderBrush="#333333" BorderThickness="1">
|
||||
<DockPanel>
|
||||
<TextBlock Text="Log Operazioni" FontWeight="Bold" Foreground="#00CC66" DockPanel.Dock="Top" Margin="0,0,0,8" />
|
||||
<RichTextBox x:Name="ExtractLogBox" IsReadOnly="True" VerticalScrollBarVisibility="Auto" FontFamily="Consolas" FontSize="11" Background="#0f0f0f" Foreground="#CCC" BorderBrush="#333333" BorderThickness="1" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,140 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using Microsoft.Win32;
|
||||
using AutoBidder.Models;
|
||||
using AutoBidder.Services;
|
||||
using AutoBidder.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AutoBidder.Dialogs
|
||||
{
|
||||
public partial class ClosedAuctionsWindow : Window
|
||||
{
|
||||
private ObservableCollection<ClosedAuctionRecord> _products = new();
|
||||
|
||||
private bool _isRunning = false;
|
||||
|
||||
// StatsService using local DB. Create context with default sqlite file in app folder
|
||||
private readonly StatsService _statsService;
|
||||
|
||||
public ClosedAuctionsWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
ProductsGrid.ItemsSource = _products;
|
||||
|
||||
var optionsBuilder = new DbContextOptionsBuilder<StatisticsContext>();
|
||||
var dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "stats.db");
|
||||
optionsBuilder.UseSqlite($"Data Source={dbPath}");
|
||||
var ctx = new StatisticsContext(optionsBuilder.Options);
|
||||
_statsService = new StatsService(ctx);
|
||||
|
||||
Log("Finestra pronta");
|
||||
}
|
||||
|
||||
private void Log(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var para = new Paragraph(new Run($"{DateTime.Now:HH:mm} - {message}"));
|
||||
ExtractLogBox.Document.Blocks.Add(para);
|
||||
// keep size manageable
|
||||
while (ExtractLogBox.Document.Blocks.Count > 500)
|
||||
ExtractLogBox.Document.Blocks.Remove(ExtractLogBox.Document.Blocks.FirstBlock);
|
||||
ExtractLogBox.ScrollToEnd();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async void StartExtractButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isRunning)
|
||||
{
|
||||
Log("Estrazione già in corso");
|
||||
return;
|
||||
}
|
||||
|
||||
_isRunning = true;
|
||||
StartExtractButton.IsEnabled = false;
|
||||
Log("Avvio procedura di estrazione da closed_auctions.php...");
|
||||
|
||||
try
|
||||
{
|
||||
var scraper = new ClosedAuctionsScraper(null, _statsService, Log);
|
||||
var closedUrl = "https://it.bidoo.com/closed_auctions.php";
|
||||
|
||||
Log($"Scarico: {closedUrl}");
|
||||
|
||||
int count = 0;
|
||||
await foreach (var rec in scraper.ScrapeYieldAsync(closedUrl))
|
||||
{
|
||||
// Filter out records without bids info (user requested)
|
||||
if (!rec.BidsUsed.HasValue)
|
||||
{
|
||||
Log($"Scartata asta (mancano puntate): {rec.AuctionUrl} - '{rec.ProductName ?? "?"}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add and log incrementally so user sees progress
|
||||
_products.Add(rec);
|
||||
count++;
|
||||
Log($"[{count}] {rec.ProductName} | Prezzo: {(rec.FinalPrice.HasValue?rec.FinalPrice.Value.ToString("F2")+"€":"--")} | Vincitore: {rec.Winner ?? "--"} | Puntate: {rec.BidsUsed.Value} | URL: {rec.AuctionUrl}");
|
||||
}
|
||||
|
||||
Log($"Estrazione completata: {count} record aggiunti.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"[ERRORE] Estrattore: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isRunning = false;
|
||||
StartExtractButton.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private async void ExportStatsButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log("Preparazione esportazione statistiche...");
|
||||
var stats = await _statsService.GetAllStatsAsync();
|
||||
if (stats == null || stats.Count == 0)
|
||||
{
|
||||
Log("Nessuna statistica disponibile da esportare.");
|
||||
MessageBox.Show(this, "Nessuna statistica disponibile.", "Esporta Statistiche", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var dlg = new SaveFileDialog() { Filter = "CSV files|*.csv|All files|*.*", FileName = "auction_stats.csv" };
|
||||
if (dlg.ShowDialog(this) != true) return;
|
||||
|
||||
using var sw = new StreamWriter(dlg.FileName, false, System.Text.Encoding.UTF8);
|
||||
sw.WriteLine("ProductKey,ProductName,TotalAuctions,AverageBidsUsed,AverageFinalPrice,LastSeen");
|
||||
foreach (var s in stats)
|
||||
{
|
||||
var line = $"\"{s.ProductKey}\",\"{s.ProductName}\",{s.TotalAuctions},{s.AverageBidsUsed:F2},{s.AverageFinalPrice:F2},{s.LastSeen:O}";
|
||||
sw.WriteLine(line);
|
||||
}
|
||||
|
||||
Log($"Statistiche esportate su: {dlg.FileName}");
|
||||
MessageBox.Show(this, "Statistiche esportate con successo.", "Esporta Statistiche", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"[ERRORE] Esporta: {ex.Message}");
|
||||
MessageBox.Show(this, "Errore durante esportazione: " + ex.Message, "Esporta Statistiche", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,10 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Configura Sessione" Height="440" Width="700"
|
||||
Background="#0a0a0a" Foreground="#FFFFFF"
|
||||
Background="{DynamicResource Brush.Bg}" Foreground="{DynamicResource Brush.Text}"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="pack://application:,,,/Icon/favicon.ico"
|
||||
ResizeMode="NoResize">
|
||||
<Border Background="#1a1a1a" CornerRadius="8" Padding="16" Margin="8">
|
||||
<Border Background="{DynamicResource Brush.Bg}" CornerRadius="8" Padding="16" Margin="8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -15,9 +14,9 @@
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Text="Inserisci Cookie di sessione" FontSize="16" FontWeight="SemiBold" Foreground="#00CC66" Margin="0,0,0,12" />
|
||||
<TextBlock Grid.Row="1" Text="Cookie di sessione (copia dal menu delle Impostazioni/Applicazioni di Chrome)" Foreground="#CCCCCC" FontSize="13" />
|
||||
<TextBlock Grid.Row="2" Foreground="#888" FontSize="12" TextWrapping="Wrap" Margin="0,4,0,10">
|
||||
<TextBlock Text="Inserisci Cookie di sessione" FontSize="16" FontWeight="SemiBold" Foreground="{DynamicResource Brush.Success}" Margin="0,0,0,12" />
|
||||
<TextBlock Grid.Row="1" Text="Cookie di sessione (copia dal menu delle Impostazioni/Applicazioni di Chrome)" Foreground="{DynamicResource Brush.Text}" FontSize="13" />
|
||||
<TextBlock Grid.Row="2" Foreground="{DynamicResource Brush.TextMuted}" FontSize="12" TextWrapping="Wrap" Margin="0,4,0,10">
|
||||
<Run Text="Come trovare i cookie di sessione in Chrome:" />
|
||||
<LineBreak/>
|
||||
<Run Text="1. Apri Chrome e premi F12 (Windows) o Cmd+Option+I (Mac) per aprire gli Strumenti per sviluppatori." />
|
||||
@@ -29,12 +28,12 @@
|
||||
<Run Text="4. Scegli il sito desiderato e copia il valore del cookie di sessione." />
|
||||
</TextBlock>
|
||||
<TextBox x:Name="CookieBox" Grid.Row="3" MinWidth="320" MinHeight="120" MaxHeight="220" VerticalScrollBarVisibility="Auto"
|
||||
Background="#181818" Foreground="#00CCFF" BorderBrush="#444" BorderThickness="1" Padding="8" FontSize="14" AcceptsReturn="True" TextWrapping="Wrap" />
|
||||
Background="{DynamicResource Brush.Bg}" Foreground="{DynamicResource Brush.Info}" BorderBrush="{DynamicResource Brush.Border}" BorderThickness="1" Padding="8" FontSize="14" AcceptsReturn="True" TextWrapping="Wrap" />
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
|
||||
<Button x:Name="OkButton" Content="OK" Width="110" Margin="6" Padding="10,8"
|
||||
Style="{StaticResource SmallButtonStyle}" Background="#00CC66" Foreground="White" Click="OkButton_Click" />
|
||||
Style="{StaticResource SmallButtonStyle}" Background="{DynamicResource Brush.Success}" Foreground="{DynamicResource Brush.TextOnAccent}" Click="OkButton_Click" />
|
||||
<Button x:Name="CancelButton" Content="Annulla" Width="110" Margin="6" Padding="10,8"
|
||||
Style="{StaticResource SmallButtonStyle}" Background="#666" Foreground="White" Click="CancelButton_Click" />
|
||||
Style="{StaticResource SmallButtonStyle}" Background="{DynamicResource Brush.SurfaceAlt}" Foreground="{DynamicResource Brush.Text}" Click="CancelButton_Click" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<Window x:Class="AutoBidder.Dialogs.StatsCleanupDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Pulizia dello storico"
|
||||
Width="620" SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ShowInTaskbar="False"
|
||||
Background="{DynamicResource Brush.Bg}">
|
||||
|
||||
<StackPanel Margin="22,18">
|
||||
|
||||
<TextBlock Text="Cosa considerare incompleto"
|
||||
FontSize="{StaticResource Font.Size.Lg}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource Brush.Text}"/>
|
||||
|
||||
<TextBlock Style="{StaticResource Hint}" Margin="0,6,0,14"
|
||||
Text="Il conto si aggiorna mentre scegli. Niente viene toccato finché non premi Pulisci, e viene salvata una copia dello storico prima di procedere."/>
|
||||
|
||||
<CheckBox x:Name="ChkNoPrice" Margin="0,4" IsChecked="True"
|
||||
Content="Aste senza prezzo finale"
|
||||
ToolTip="Non dicono nulla e sporcano ogni media: il prezzo è il dato su cui si regge tutto il resto."
|
||||
Checked="Option_Changed" Unchecked="Option_Changed"/>
|
||||
|
||||
<CheckBox x:Name="ChkBadBids" Margin="0,4" IsChecked="True"
|
||||
Content="Aste con puntate del vincitore incoerenti col prezzo"
|
||||
ToolTip="Ogni puntata alza il prezzo di un centesimo, quindi il prezzo in centesimi è il totale delle puntate dell'asta. Un vincitore che ne dichiara di più — o zero — porta un dato che non può essere vero."
|
||||
Checked="Option_Changed" Unchecked="Option_Changed"/>
|
||||
|
||||
<CheckBox x:Name="ChkDuplicates" Margin="0,4" IsChecked="True"
|
||||
Content="Doppioni con lo stesso identificativo (tiene il più recente)"
|
||||
ToolTip="La stessa asta può essere registrata due volte: quando il motore ne vede la fine e quando viene tolta dal monitor. Si tiene la versione più recente."
|
||||
Checked="Option_Changed" Unchecked="Option_Changed"/>
|
||||
|
||||
<CheckBox x:Name="ChkNoWinnerBids" Margin="0,4"
|
||||
Content="Aste di cui non si conoscono le puntate del vincitore"
|
||||
ToolTip="Severo: toglie tutte le aste su cui non si può calcolare il costo reale. Prima di usarlo, prova «Recupera puntate dei vincitori» nella scheda Storico — il dato si può quasi sempre riprendere dal server."
|
||||
Checked="Option_Changed" Unchecked="Option_Changed"/>
|
||||
|
||||
<CheckBox x:Name="ChkNoWinner" Margin="0,4"
|
||||
Content="Aste senza vincitore"
|
||||
ToolTip="Aste scadute senza offerte, oppure registrate a metà."
|
||||
Checked="Option_Changed" Unchecked="Option_Changed"/>
|
||||
|
||||
<CheckBox x:Name="ChkNoValue" Margin="0,4"
|
||||
Content="Aste senza valore del prodotto"
|
||||
ToolTip="Senza il «Compra Subito» non si può calcolare il risparmio, ma il prezzo e le puntate restano validi."
|
||||
Checked="Option_Changed" Unchecked="Option_Changed"/>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
|
||||
<CheckBox x:Name="ChkOlderThan" VerticalAlignment="Center"
|
||||
Content="Aste chiuse da più di"
|
||||
ToolTip="Le aste vecchie descrivono un mercato che può essere cambiato."
|
||||
Checked="Option_Changed" Unchecked="Option_Changed"/>
|
||||
<TextBox x:Name="DaysBox" Text="180" Width="60" Margin="8,0"
|
||||
VerticalAlignment="Center" TextChanged="Option_Changed"/>
|
||||
<TextBlock Text="giorni" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource Brush.Text}"/>
|
||||
</StackPanel>
|
||||
|
||||
<Border Style="{StaticResource InfoBox}" Margin="0,16,0,0">
|
||||
<StackPanel>
|
||||
<TextBlock x:Name="SummaryText" TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource Brush.Text}"
|
||||
FontSize="{StaticResource Font.Size.Sm}" LineHeight="18"
|
||||
Text="Calcolo…"/>
|
||||
<TextBlock x:Name="DetailText" TextWrapping="Wrap" Margin="0,8,0,0"
|
||||
Foreground="{DynamicResource Brush.TextMuted}"
|
||||
FontSize="{StaticResource Font.Size.Sm}" LineHeight="17"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,18,0,0">
|
||||
<Button x:Name="CleanButton" Content="Pulisci" Padding="26,7" Margin="0,0,10,0"
|
||||
Style="{StaticResource ModernButton}"
|
||||
Background="{DynamicResource Brush.Danger}"
|
||||
Foreground="{DynamicResource Brush.TextOnAccent}"
|
||||
Click="Clean_Click"/>
|
||||
<Button Content="Annulla" Padding="26,7"
|
||||
Style="{StaticResource ModernButton}"
|
||||
Background="{DynamicResource Brush.SurfaceAlt}"
|
||||
IsCancel="True"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using AutoBidder.Models;
|
||||
using AutoBidder.Utilities;
|
||||
|
||||
namespace AutoBidder.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// Sceglie cosa considerare incompleto nello storico e lo toglie.
|
||||
///
|
||||
/// <para>Il conto si aggiorna a ogni spunta, <b>prima</b> di toccare qualunque cosa: una
|
||||
/// pulizia è irreversibile, e l'unico modo di renderla una decisione informata è mostrare
|
||||
/// quante righe cadrebbero e per quale motivo mentre si sceglie. La copia di sicurezza
|
||||
/// prima di scrivere è la seconda rete: le statistiche sono mesi di raccolta che nessun
|
||||
/// pulsante ha il diritto di far sparire per un clic distratto.</para>
|
||||
/// </summary>
|
||||
public partial class StatsCleanupDialog : Window
|
||||
{
|
||||
private readonly List<CompletedAuctionRecord> _records;
|
||||
|
||||
public StatsCleanupDialog(List<CompletedAuctionRecord> records)
|
||||
{
|
||||
InitializeComponent();
|
||||
_records = records ?? new List<CompletedAuctionRecord>();
|
||||
Loaded += (_, _) => Refresh();
|
||||
}
|
||||
|
||||
/// <summary>Righe rimosse, dopo la conferma. 0 se l'utente ha annullato.</summary>
|
||||
public int RemovedCount { get; private set; }
|
||||
|
||||
/// <summary>Dove è finita la copia di sicurezza.</summary>
|
||||
public string? BackupPath { get; private set; }
|
||||
|
||||
private StatsMaintenance.CleanupOptions ReadOptions()
|
||||
{
|
||||
var options = new StatsMaintenance.CleanupOptions
|
||||
{
|
||||
RemoveWithoutPrice = ChkNoPrice.IsChecked == true,
|
||||
RemoveInconsistentBids = ChkBadBids.IsChecked == true,
|
||||
RemoveDuplicates = ChkDuplicates.IsChecked == true,
|
||||
RemoveWithoutWinnerBids = ChkNoWinnerBids.IsChecked == true,
|
||||
RemoveWithoutWinner = ChkNoWinner.IsChecked == true,
|
||||
RemoveWithoutValue = ChkNoValue.IsChecked == true
|
||||
};
|
||||
|
||||
if (ChkOlderThan.IsChecked == true &&
|
||||
int.TryParse(DaysBox.Text.Trim(), out var days) && days > 0)
|
||||
{
|
||||
options.OlderThan = DateTime.Now.AddDays(-days);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private void Option_Changed(object sender, RoutedEventArgs e) => Refresh();
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
if (SummaryText == null) return;
|
||||
|
||||
var report = StatsMaintenance.Preview(_records, ReadOptions());
|
||||
|
||||
SummaryText.Text = report.Removed == 0
|
||||
? $"Nessuna delle {report.Total} aste verrebbe tolta."
|
||||
: $"Verrebbero tolte {report.Removed} aste su {report.Total}. " +
|
||||
$"Ne resterebbero {report.Kept}.";
|
||||
|
||||
DetailText.Text = report.ByReason.Count == 0
|
||||
? ""
|
||||
: string.Join("\n", report.ByReason
|
||||
.OrderByDescending(kv => kv.Value)
|
||||
.Select(kv => $"• {kv.Value} {kv.Key}"));
|
||||
|
||||
CleanButton.IsEnabled = report.Removed > 0;
|
||||
}
|
||||
|
||||
private void Clean_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var options = ReadOptions();
|
||||
var report = StatsMaintenance.Preview(_records, options);
|
||||
|
||||
if (report.Removed == 0) return;
|
||||
|
||||
var answer = MessageBox.Show(this,
|
||||
$"Tolgo {report.Removed} aste su {report.Total}.\n\n" +
|
||||
"L'operazione non è reversibile, ma prima viene salvata una copia " +
|
||||
"dello storico nella cartella dei backup.\n\nProcedo?",
|
||||
"Pulizia dello storico", MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||
|
||||
if (answer != MessageBoxResult.Yes) return;
|
||||
|
||||
try
|
||||
{
|
||||
BackupPath = CompletedAuctionsStore.BackupNow();
|
||||
|
||||
// La stessa risposta usata per il conto: contare con una regola e
|
||||
// cancellare con un'altra è il modo classico di far sparire dati che
|
||||
// nessuno voleva perdere.
|
||||
var kept = StatsMaintenance.Apply(_records, options);
|
||||
CompletedAuctionsStore.ReplaceAll(kept);
|
||||
|
||||
RemovedCount = report.Removed;
|
||||
DialogResult = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, $"Pulizia non riuscita: {ex.Message}",
|
||||
"Pulizia dello storico", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user