Files
Mimante/Mimante/Core/EventHandlers/MainWindow.EventHandlers.Stats.cs
Alberto Balbo 6036896f7d Refactoring e nuove funzionalità per AutoBidder v4.0
* Aggiornamento alla versione 4.0.0
* Refactoring architetturale: introdotte partial classes e UserControls modulari per migliorare manutenibilità e leggibilità.
* Aggiunti nuovi UserControls: `AuctionMonitorControl`, `BrowserControl`, `SettingsControl`, `StatisticsControl`.
* Introdotto supporto per WebView2 per il browser integrato.
* Migliorata gestione delle aste: aggiunta/rimozione tramite URL o ID, configurazione predefinita.
* Nuove funzionalità di esportazione: supporto CSV, JSON, XML con opzioni configurabili.
* Logging avanzato: codifica colore per severità e auto-scroll.
* Tema scuro moderno e miglioramenti UI/UX: sidebar di navigazione, griglie virtualizzate, icone emoji.
* Persistenza dati: salvataggio automatico di aste e impostazioni in file JSON.
* Documentazione aggiornata: `README.md`, `CHANGELOG.md` e nuovi file di supporto.
* Miglioramenti alla sicurezza: cookie di sessione salvati in modo sicuro con DPAPI.
* Preparazione per future estensioni: placeholder per funzionalità avanzate e struttura modulare.
2025-11-17 16:01:22 +01:00

272 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Xml.Linq;
using AutoBidder.Models;
using AutoBidder.Utilities;
using Microsoft.EntityFrameworkCore;
namespace AutoBidder
{
/// <summary>
/// Statistics and closed auctions event handlers
/// NOTA: Funzionalità statistiche temporaneamente disabilitate - in sviluppo
/// </summary>
public partial class MainWindow
{
private void ExportStatsButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(this, "Funzionalità statistiche in sviluppo", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
}
private async void LoadClosedAuctionsButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(this, "Funzionalità statistiche in sviluppo", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
/* CODICE TEMPORANEAMENTE DISABILITATO - Statistiche in sviluppo
try
{
StatsStatusText.Text = "Avvio caricamento statistiche...";
var settings = Utilities.SettingsManager.Load();
if (settings == null || string.IsNullOrWhiteSpace(settings.ExportPath) || !Directory.Exists(settings.ExportPath))
{
MessageBox.Show(this, "Percorso export non configurato o non valido. Configuralo nelle impostazioni.", "Carica Statistiche", MessageBoxButton.OK, MessageBoxImage.Warning);
StatsStatusText.Text = "Percorso export non valido";
return;
}
ExportProgressBar.Visibility = Visibility.Visible;
ExportProgressText.Visibility = Visibility.Visible;
ExportProgressText.Text = "Caricamento statistiche...";
var folder = settings.ExportPath!;
var files = Directory.GetFiles(folder, "auction_*.*");
if (files.Length == 0)
{
MessageBox.Show(this, "Nessun file di aste trovato nella cartella di export.", "Carica Statistiche", MessageBoxButton.OK, MessageBoxImage.Information);
StatsStatusText.Text = "Nessun file trovato";
ExportProgressBar.Visibility = Visibility.Collapsed;
ExportProgressText.Visibility = Visibility.Collapsed;
return;
}
var aggregated = new Dictionary<string, List<ClosedAuctionRecord>>(StringComparer.OrdinalIgnoreCase);
await Task.Run(() =>
{
foreach (var f in files)
{
try
{
var ext = Path.GetExtension(f).ToLowerInvariant();
if (ext == ".json")
{
var txt = File.ReadAllText(f, Encoding.UTF8);
try
{
var rec = System.Text.Json.JsonSerializer.Deserialize<ClosedAuctionRecord>(txt);
if (rec != null)
{
var key = (rec.ProductName ?? ExtractProductFromFilename(f) ?? "<unknown>").Trim();
if (!aggregated.ContainsKey(key)) aggregated[key] = new List<ClosedAuctionRecord>();
aggregated[key].Add(rec);
continue;
}
}
catch { }
try
{
var arr = System.Text.Json.JsonSerializer.Deserialize<List<ClosedAuctionRecord>>(txt);
if (arr != null)
{
foreach (var r in arr)
{
var key = (r.ProductName ?? ExtractProductFromFilename(f) ?? "<unknown>").Trim();
if (!aggregated.ContainsKey(key)) aggregated[key] = new List<ClosedAuctionRecord>();
aggregated[key].Add(r);
}
}
}
catch { }
}
else if (ext == ".xml")
{
try
{
var doc = XDocument.Load(f);
var auctionElems = doc.Descendants("Auction");
if (!auctionElems.Any()) auctionElems = doc.Descendants("AuctionExport");
foreach (var n in auctionElems)
{
var name = n.Descendants("Name").FirstOrDefault()?.Value ?? n.Descendants("ProductName").FirstOrDefault()?.Value;
double d = 0; double.TryParse(n.Descendants("FinalPrice").FirstOrDefault()?.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out d);
int bids = 0; int.TryParse(n.Descendants("TotalBids").FirstOrDefault()?.Value, out bids);
var winner = n.Descendants("Winner").FirstOrDefault()?.Value ?? string.Empty;
var url = n.Descendants("OriginalUrl").FirstOrDefault()?.Value ?? string.Empty;
var rec = new ClosedAuctionRecord { ProductName = name, FinalPrice = d == 0 ? null : (double?)d, Winner = winner, BidsUsed = bids, AuctionUrl = url };
var key = (rec.ProductName ?? ExtractProductFromFilename(f) ?? "<unknown>").Trim();
if (!aggregated.ContainsKey(key)) aggregated[key] = new List<ClosedAuctionRecord>();
aggregated[key].Add(rec);
}
}
catch { }
}
else // CSV or text
{
try
{
var lines = File.ReadAllLines(f, Encoding.UTF8);
string product = ExtractProductFromFilename(f) ?? "<unknown>";
double? price = null; int? bids = null; string winner = string.Empty; string url = string.Empty;
foreach (var l in lines)
{
var line = l.Trim();
if (line.StartsWith("Name,") || line.StartsWith("ProductName,"))
{
var parts = line.Split(',', 2);
if (parts.Length == 2) product = parts[1].Trim('"');
}
else if (line.StartsWith("FinalPrice", StringComparison.OrdinalIgnoreCase) || line.StartsWith("Price,"))
{
var parts = line.Split(',', 2);
if (parts.Length == 2 && double.TryParse(parts[1].Trim('"').Replace('€', ' ').Trim(), NumberStyles.Any, CultureInfo.InvariantCulture, out var p)) price = p;
}
else if (line.StartsWith("TotalBids", StringComparison.OrdinalIgnoreCase) || line.StartsWith("BidsUsed", StringComparison.OrdinalIgnoreCase))
{
var parts = line.Split(',', 2);
if (parts.Length == 2 && int.TryParse(parts[1].Trim('"'), out var b)) bids = b;
}
else if (line.StartsWith("Winner", StringComparison.OrdinalIgnoreCase))
{
var parts = line.Split(',', 2);
if (parts.Length == 2) winner = parts[1].Trim('"');
}
else if (line.StartsWith("OriginalUrl", StringComparison.OrdinalIgnoreCase) || line.StartsWith("AuctionUrl", StringComparison.OrdinalIgnoreCase))
{
var parts = line.Split(',', 2);
if (parts.Length == 2) url = parts[1].Trim('"');
}
}
var rec = new ClosedAuctionRecord { ProductName = product, FinalPrice = price, BidsUsed = bids, Winner = winner, AuctionUrl = url };
var key = (rec.ProductName ?? ExtractProductFromFilename(f) ?? "<unknown>").Trim();
if (!aggregated.ContainsKey(key)) aggregated[key] = new List<ClosedAuctionRecord>();
aggregated[key].Add(rec);
}
catch { }
}
}
catch { }
}
});
var stats = new List<object>();
foreach (var kv in aggregated)
{
var list = kv.Value.Where(x => x.FinalPrice.HasValue || x.BidsUsed.HasValue).ToList();
if (list.Count == 0) continue;
var avgPrice = list.Where(x => x.FinalPrice.HasValue).Select(x => x.FinalPrice!.Value).DefaultIfEmpty(0).Average();
var avgBids = list.Where(x => x.BidsUsed.HasValue).Select(x => x.BidsUsed!.Value).DefaultIfEmpty(0).Average();
var winner = list.Where(x => !string.IsNullOrEmpty(x.Winner)).GroupBy(x => x.Winner).OrderByDescending(g => g.Count()).Select(g => g.Key).FirstOrDefault() ?? string.Empty;
var example = list.FirstOrDefault(x => !string.IsNullOrEmpty(x.AuctionUrl))?.AuctionUrl ?? string.Empty;
stats.Add(new
{
ProductName = kv.Key,
FinalPrice = avgPrice,
Winner = winner,
BidsUsed = (int)Math.Round(avgBids),
AuctionUrl = example,
Count = list.Count,
AverageFinalPrice = avgPrice,
AverageBidsUsed = avgBids
});
}
Dispatcher.Invoke(() =>
{
StatsDataGrid.ItemsSource = stats.OrderByDescending(s => (int)s.GetType().GetProperty("Count")!.GetValue(s)).ToList();
StatsStatusText.Text = $"Caricati {stats.Count} prodotti ({files.Length} file analizzati)";
ExportProgressBar.Visibility = Visibility.Collapsed;
ExportProgressText.Visibility = Visibility.Collapsed;
});
}
catch (Exception ex)
{
StatsStatusText.Text = "Errore caricamento statistiche";
Log($"[ERRORE] Carica statistiche: {ex.Message}", LogLevel.Error);
MessageBox.Show(this, "Errore durante caricamento statistiche: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
ExportProgressBar.Visibility = Visibility.Collapsed;
ExportProgressText.Visibility = Visibility.Collapsed;
}
*/
}
private static string? ExtractProductFromFilename(string path)
{
try
{
var name = Path.GetFileNameWithoutExtension(path);
var m = Regex.Match(name, @"auction_(.+)");
if (m.Success)
{
var v = m.Groups[1].Value;
if (Regex.IsMatch(v, "^\\d+$")) return null;
return v.Replace('_', ' ');
}
return null;
}
catch { return null; }
}
private async void ApplyInsightsButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(this, "Funzionalità statistiche in sviluppo", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
/* CODICE TEMPORANEAMENTE DISABILITATO
try
{
if (_selectedAuction == null)
{
MessageBox.Show(this, "Seleziona un'asta prima di applicare le raccomandazioni.", "Applica Raccomandazioni", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var optionsBuilder = new Microsoft.EntityFrameworkCore.DbContextOptionsBuilder<Data.StatisticsContext>();
var dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "stats.db");
optionsBuilder.UseSqlite($"Data Source={dbPath}");
using var ctx = new Data.StatisticsContext(optionsBuilder.Options);
var svc = new Services.StatsService(ctx);
var (recBids, recPrice) = await svc.GetRecommendationAsync(_selectedAuction.AuctionInfo.Name, _selectedAuction.AuctionInfo.OriginalUrl);
_selectedAuction.MaxClicks = Math.Max(_selectedAuction.MaxClicks, recBids);
_selectedAuction.MaxPrice = Math.Max(_selectedAuction.MaxPrice, recPrice);
Log($"[OK] Raccomandazioni: MaxClicks={recBids}, MaxPrice={recPrice:F2} applicate a {_selectedAuction.Name}", LogLevel.Success);
UpdateSelectedAuctionDetails(_selectedAuction);
}
catch (Exception ex)
{
Log($"[ERRORE] ApplyInsights: {ex.Message}", LogLevel.Error);
MessageBox.Show(this, "Errore applicazione raccomandazioni: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
*/
}
private void FreeBidsStart_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(this, "Funzionalità non ancora implementata", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
}
private void FreeBidsStop_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(this, "Funzionalità non ancora implementata", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
}