Varie migliorie

This commit is contained in:
2026-06-09 18:32:30 +02:00
parent 711cc11805
commit 0a50ba7df5
10 changed files with 1485 additions and 964 deletions
+136 -102
View File
@@ -1,4 +1,4 @@
@page @page
@model AutoBidder.Pages.Account.LoginModel @model AutoBidder.Pages.Account.LoginModel
@{ @{
Layout = null; Layout = null;
@@ -10,13 +10,28 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Login - AutoBidder</title> <title>Login - AutoBidder</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous" />
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet" />
<style> <style>
* { *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
margin: 0;
padding: 0; :root {
box-sizing: border-box; --md-primary: #6750A4;
--md-on-primary: #FFFFFF;
--md-primary-container: #EADDFF;
--md-on-primary-container: #21005D;
--md-surface: #FEF7FF;
--md-surface-container: #F3EDF7;
--md-surface-container-low: #F7F2FA;
--md-on-surface: #1C1B1F;
--md-on-surface-variant: #49454F;
--md-outline: #79747E;
--md-outline-variant: #CAC4D0;
--md-error: #B3261E;
--md-error-container: #F9DEDC;
--md-on-error-container: #410E0B;
} }
body { body {
@@ -24,143 +39,162 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); background-color: var(--md-surface);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Roboto', system-ui, -apple-system, sans-serif;
color: var(--md-on-surface);
padding: 24px;
} }
.login-card { .login-surface {
background: rgba(255, 255, 255, 0.05); background-color: var(--md-surface-container-low);
backdrop-filter: blur(10px); border-radius: 28px;
border: 1px solid rgba(255, 255, 255, 0.1); padding: 40px 32px;
border-radius: 20px;
padding: 40px;
width: 100%; width: 100%;
max-width: 380px; max-width: 400px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3); box-shadow: 0 2px 6px rgba(0,0,0,.08), 0 4px 16px rgba(103,80,164,.1);
} }
.login-header { .login-hero {
text-align: center; text-align: center;
margin-bottom: 35px; margin-bottom: 32px;
} }
.login-header h1 { .login-icon {
color: #fff; width: 64px;
height: 64px;
background-color: var(--md-primary-container);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
font-size: 32px;
color: var(--md-on-primary-container);
}
.login-hero h1 {
font-size: 28px; font-size: 28px;
font-weight: 600; font-weight: 400;
margin-bottom: 8px; color: var(--md-on-surface);
margin-bottom: 4px;
} }
.login-header p { .login-hero p {
color: rgba(255, 255, 255, 0.6);
font-size: 14px; font-size: 14px;
color: var(--md-on-surface-variant);
} }
.form-floating { .form-field {
margin-bottom: 20px; position: relative;
margin-bottom: 16px;
} }
.form-floating .form-control { .form-field label {
background: rgba(255, 255, 255, 0.08); display: block;
border: 1px solid rgba(255, 255, 255, 0.15); font-size: 12px;
border-radius: 12px; font-weight: 500;
color: #fff; color: var(--md-on-surface-variant);
height: 55px; margin-bottom: 6px;
padding: 16px;
} }
.form-floating .form-control:focus { .form-field input {
background: rgba(255, 255, 255, 0.12); width: 100%;
border-color: #4f46e5; background-color: var(--md-surface);
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.25); color: var(--md-on-surface);
color: #fff; border: 1px solid var(--md-outline);
border-radius: 4px;
padding: 14px 16px;
font-size: 16px;
font-family: inherit;
outline: none;
transition: border-color .15s, box-shadow .15s;
} }
.form-floating .form-control::placeholder { .form-field input:focus {
color: transparent; border: 2px solid var(--md-primary);
padding: 13px 15px;
box-shadow: none;
} }
.form-floating label { .form-field input::placeholder { color: transparent; }
color: rgba(255, 255, 255, 0.5);
padding: 16px; .form-check-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 24px;
} }
.form-floating .form-control:focus ~ label, .form-check-row input[type="checkbox"] {
.form-floating .form-control:not(:placeholder-shown) ~ label { width: 18px;
color: rgba(255, 255, 255, 0.7); height: 18px;
accent-color: var(--md-primary);
cursor: pointer;
} }
.form-check { .form-check-row label {
margin-bottom: 25px;
}
.form-check-input {
background-color: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
}
.form-check-input:checked {
background-color: #4f46e5;
border-color: #4f46e5;
}
.form-check-label {
color: rgba(255, 255, 255, 0.7);
font-size: 14px; font-size: 14px;
color: var(--md-on-surface-variant);
cursor: pointer;
user-select: none;
} }
.btn-login { .btn-login {
width: 100%; width: 100%;
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); background-color: var(--md-primary);
color: var(--md-on-primary);
border: none; border: none;
border-radius: 12px; border-radius: 9999px;
color: #fff; padding: 0 24px;
font-weight: 600; height: 40px;
font-size: 16px; font-size: 14px;
padding: 14px; font-weight: 500;
transition: all 0.3s ease; font-family: inherit;
letter-spacing: .1px;
cursor: pointer;
transition: filter .2s, box-shadow .2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
} }
.btn-login:hover { .btn-login:hover {
transform: translateY(-2px); filter: brightness(0.92);
box-shadow: 0 10px 30px rgba(79, 70, 229, 0.4); box-shadow: 0 2px 6px rgba(103,80,164,.35);
color: #fff;
}
.btn-login:active {
transform: translateY(0);
} }
.alert-error { .alert-error {
background: rgba(239, 68, 68, 0.15); background-color: var(--md-error-container);
border: 1px solid rgba(239, 68, 68, 0.3); color: var(--md-on-error-container);
border-radius: 12px; border-radius: 12px;
color: #fca5a5;
padding: 12px 16px; padding: 12px 16px;
margin-bottom: 20px; margin-bottom: 16px;
font-size: 14px; font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
} }
.login-footer { .login-footer {
text-align: center; text-align: center;
margin-top: 25px; margin-top: 24px;
padding-top: 20px; padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.1); border-top: 1px solid var(--md-outline-variant);
} }
.login-footer small { .login-footer small {
color: rgba(255, 255, 255, 0.4);
font-size: 12px; font-size: 12px;
} color: var(--md-on-surface-variant);
.login-footer i {
margin-right: 5px;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="login-card"> <div class="login-surface">
<div class="login-header"> <div class="login-hero">
<div class="login-icon">
<i class="bi bi-lightning-charge-fill"></i>
</div>
<h1>AutoBidder</h1> <h1>AutoBidder</h1>
<p>Sistema Gestione Aste Bidoo</p> <p>Sistema Gestione Aste Bidoo</p>
</div> </div>
@@ -175,25 +209,25 @@
<form method="post"> <form method="post">
@Html.AntiForgeryToken() @Html.AntiForgeryToken()
<div class="form-floating"> <div class="form-field">
<input type="text" class="form-control" id="username" name="Username"
placeholder="Username" value="@Model.Username" required autocomplete="username" />
<label for="username"><i class="bi bi-person"></i> Username</label> <label for="username"><i class="bi bi-person"></i> Username</label>
<input type="text" id="username" name="Username"
placeholder="Username" value="@Model.Username" required autocomplete="username" />
</div> </div>
<div class="form-floating"> <div class="form-field">
<input type="password" class="form-control" id="password" name="Password"
placeholder="Password" required autocomplete="current-password" />
<label for="password"><i class="bi bi-lock"></i> Password</label> <label for="password"><i class="bi bi-lock"></i> Password</label>
<input type="password" id="password" name="Password"
placeholder="Password" required autocomplete="current-password" />
</div> </div>
<div class="form-check"> <div class="form-check-row">
<input class="form-check-input" type="checkbox" id="rememberMe" name="RememberMe" value="true" /> <input type="checkbox" id="rememberMe" name="RememberMe" value="true" />
<label class="form-check-label" for="rememberMe">Ricordami</label> <label for="rememberMe">Ricordami</label>
</div> </div>
<button type="submit" class="btn btn-login"> <button type="submit" class="btn-login">
<i class="bi bi-box-arrow-in-right"></i> Accedi <i class="bi bi-box-arrow-in-right"></i> Accedi
</button> </button>
</form> </form>
@@ -203,4 +237,4 @@
</div> </div>
</div> </div>
</body> </body>
</html> </html>
+1 -1
View File
@@ -10,7 +10,7 @@
<PageTitle>Esplora Aste - AutoBidder</PageTitle> <PageTitle>Esplora Aste - AutoBidder</PageTitle>
<div class="browser-container animate-fade-in p-4"> <div class="browser-container md-page-padded animate-fade-in">
<!-- Header --> <!-- Header -->
<div class="d-flex align-items-center justify-content-between mb-4 flex-wrap gap-3"> <div class="d-flex align-items-center justify-content-between mb-4 flex-wrap gap-3">
<div class="d-flex align-items-center animate-fade-in-down"> <div class="d-flex align-items-center animate-fade-in-down">
+71 -73
View File
@@ -1,4 +1,4 @@
@page "/" @page "/"
@attribute [Microsoft.AspNetCore.Authorization.Authorize] @attribute [Microsoft.AspNetCore.Authorization.Authorize]
@inject AuctionMonitor AuctionMonitor @inject AuctionMonitor AuctionMonitor
@inject AuctionStateService AuctionStateService @inject AuctionStateService AuctionStateService
@@ -8,87 +8,86 @@
<PageTitle>Monitor Aste - AutoBidder</PageTitle> <PageTitle>Monitor Aste - AutoBidder</PageTitle>
<div class="auction-monitor-container"> <div class="auction-monitor-container">
<!-- Toolbar Compatta --> <!-- MD3 Toolbar -->
<div class="toolbar-compact"> <div class="md-toolbar">
<!-- Pulsanti Azioni Massiva (senza conteggi) --> <!-- Bulk action buttons -->
<div class="btn-group-actions"> <div class="d-flex gap-2 align-items-center">
<button class="action-btn success" @onclick="StartAll" title="Avvia tutte le aste"> <button class="md-icon-btn success" @onclick="StartAll" title="Avvia tutte le aste" style="background-color:var(--md-success-container);color:var(--md-on-success-container);">
<i class="bi bi-play-fill"></i> <i class="bi bi-play-fill"></i>
</button> </button>
<button class="action-btn warning" @onclick="PauseAll" title="Metti in pausa tutte le aste"> <button class="md-icon-btn warning" @onclick="PauseAll" title="Metti in pausa tutte le aste" style="background-color:var(--md-warning-container);color:var(--md-on-warning-container);">
<i class="bi bi-pause-fill"></i> <i class="bi bi-pause-fill"></i>
</button> </button>
<button class="action-btn secondary" @onclick="StopAll" title="Ferma tutte le aste"> <button class="md-icon-btn" @onclick="StopAll" title="Ferma tutte le aste" style="background-color:var(--md-surface-container-highest);color:var(--md-on-surface-variant);">
<i class="bi bi-stop-fill"></i> <i class="bi bi-stop-fill"></i>
</button> </button>
</div> </div>
<!-- Indicatori Stato Aste (tutti gli stati) --> <!-- Status pills -->
<div class="status-indicators"> <div class="d-flex gap-2 flex-wrap align-items-center">
<div class="status-pill total" title="Totale aste"> <span class="md-status-pill total" title="Totale aste">
<i class="bi bi-collection"></i> <i class="bi bi-collection"></i>
<span>@(auctions?.Count ?? 0)</span> <span>@(auctions?.Count ?? 0)</span>
</div> </span>
<div class="status-pill active" title="Aste attive"> <span class="md-status-pill active" title="Aste attive">
<i class="bi bi-play-circle-fill"></i> <i class="bi bi-play-circle-fill"></i>
<span>@GetActiveAuctionsCount()</span> <span>@GetActiveAuctionsCount()</span>
</div> </span>
<div class="status-pill paused" title="Aste in pausa"> <span class="md-status-pill paused" title="Aste in pausa">
<i class="bi bi-pause-circle-fill"></i> <i class="bi bi-pause-circle-fill"></i>
<span>@GetPausedAuctionsCount()</span> <span>@GetPausedAuctionsCount()</span>
</div> </span>
<div class="status-pill stopped" title="Aste fermate"> <span class="md-status-pill stopped" title="Aste fermate">
<i class="bi bi-stop-circle-fill"></i> <i class="bi bi-stop-circle-fill"></i>
<span>@GetStoppedAuctionsCount()</span> <span>@GetStoppedAuctionsCount()</span>
</div> </span>
<div class="status-pill won" title="Aste vinte"> <span class="md-status-pill won" title="Aste vinte">
<i class="bi bi-trophy-fill"></i> <i class="bi bi-trophy-fill"></i>
<span>@GetWonAuctionsCount()</span> <span>@GetWonAuctionsCount()</span>
</div> </span>
<div class="status-pill lost" title="Aste perse"> <span class="md-status-pill lost" title="Aste perse">
<i class="bi bi-x-circle-fill"></i> <i class="bi bi-x-circle-fill"></i>
<span>@GetLostAuctionsCount()</span> <span>@GetLostAuctionsCount()</span>
</div> </span>
</div> </div>
<!-- Pulsanti Gestione --> <!-- Management buttons -->
<div class="btn-group-manage"> <div class="d-flex gap-1 align-items-center ms-auto flex-wrap">
<button class="manage-btn primary" @onclick="ShowAddAuctionDialog" title="Aggiungi nuova asta"> <button class="md-icon-btn primary" @onclick="ShowAddAuctionDialog" title="Aggiungi nuova asta">
<i class="bi bi-plus-lg"></i> <i class="bi bi-plus-lg"></i>
</button> </button>
<button class="manage-btn danger" @onclick="RemoveSelectedAuction" disabled="@(selectedAuction == null)" title="Rimuovi selezionata"> <button class="md-icon-btn danger" @onclick="RemoveSelectedAuction" disabled="@(selectedAuction == null)" title="Rimuovi selezionata">
<i class="bi bi-trash"></i> <i class="bi bi-trash"></i>
</button> </button>
<div class="manage-separator"></div> <div style="width:1px;height:24px;background:var(--md-outline-variant);margin:0 4px;"></div>
<button class="manage-btn outline-success" @onclick="RemoveActiveAuctions" disabled="@(GetActiveAuctionsCount() == 0)" title="Rimuovi attive"> <button class="md-icon-btn success" @onclick="RemoveActiveAuctions" disabled="@(GetActiveAuctionsCount() == 0)" title="Rimuovi attive">
<i class="bi bi-play-circle"></i> <i class="bi bi-play-circle"></i>
</button> </button>
<button class="manage-btn outline-warning" @onclick="RemovePausedAuctions" disabled="@(GetPausedAuctionsCount() == 0)" title="Rimuovi in pausa"> <button class="md-icon-btn warning" @onclick="RemovePausedAuctions" disabled="@(GetPausedAuctionsCount() == 0)" title="Rimuovi in pausa">
<i class="bi bi-pause-circle"></i> <i class="bi bi-pause-circle"></i>
</button> </button>
<button class="manage-btn outline-secondary" @onclick="RemoveStoppedAuctions" disabled="@(GetStoppedAuctionsCount() == 0)" title="Rimuovi fermate"> <button class="md-icon-btn" @onclick="RemoveStoppedAuctions" disabled="@(GetStoppedAuctionsCount() == 0)" title="Rimuovi fermate">
<i class="bi bi-stop-circle"></i> <i class="bi bi-stop-circle"></i>
</button> </button>
<button class="manage-btn outline-gold" @onclick="RemoveWonAuctions" disabled="@(GetWonAuctionsCount() == 0)" title="Rimuovi vinte"> <button class="md-icon-btn" @onclick="RemoveWonAuctions" disabled="@(GetWonAuctionsCount() == 0)" title="Rimuovi vinte" style="color:var(--md-primary);">
<i class="bi bi-trophy"></i> <i class="bi bi-trophy"></i>
</button> </button>
<button class="manage-btn outline-danger" @onclick="RemoveLostAuctions" disabled="@(GetLostAuctionsCount() == 0)" title="Rimuovi perse"> <button class="md-icon-btn danger" @onclick="RemoveLostAuctions" disabled="@(GetLostAuctionsCount() == 0)" title="Rimuovi perse">
<i class="bi bi-x-circle"></i> <i class="bi bi-x-circle"></i>
</button> </button>
<div class="manage-separator"></div> <div style="width:1px;height:24px;background:var(--md-outline-variant);margin:0 4px;"></div>
<button class="manage-btn danger-fill" @onclick="RemoveAllAuctions" disabled="@((auctions?.Count ?? 0) == 0)" title="Rimuovi TUTTE"> <button class="md-icon-btn danger" @onclick="RemoveAllAuctions" disabled="@((auctions?.Count ?? 0) == 0)" title="Rimuovi TUTTE">
<i class="bi bi-trash-fill"></i> <i class="bi bi-trash-fill"></i>
</button> </button>
</div> </div>
</div> </div>
<!-- Area Principale con Layout a Griglia --> <!-- Main content grid -->
<div class="main-content-area"> <div class="main-content-area">
<!-- Riga Superiore: Aste + Log -->
<div class="top-row" id="topRow"> <div class="top-row" id="topRow">
<!-- Pannello Aste --> <!-- Auctions panel -->
<div class="panel panel-auctions" id="panelAuctions"> <div class="md-panel" id="panelAuctions">
<div class="panel-header"> <div class="md-panel-header">
<span><i class="bi bi-list-check"></i> Aste Monitorate</span> <span><i class="bi bi-list-check"></i> Aste Monitorate</span>
</div> </div>
@if ((auctions?.Count ?? 0) == 0) @if ((auctions?.Count ?? 0) == 0)
@@ -105,7 +104,7 @@
<tr> <tr>
<th class="col-stato sortable-header" @onclick='() => SortAuctionsBy("stato")'>Stato @GetSortIndicator("stato")</th> <th class="col-stato sortable-header" @onclick='() => SortAuctionsBy("stato")'>Stato @GetSortIndicator("stato")</th>
<th class="col-nome sortable-header" @onclick='() => SortAuctionsBy("nome")'>Nome @GetSortIndicator("nome")</th> <th class="col-nome sortable-header" @onclick='() => SortAuctionsBy("nome")'>Nome @GetSortIndicator("nome")</th>
<th class="col-prezzo sortable-header" @onclick='() => SortAuctionsBy("prezzo")'>€ @GetSortIndicator("prezzo")</th> <th class="col-prezzo sortable-header" @onclick='() => SortAuctionsBy("prezzo")'>€ @GetSortIndicator("prezzo")</th>
<th class="col-timer sortable-header" @onclick='() => SortAuctionsBy("timer")'>Timer @GetSortIndicator("timer")</th> <th class="col-timer sortable-header" @onclick='() => SortAuctionsBy("timer")'>Timer @GetSortIndicator("timer")</th>
<th class="col-ultimo">Ultimo</th> <th class="col-ultimo">Ultimo</th>
<th class="col-click sortable-header" @onclick='() => SortAuctionsBy("puntate")'>Punt. @GetSortIndicator("puntate")</th> <th class="col-click sortable-header" @onclick='() => SortAuctionsBy("puntate")'>Punt. @GetSortIndicator("puntate")</th>
@@ -171,18 +170,18 @@
<!-- Splitter Verticale --> <!-- Splitter Verticale -->
<div class="gutter gutter-vertical" id="gutterVertical"></div> <div class="gutter gutter-vertical" id="gutterVertical"></div>
<!-- Pannello Log --> <!-- Log panel -->
<div class="panel panel-log" id="panelLog"> <div class="md-panel" id="panelLog">
<div class="panel-header"> <div class="md-panel-header">
<span><i class="bi bi-terminal"></i> Log</span> <span><i class="bi bi-terminal"></i> Log</span>
<button class="btn btn-xs btn-secondary" @onclick="ClearGlobalLog"> <button class="md-icon-btn" @onclick="ClearGlobalLog" title="Pulisci log">
<i class="bi bi-trash"></i> <i class="bi bi-trash"></i>
</button> </button>
</div> </div>
<div class="panel-content log-box" id="globalLogContainer" @ref="globalLogRef"> <div class="md-panel-content md-log-box" id="globalLogContainer" @ref="globalLogRef">
@if (globalLog.Count == 0) @if (globalLog.Count == 0)
{ {
<div class="text-muted"><i class="bi bi-inbox"></i> Nessun log ancora...</div> <div style="color:var(--md-on-surface-variant);"><i class="bi bi-inbox"></i> Nessun log ancora...</div>
} }
else else
{ {
@@ -195,18 +194,17 @@
</div> </div>
</div> </div>
<!-- Splitter Orizzontale -->
<div class="gutter gutter-horizontal" id="gutterHorizontal"></div> <div class="gutter gutter-horizontal" id="gutterHorizontal"></div>
<!-- Riga Inferiore: Dettagli Asta --> <!-- Bottom row: Auction details -->
<div class="bottom-row" id="bottomRow"> <div class="bottom-row" id="bottomRow">
<div class="panel panel-details" id="panelDetails"> <div class="md-panel" id="panelDetails">
@if (selectedAuction != null) @if (selectedAuction != null)
{ {
<div class="auction-details-content"> <div class="auction-details-content">
<div class="details-header"> <div class="md-panel-header">
<i class="bi bi-info-circle-fill"></i> @selectedAuction.Name <span><i class="bi bi-info-circle-fill" style="color:var(--md-primary);"></i> @selectedAuction.Name</span>
<small class="text-muted">(ID: @selectedAuction.AuctionId)</small> <small style="color:var(--md-on-surface-variant);">ID: @selectedAuction.AuctionId</small>
</div> </div>
<ul class="nav nav-tabs" role="tablist"> <ul class="nav nav-tabs" role="tablist">
@@ -260,11 +258,11 @@
<input type="number" class="form-control form-control-sm input-narrow" @bind="selectedAuction.BidBeforeDeadlineMs" @bind:after="SaveAuctions" /> <input type="number" class="form-control form-control-sm input-narrow" @bind="selectedAuction.BidBeforeDeadlineMs" @bind:after="SaveAuctions" />
</div> </div>
<div class="setting-item"> <div class="setting-item">
<label><i class="bi bi-currency-euro"></i> Min €</label> <label><i class="bi bi-currency-euro"></i> Min €</label>
<input type="number" step="0.01" class="form-control form-control-sm input-narrow" @bind="selectedAuction.MinPrice" @bind:after="SaveAuctions" /> <input type="number" step="0.01" class="form-control form-control-sm input-narrow" @bind="selectedAuction.MinPrice" @bind:after="SaveAuctions" />
</div> </div>
<div class="setting-item"> <div class="setting-item">
<label><i class="bi bi-currency-euro"></i> Max €</label> <label><i class="bi bi-currency-euro"></i> Max €</label>
<input type="number" step="0.01" class="form-control form-control-sm input-narrow" @bind="selectedAuction.MaxPrice" @bind:after="SaveAuctions" /> <input type="number" step="0.01" class="form-control form-control-sm input-narrow" @bind="selectedAuction.MaxPrice" @bind:after="SaveAuctions" />
</div> </div>
<div class="setting-item"> <div class="setting-item">
@@ -317,7 +315,7 @@
<i class="bi bi-truck"></i> <i class="bi bi-truck"></i>
<div> <div>
<small>Spedizione</small> <small>Spedizione</small>
<strong>€@(selectedAuction.ShippingCost?.ToString("F2") ?? "0.00")</strong> <strong>€@(selectedAuction.ShippingCost?.ToString("F2") ?? "0.00")</strong>
</div> </div>
</div> </div>
</div> </div>
@@ -325,7 +323,7 @@
<div class="calc-item"> <div class="calc-item">
<i class="bi bi-currency-euro"></i> <i class="bi bi-currency-euro"></i>
<span class="label">Prezzo attuale</span> <span class="label">Prezzo attuale</span>
<span class="value">€@selectedAuction.CalculatedValue.CurrentPrice.ToString("F2")</span> <span class="value">€@selectedAuction.CalculatedValue.CurrentPrice.ToString("F2")</span>
</div> </div>
<div class="calc-item"> <div class="calc-item">
<i class="bi bi-hand-index"></i> <i class="bi bi-hand-index"></i>
@@ -340,13 +338,13 @@
<div class="calc-item"> <div class="calc-item">
<i class="bi bi-cash-coin"></i> <i class="bi bi-cash-coin"></i>
<span class="label">Costo puntate</span> <span class="label">Costo puntate</span>
<span class="value">€@selectedAuction.CalculatedValue.MyBidsCost.ToString("F2")</span> <span class="value">€@selectedAuction.CalculatedValue.MyBidsCost.ToString("F2")</span>
</div> </div>
</div> </div>
<div class="totals-compact"> <div class="totals-compact">
<div class="total-item warning"> <div class="total-item warning">
<span>Costo Totale se vinci</span> <span>Costo Totale se vinci</span>
<strong>€@selectedAuction.CalculatedValue.TotalCostIfWin.ToString("F2")</strong> <strong>€@selectedAuction.CalculatedValue.TotalCostIfWin.ToString("F2")</strong>
</div> </div>
<div class="total-item @(selectedAuction.CalculatedValue.Savings > 0 ? "success" : "danger")"> <div class="total-item @(selectedAuction.CalculatedValue.Savings > 0 ? "success" : "danger")">
<span> <span>
@@ -400,7 +398,7 @@
{ {
<tr class="@(bid.IsMyBid ? "my-bid-row" : "")"> <tr class="@(bid.IsMyBid ? "my-bid-row" : "")">
<td>@if (bid.IsMyBid){<strong class="text-success">@bid.Username</strong><span class="badge bg-success ms-1">TU</span>}else{@bid.Username}</td> <td>@if (bid.IsMyBid){<strong class="text-success">@bid.Username</strong><span class="badge bg-success ms-1">TU</span>}else{@bid.Username}</td>
<td class="fw-bold">€@bid.PriceFormatted</td> <td class="fw-bold">€@bid.PriceFormatted</td>
<td class="text-muted small">@bid.TimeFormatted</td> <td class="text-muted small">@bid.TimeFormatted</td>
<td><span class="badge bg-secondary">@bid.BidType</span></td> <td><span class="badge bg-secondary">@bid.BidType</span></td>
</tr> </tr>
@@ -499,8 +497,8 @@
} }
else else
{ {
<div class="details-placeholder"> <div class="details-placeholder" style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:32px;color:var(--md-on-surface-variant);gap:8px;">
<i class="bi bi-arrow-up"></i> <i class="bi bi-arrow-up" style="font-size:2rem;color:var(--md-primary);"></i>
<p>Seleziona un'asta per visualizzare i dettagli</p> <p>Seleziona un'asta per visualizzare i dettagli</p>
</div> </div>
} }
@@ -512,7 +510,7 @@
<!-- Modal Aggiungi Asta --> <!-- Modal Aggiungi Asta -->
@if (showAddDialog) @if (showAddDialog)
{ {
<div class="modal show d-block" tabindex="-1" style="background: rgba(0,0,0,0.5);"> <div class="modal show d-block" tabindex="-1" style="background: rgba(0,0,0,0.3);">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
+77 -43
View File
@@ -1,4 +1,4 @@
@page "/settings" @page "/settings"
@attribute [Microsoft.AspNetCore.Authorization.Authorize] @attribute [Microsoft.AspNetCore.Authorization.Authorize]
@inject SessionService SessionService @inject SessionService SessionService
@inject AuctionMonitor AuctionMonitor @inject AuctionMonitor AuctionMonitor
@@ -7,14 +7,14 @@
<PageTitle>Impostazioni - AutoBidder</PageTitle> <PageTitle>Impostazioni - AutoBidder</PageTitle>
<div class="settings-container px-3 px-md-4 py-3"> <div class="settings-container md-page-padded">
<div class="d-flex align-items-center gap-3 mb-3"> <div class="md-toolbar mb-3" style="border-radius:var(--md-shape-medium);border:none;background:transparent;padding-left:0;">
<i class="bi bi-gear-fill text-primary" style="font-size: 2rem;"></i> <i class="bi bi-gear-fill" style="font-size:2rem;color:var(--md-primary);"></i>
<div> <div>
<h2 class="mb-0 fw-bold">Impostazioni</h2> <h2 class="mb-0 md-headline-small">Impostazioni</h2>
<small class="text-muted">Configura sessione, comportamento aste e limiti.</small> <small style="color:var(--md-on-surface-variant);">Configura sessione, comportamento aste e limiti.</small>
</div>
</div> </div>
</div>
<div class="accordion" id="settingsAccordion"> <div class="accordion" id="settingsAccordion">
<!-- SESSIONE BIDOO --> <!-- SESSIONE BIDOO -->
@@ -162,7 +162,7 @@
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<label class="form-label fw-bold"><i class="bi bi-hourglass-split"></i> Intervallo Ticker (ms)</label> <label class="form-label fw-bold"><i class="bi bi-hourglass-split"></i> Intervallo Ticker (ms)</label>
<input type="number" class="form-control" @bind="settings.TickerIntervalMs" min="10" max="500" /> <input type="number" class="form-control" @bind="settings.TickerIntervalMs" min="10" max="500" />
<div class="form-text">Più basso = più preciso ma più CPU. Consigliato: 50-100ms</div> <div class="form-text">Più basso = più preciso ma più CPU. Consigliato: 50-100ms</div>
</div> </div>
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<label class="form-label fw-bold"><i class="bi bi-funnel"></i> Soglia controllo strategie (ms)</label> <label class="form-label fw-bold"><i class="bi bi-funnel"></i> Soglia controllo strategie (ms)</label>
@@ -197,7 +197,7 @@
<div class="form-text">0 = illimitati</div> <div class="form-text">0 = illimitati</div>
</div> </div>
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<label class="form-label fw-bold"><i class="bi bi-currency-euro"></i> Prezzo minimo (€)</label> <label class="form-label fw-bold"><i class="bi bi-currency-euro"></i> Prezzo minimo (€)</label>
<div class="input-group"> <div class="input-group">
<input type="number" step="0.01" class="form-control" @bind="settings.DefaultMinPrice" /> <input type="number" step="0.01" class="form-control" @bind="settings.DefaultMinPrice" />
<button class="btn btn-outline-primary" @onclick="() => ApplySingleSettingToAll(nameof(settings.DefaultMinPrice))" <button class="btn btn-outline-primary" @onclick="() => ApplySingleSettingToAll(nameof(settings.DefaultMinPrice))"
@@ -209,7 +209,7 @@
<div class="form-text">0 = punta a qualsiasi prezzo. Il prezzo deve essere ? a questo valore per puntare.</div> <div class="form-text">0 = punta a qualsiasi prezzo. Il prezzo deve essere ? a questo valore per puntare.</div>
</div> </div>
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<label class="form-label fw-bold"><i class="bi bi-currency-euro"></i> Prezzo massimo (€)</label> <label class="form-label fw-bold"><i class="bi bi-currency-euro"></i> Prezzo massimo (€)</label>
<div class="input-group"> <div class="input-group">
<input type="number" step="0.01" class="form-control" @bind="settings.DefaultMaxPrice" /> <input type="number" step="0.01" class="form-control" @bind="settings.DefaultMaxPrice" />
<button class="btn btn-outline-primary" @onclick="() => ApplySingleSettingToAll(nameof(settings.DefaultMaxPrice))" <button class="btn btn-outline-primary" @onclick="() => ApplySingleSettingToAll(nameof(settings.DefaultMaxPrice))"
@@ -221,19 +221,19 @@
<div class="form-text">0 = nessun limite. Se il prezzo supera questo valore, SMETTE di puntare.</div> <div class="form-text">0 = nessun limite. Se il prezzo supera questo valore, SMETTE di puntare.</div>
</div> </div>
<div class="col-12"> <div class="col-12">
<label class="form-label fw-bold"><i class="bi bi-database-gear"></i> Priorità limiti nuove aste</label> <label class="form-label fw-bold"><i class="bi bi-database-gear"></i> Priorità limiti nuove aste</label>
<select class="form-select" @bind="settings.NewAuctionLimitsPriority"> <select class="form-select" @bind="settings.NewAuctionLimitsPriority">
<option value="ProductStats">Usa limiti salvati nelle statistiche prodotto</option> <option value="ProductStats">Usa limiti salvati nelle statistiche prodotto</option>
<option value="GlobalDefaults">Usa sempre limiti globali</option> <option value="GlobalDefaults">Usa sempre limiti globali</option>
</select> </select>
<div class="form-text"> <div class="form-text">
Se "Statistiche prodotto", quando aggiungi un'asta di un prodotto già salvato, verranno usati i limiti personalizzati delle statistiche invece di quelli globali. Se "Statistiche prodotto", quando aggiungi un'asta di un prodotto già salvato, verranno usati i limiti personalizzati delle statistiche invece di quelli globali.
</div> </div>
</div> </div>
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<label class="form-label fw-bold"><i class="bi bi-shield-check"></i> Puntate minime da mantenere</label> <label class="form-label fw-bold"><i class="bi bi-shield-check"></i> Puntate minime da mantenere</label>
<input type="number" class="form-control" @bind="settings.MinimumRemainingBids" /> <input type="number" class="form-control" @bind="settings.MinimumRemainingBids" />
<div class="form-text">Questa è un'impostazione globale</div> <div class="form-text">Questa è un'impostazione globale</div>
</div> </div>
</div> </div>
@@ -263,7 +263,7 @@
<div class="alert alert-info border-0 shadow-sm mb-4"> <div class="alert alert-info border-0 shadow-sm mb-4">
<i class="bi bi-info-circle me-2"></i> <i class="bi bi-info-circle me-2"></i>
<strong>Nota:</strong> Le strategie decidono <strong>SE</strong> puntare, non <strong>QUANDO</strong>. <strong>Nota:</strong> Le strategie decidono <strong>SE</strong> puntare, non <strong>QUANDO</strong>.
Il timing è controllato solo dall'impostazione "Anticipo puntata" nelle Impostazioni Predefinite. Il timing è controllato solo dall'impostazione "Anticipo puntata" nelle Impostazioni Predefinite.
</div> </div>
<h6 class="fw-bold mb-3"><i class="bi bi-thermometer-half"></i> Rilevamento Competizione</h6> <h6 class="fw-bold mb-3"><i class="bi bi-thermometer-half"></i> Rilevamento Competizione</h6>
@@ -346,12 +346,12 @@
<input type="checkbox" class="form-check-input" id="probabilistic" @bind="settings.ProbabilisticBiddingEnabled" /> <input type="checkbox" class="form-check-input" id="probabilistic" @bind="settings.ProbabilisticBiddingEnabled" />
<label class="form-check-label" for="probabilistic"> <label class="form-check-label" for="probabilistic">
<strong>Policy probabilistica</strong> <strong>Policy probabilistica</strong>
<div class="form-text">Decide se puntare con probabilità basata su competizione</div> <div class="form-text">Decide se puntare con probabilità basata su competizione</div>
</label> </label>
</div> </div>
</div> </div>
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<label class="form-label">Probabilità base (0-1)</label> <label class="form-label">Probabilità base (0-1)</label>
<input type="number" step="0.1" class="form-control" @bind="settings.BaseBidProbability" /> <input type="number" step="0.1" class="form-control" @bind="settings.BaseBidProbability" />
</div> </div>
</div> </div>
@@ -393,7 +393,7 @@
<select class="form-select" @bind="settings.AggressiveBidderAction"> <select class="form-select" @bind="settings.AggressiveBidderAction">
<option value="Compete">? Continua normalmente (consigliato)</option> <option value="Compete">? Continua normalmente (consigliato)</option>
<option value="Avoid">?? Evita asta (pausa automatica)</option> <option value="Avoid">?? Evita asta (pausa automatica)</option>
<option value="Outbid">?? Punta più aggressivamente</option> <option value="Outbid">?? Punta più aggressivamente</option>
</select> </select>
</div> </div>
</div> </div>
@@ -419,11 +419,11 @@
<input type="number" class="form-control" @bind="settings.MaxBidsPerAuction" /> <input type="number" class="form-control" @bind="settings.MaxBidsPerAuction" />
</div> </div>
<div class="col-12 col-md-4"> <div class="col-12 col-md-4">
<label class="form-label">Budget giornaliero (€)</label> <label class="form-label">Budget giornaliero (€)</label>
<input type="number" step="0.01" class="form-control" @bind="settings.DailyBudgetEuro" /> <input type="number" step="0.01" class="form-control" @bind="settings.DailyBudgetEuro" />
</div> </div>
<div class="col-12 col-md-4"> <div class="col-12 col-md-4">
<label class="form-label">Costo medio puntata (€)</label> <label class="form-label">Costo medio puntata (€)</label>
<input type="number" step="0.01" class="form-control" @bind="settings.AverageBidCostEuro" /> <input type="number" step="0.01" class="form-control" @bind="settings.AverageBidCostEuro" />
</div> </div>
</div> </div>
@@ -680,6 +680,40 @@
</div> </div>
</div> </div>
<!-- ASPETTO E TEMA -->
<div class="accordion-item">
<h2 class="accordion-header" id="heading-theme">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-theme" aria-expanded="false" aria-controls="collapse-theme">
<i class="bi bi-moon-stars-fill me-2"></i> Aspetto e Tema
</button>
</h2>
<div id="collapse-theme" class="accordion-collapse collapse" aria-labelledby="heading-theme" data-bs-parent="#settingsAccordion">
<div class="accordion-body">
<p class="text-muted small mb-3">Personalizza l'aspetto visivo dell'applicazione. Le modifiche vengono applicate immediatamente.</p>
<div class="md-theme-toggle mb-3">
<div class="md-theme-toggle-label">
<strong><i class="bi bi-moon-fill me-2"></i>Modalità Scura</strong>
<small>Sostituisce il tema chiaro con colori ottimizzati per ambienti con poca luce.</small>
</div>
<div class="form-check form-switch ms-3 mb-0" style="padding-left:3.5rem;">
<input class="form-check-input" type="checkbox" role="switch" id="darkModeToggle"
style="width:3rem;height:1.5rem;cursor:pointer;"
checked="@settings.DarkMode"
@onchange="ToggleDarkMode" />
</div>
</div>
@if (!string.IsNullOrEmpty(themeMessage))
{
<div class="alert alert-success border-0 mt-2 py-2">
<i class="bi bi-check-circle me-2"></i>@themeMessage
</div>
}
</div>
</div>
</div>
<!-- INFORMAZIONI APPLICAZIONE --> <!-- INFORMAZIONI APPLICAZIONE -->
<div class="accordion-item"> <div class="accordion-item">
<h2 class="accordion-header" id="heading-info"> <h2 class="accordion-header" id="heading-info">
@@ -759,29 +793,14 @@
max-width: 1100px; max-width: 1100px;
margin: 0 auto; margin: 0 auto;
} }
.accordion-button {
word-break: break-word;
}
.font-monospace { .font-monospace {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-family: 'Roboto Mono', ui-monospace, monospace;
font-size: 0.925rem; font-size: 0.875rem;
} }
.fade-in { animation: fadeIn 0.25s ease-in; }
.fade-in {
animation: fadeIn 0.3s ease-in;
}
@@keyframes fadeIn { @@keyframes fadeIn {
from { from { opacity: 0; transform: translateY(-8px); }
opacity: 0; to { opacity: 1; transform: translateY(0); }
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
</style> </style>
@@ -804,6 +823,9 @@ private bool applyToAllSuccess = false;
private HashSet<string> applyingSettings = new(); private HashSet<string> applyingSettings = new();
private string? singleSettingMessage = null; private string? singleSettingMessage = null;
// Tema
private string? themeMessage = null;
private AutoBidder.Utilities.AppSettings settings = new(); private AutoBidder.Utilities.AppSettings settings = new();
private System.Threading.Timer? updateTimer; private System.Threading.Timer? updateTimer;
@@ -907,11 +929,11 @@ private System.Threading.Timer? updateTimer;
break; break;
case nameof(settings.DefaultMinPrice): case nameof(settings.DefaultMinPrice):
auction.MinPrice = settings.DefaultMinPrice; auction.MinPrice = settings.DefaultMinPrice;
settingLabel = $"Prezzo minimo (€{settings.DefaultMinPrice:F2})"; settingLabel = $"Prezzo minimo (€{settings.DefaultMinPrice:F2})";
break; break;
case nameof(settings.DefaultMaxPrice): case nameof(settings.DefaultMaxPrice):
auction.MaxPrice = settings.DefaultMaxPrice; auction.MaxPrice = settings.DefaultMaxPrice;
settingLabel = $"Prezzo massimo (€{settings.DefaultMaxPrice:F2})"; settingLabel = $"Prezzo massimo (€{settings.DefaultMaxPrice:F2})";
break; break;
} }
count++; count++;
@@ -1073,7 +1095,19 @@ private System.Threading.Timer? updateTimer;
private void SaveSettings() private void SaveSettings()
{ {
AutoBidder.Utilities.SettingsManager.Save(settings); AutoBidder.Utilities.SettingsManager.Save(settings);
_ = JSRuntime.InvokeVoidAsync("alert", "? Impostazioni salvate con successo!"); _ = JSRuntime.InvokeVoidAsync("alert", " Impostazioni salvate con successo!");
}
private async Task ToggleDarkMode(ChangeEventArgs e)
{
settings.DarkMode = (bool)(e.Value ?? false);
AutoBidder.Utilities.SettingsManager.Save(settings);
await JSRuntime.InvokeVoidAsync("mdTheme.apply", settings.DarkMode);
themeMessage = settings.DarkMode ? "Modalità scura attivata." : "Modalità chiara attivata.";
StateHasChanged();
await Task.Delay(2500);
themeMessage = null;
StateHasChanged();
} }
private void SetRememberState(bool remember) private void SetRememberState(bool remember)
+67 -404
View File
@@ -20,7 +20,7 @@
</div> </div>
@if (StatsService.IsAvailable) @if (StatsService.IsAvailable)
{ {
<button class="action-btn primary" @onclick="RefreshStats" disabled="@isLoading" title="Aggiorna statistiche"> <button class="md-icon-btn primary" @onclick="RefreshStats" disabled="@isLoading" title="Aggiorna statistiche">
@if (isLoading) @if (isLoading)
{ {
<span class="spinner-border spinner-border-sm"></span> <span class="spinner-border spinner-border-sm"></span>
@@ -972,432 +972,95 @@ private bool isBulkOperating = false;
} }
<style> <style>
/* ??? PAGE LAYOUT ??? */ /* Statistics page — MD3 overrides */
.stats-page {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.5rem;
height: 100%;
overflow: hidden;
}
/* ??? TOOLBAR ??? */
.stats-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.4rem 0.6rem;
background: linear-gradient(135deg, rgba(20, 20, 30, 0.8) 0%, rgba(30, 30, 45, 0.8) 100%);
border-radius: var(--radius-md);
border: 1px solid rgba(255, 255, 255, 0.05);
flex-shrink: 0;
}
.stats-toolbar-title {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.85rem;
font-weight: 700;
color: var(--text-primary);
}
.stats-toolbar-title i {
font-size: 1.1rem;
color: #818cf8;
}
.action-btn.primary {
background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
color: white;
}
.action-btn.primary:hover {
box-shadow: 0 0 10px rgba(99, 102, 241, 0.5);
transform: translateY(-1px);
}
/* ??? ALERT / LOADING ??? */
.stats-alert {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: rgba(245, 158, 11, 0.1);
border: 1px solid rgba(245, 158, 11, 0.25);
border-radius: var(--radius-md);
color: #fbbf24;
}
.stats-alert i { font-size: 1.4rem; }
.stats-alert strong { display: block; font-size: 0.8rem; }
.stats-alert small { opacity: 0.7; font-size: 0.72rem; }
.stats-loading {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
padding: 3rem;
color: var(--text-secondary);
}
.stats-empty { .stats-empty {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 0.5rem; gap: 8px;
padding: 2rem; padding: 32px;
color: var(--text-secondary); color: var(--md-on-surface-variant);
font-size: 0.78rem; font-size: 13px;
} }
.stats-empty i { font-size: 1.8rem; opacity: 0.4; } .stats-empty i { font-size: 2rem; opacity: 0.4; }
/* ??? PANELS ??? */ .stats-panel-products { flex: 1 1 auto; min-height: 200px; overflow: hidden; }
.stats-panel-products { .stats-panel-auctions { flex: 0 0 auto; max-height: 300px; overflow: hidden; }
flex: 1 1 auto;
min-height: 200px;
overflow: hidden;
}
.stats-panel-auctions {
flex: 0 0 auto;
max-height: 300px;
overflow: hidden;
}
/* ??? FILTER BAR (inside panel-header) ??? */
.stats-filter-bar {
display: flex;
align-items: center;
gap: 0.4rem;
margin-left: auto;
}
/* Filter bar */
.stats-filter-bar { display:flex; align-items:center; gap:6px; margin-left:auto; }
.stats-search { .stats-search {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.25rem; gap: 4px;
background: rgba(255, 255, 255, 0.05); background-color: var(--md-surface-container);
border: 1px solid rgba(255, 255, 255, 0.08); border: 1px solid var(--md-outline-variant);
border-radius: var(--radius-sm); border-radius: var(--md-shape-extra-small);
padding: 0.15rem 0.4rem; padding: 4px 10px;
font-size: 0.7rem; font-size: 13px;
} }
.stats-search i { color: var(--text-secondary); font-size: 0.65rem; } .stats-search i { color: var(--md-on-surface-variant); font-size: 12px; }
.stats-search input { .stats-search input {
background: transparent; background: transparent;
border: none; border: none;
outline: none; outline: none;
color: var(--text-primary); color: var(--md-on-surface);
font-size: 0.7rem; font-size: 13px;
width: 120px; width: 130px;
} font-family: var(--md-font-family);
.stats-search input::placeholder { color: rgba(255,255,255,0.25); }
.stats-search-clear {
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
padding: 0;
font-size: 0.7rem;
line-height: 1;
} }
.stats-search input::placeholder { color: var(--md-on-surface-variant); opacity: 0.6; }
.stats-search-clear { background:none; border:none; color:var(--md-on-surface-variant); cursor:pointer; padding:0; font-size:14px; }
.stats-select { /* Inline input (compact table cells) */
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius-sm);
color: var(--text-primary);
font-size: 0.7rem;
padding: 0.15rem 0.35rem;
outline: none;
}
.stats-select option { background: var(--bg-card); }
/* ??? TABLE (shared) ??? */
.stats-table {
width: 100%;
border-collapse: collapse;
font-size: 0.78rem;
}
.stats-table thead {
position: sticky;
top: 0;
z-index: 2;
}
.stats-table th {
padding: 0.4rem 0.5rem;
font-size: 0.68rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.3px;
color: var(--text-secondary);
background: rgba(255, 255, 255, 0.03);
border-bottom: 1px solid var(--border-color);
white-space: nowrap;
}
.stats-table td {
padding: 0.3rem 0.5rem;
vertical-align: middle;
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
color: var(--text-primary);
}
.stats-table tbody tr {
transition: background 0.12s ease;
}
.stats-table tbody tr:hover {
background: rgba(99, 102, 241, 0.06);
}
.stats-table tbody tr.row-won {
background: rgba(34, 197, 94, 0.05);
}
.stats-table tbody tr.row-won:hover {
background: rgba(34, 197, 94, 0.1);
}
.sortable-header {
cursor: pointer;
user-select: none;
}
.sortable-header:hover {
color: var(--text-primary);
background: rgba(255, 255, 255, 0.06) !important;
}
/* ??? CELL HELPERS ??? */
.cell-name { font-weight: 600; }
.cell-price { font-weight: 600; color: #818cf8; }
.cell-muted { color: var(--text-secondary); font-size: 0.72rem; }
.stats-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.15rem 0.4rem;
border-radius: 10px;
font-size: 0.68rem;
font-weight: 600;
line-height: 1;
}
.badge-success { background: rgba(34, 197, 94, 0.15); color: #4ade80; }
.badge-danger { background: rgba(239, 68, 68, 0.15); color: #f87171; }
.badge-muted { background: rgba(255, 255, 255, 0.05); color: var(--text-secondary); }
.stats-price { font-weight: 600; color: #818cf8; }
/* ??? PRODUCT TABLE INLINE ??? */
.product-table-inline {
table-layout: fixed;
}
.ps-col-name { width: auto; min-width: 180px; }
.ps-col-num { width: 42px; }
.ps-col-price { width: 62px; }
.ps-col-toggle { width: 34px; }
.ps-col-input { width: 72px; }
.ps-col-actions { width: 120px; }
.ps-product-title {
font-weight: 600;
font-size: 0.78rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 260px;
}
.ps-price-stat {
font-size: 0.72rem;
color: var(--text-secondary);
}
.ps-price-stat.ps-price-avg {
font-weight: 600;
color: #818cf8;
}
.ps-wins {
color: #fbbf24;
font-weight: 600;
}
/* ??? INLINE INPUT (DARK) ??? */
.ps-inline-input { .ps-inline-input {
width: 100%; width: 100%;
font-size: 0.7rem; font-size: 12px;
padding: 0.15rem 0.3rem; padding: 3px 6px;
height: 24px; height: 26px;
text-align: center; text-align: center;
border-radius: 4px; border-radius: var(--md-shape-extra-small);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid var(--md-outline);
background: rgba(255, 255, 255, 0.04); background-color: var(--md-surface-container-lowest);
color: var(--text-primary); color: var(--md-on-surface);
outline: none; outline: none;
transition: border-color 0.15s, box-shadow 0.15s; font-family: var(--md-font-family);
transition: border-color .15s;
} }
.ps-inline-input:focus { .ps-inline-input:focus { border: 2px solid var(--md-primary); padding: 2px 5px; }
border-color: rgba(99, 102, 241, 0.5); .ps-inline-input::placeholder { color: var(--md-on-surface-variant); opacity: 0.5; font-style:italic; font-size:11px; }
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.15);
background: rgba(255, 255, 255, 0.06);
}
.ps-inline-input::placeholder {
color: rgba(255, 255, 255, 0.2);
font-style: italic;
font-size: 0.62rem;
}
/* Hide number input spinners */
.ps-inline-input::-webkit-outer-spin-button, .ps-inline-input::-webkit-outer-spin-button,
.ps-inline-input::-webkit-inner-spin-button { .ps-inline-input::-webkit-inner-spin-button { -webkit-appearance:none; margin:0; }
-webkit-appearance: none; .ps-inline-input[type=number] { -moz-appearance:textfield; }
margin: 0;
}
.ps-inline-input[type=number] {
-moz-appearance: textfield;
}
/* ??? ACTION BUTTONS ??? */
.ps-action-group {
display: flex;
gap: 3px;
justify-content: center;
}
.product-row .btn-xs {
padding: 0.12rem 0.3rem;
font-size: 0.68rem;
line-height: 1.2;
border-radius: 4px;
border: 1px solid transparent;
cursor: pointer;
transition: all 0.15s ease;
}
.product-row .btn-xs i { font-size: 0.7rem; }
.btn-outline-info {
background: transparent;
border-color: rgba(56, 189, 248, 0.3);
color: #38bdf8;
}
.btn-outline-info:hover { background: rgba(56, 189, 248, 0.15); }
.btn-success {
background: rgba(34, 197, 94, 0.15);
color: #4ade80;
}
.btn-success:hover { background: rgba(34, 197, 94, 0.3); }
.btn-primary {
background: rgba(99, 102, 241, 0.15);
color: #a5b4fc;
}
.btn-primary:hover { background: rgba(99, 102, 241, 0.3); }
.btn-outline-danger {
background: transparent;
border-color: rgba(239, 68, 68, 0.3);
color: #f87171;
}
.btn-outline-danger:hover { background: rgba(239, 68, 68, 0.15); }
.btn-xs:disabled {
opacity: 0.35;
cursor: not-allowed;
}
/* ??? PRODUCT ROW HOVER ??? */
.product-row {
transition: background 0.12s ease;
}
.product-row:hover {
background: rgba(99, 102, 241, 0.04);
}
/* ??? TOGGLE SWITCH ??? */
.ps-switch {
position: relative;
display: inline-block;
width: 28px;
height: 16px;
cursor: pointer;
}
.ps-switch input {
opacity: 0;
width: 0;
height: 0;
}
.ps-switch-slider {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(255, 255, 255, 0.1);
border-radius: 16px;
transition: background 0.2s ease;
}
.ps-switch-slider::before {
content: '';
position: absolute;
width: 12px;
height: 12px;
left: 2px;
bottom: 2px;
background: var(--text-secondary);
border-radius: 50%;
transition: transform 0.2s ease, background 0.2s ease;
}
.ps-switch input:checked + .ps-switch-slider {
background: rgba(34, 197, 94, 0.3);
}
.ps-switch input:checked + .ps-switch-slider::before {
transform: translateX(12px);
background: #4ade80;
}
/* ??? DISABLED INPUT ??? */
.ps-inline-input.ps-input-disabled, .ps-inline-input.ps-input-disabled,
.ps-inline-input:disabled { .ps-inline-input:disabled { opacity: 0.35; cursor: not-allowed; }
opacity: 0.25;
cursor: not-allowed;
}
/* ═══ BULK ACTION BAR ═══ */ /* Column widths */
.ps-bulk-bar { .ps-col-select { width: 30px; }
display: flex; .ps-col-num { width: 44px; }
align-items: center; .ps-col-price { width: 64px; }
gap: 0.35rem; .ps-col-toggle { width: 36px; }
padding: 0.25rem 0.5rem; .ps-col-input { width: 76px; }
background: rgba(255, 255, 255, 0.02); .ps-col-actions { width: 124px; }
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
flex-shrink: 0; .ps-product-title { font-weight:500; font-size:13px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:260px; color:var(--md-on-surface); }
flex-wrap: wrap; .ps-price-stat { font-size:12px; color:var(--md-on-surface-variant); }
} .ps-price-stat.ps-price-avg { font-weight:600; color:var(--md-primary); }
.ps-bulk-label { .ps-wins { color:var(--md-warning); font-weight:600; }
font-size: 0.62rem;
color: var(--text-secondary); /* Action groups */
margin-right: 0.25rem; .ps-action-group { display:flex; gap:3px; justify-content:center; }
white-space: nowrap;
} /* Toggle switch — MD3 tonal */
.ps-bulk-bar .btn-xs { .ps-switch { position:relative; display:inline-block; width:28px; height:16px; }
padding: 0.12rem 0.4rem; .ps-switch input { opacity:0; width:0; height:0; }
font-size: 0.62rem; .ps-switch-slider { position:absolute; inset:0; background-color:var(--md-outline-variant); border-radius:999px; cursor:pointer; transition:.2s; }
line-height: 1.3; .ps-switch-slider::before { content:""; position:absolute; width:10px; height:10px; left:3px; bottom:3px; background:white; border-radius:50%; transition:.2s; }
border-radius: 4px; .ps-switch input:checked + .ps-switch-slider { background:var(--md-primary); }
border: 1px solid transparent; .ps-switch input:checked + .ps-switch-slider::before { transform:translateX(12px); }
cursor: pointer;
transition: all 0.15s ease; /* Bulk action bar */
white-space: nowrap; .ps-bulk-bar { display:flex; align-items:center; gap:6px; padding:6px 12px; background-color:var(--md-surface-container-low); border-bottom:1px solid var(--md-outline-variant); flex-wrap:wrap; }
} .ps-bulk-label { font-size:12px; color:var(--md-on-surface-variant); margin-right:4px; white-space:nowrap; }
.ps-bulk-bar .btn-xs i { font-size: 0.6rem; margin-right: 0.15rem; }
.ps-bulk-bar .btn-xs:disabled {
opacity: 0.35;
cursor: not-allowed;
}
.btn-outline-toggle {
background: transparent;
border-color: rgba(168, 162, 158, 0.25);
color: var(--text-secondary);
}
.btn-outline-toggle:hover { background: rgba(168, 162, 158, 0.1); }
</style> </style>
+15
View File
@@ -5,8 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="~/" /> <base href="~/" />
<link rel="icon" type="image/x-icon" href="Icon/favicon.ico" /> <link rel="icon" type="image/x-icon" href="Icon/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous" />
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Roboto+Mono:wght@400;500&display=swap" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet" />
<link href="css/material3.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" /> <link href="css/app.css" rel="stylesheet" />
<link href="css/animations.css" rel="stylesheet" /> <link href="css/animations.css" rel="stylesheet" />
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" /> <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
@@ -26,6 +30,17 @@
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
window.mdTheme = {
apply: function (dark) {
if (dark) {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.removeAttribute('data-theme');
}
}
};
</script>
<script src="_framework/blazor.server.js"></script> <script src="_framework/blazor.server.js"></script>
</body> </body>
</html> </html>
+23 -91
View File
@@ -1,103 +1,35 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
@inject IJSRuntime JSRuntime
@inject Microsoft.AspNetCore.Components.NavigationManager NavigationManager
<div class="app-container"> <div class="md-app-container">
<aside class="app-sidebar"> <NavMenu />
<NavMenu />
</aside>
<main class="app-main"> <main class="md-app-main">
<article class="app-content"> <article class="md-app-content">
@Body @Body
</article> </article>
</main> </main>
</div> </div>
<div id="blazor-error-ui"> <div id="blazor-error-ui">
<div class="error-content"> <i class="bi bi-exclamation-triangle-fill me-2"></i>
<i class="bi bi-exclamation-triangle-fill"></i> <span>Si è verificato un errore. <a href="" class="reload">Ricarica</a></span>
<span>Si e verificato un errore. <a href="" class="reload">Ricarica</a></span> <a class="ms-3" onclick="document.getElementById('blazor-error-ui').style.display='none'" style="cursor:pointer;color:inherit;">✕</a>
<button class="dismiss-btn" onclick="this.parentElement.parentElement.style.display='none'">×</button>
</div>
</div> </div>
<style> @code {
.app-container { protected override async Task OnAfterRenderAsync(bool firstRender)
display: flex; {
min-height: 100vh; if (firstRender)
background: #0f0f0f; {
} try
{
.app-sidebar { var s = AutoBidder.Utilities.SettingsManager.Load();
width: 260px; await JSRuntime.InvokeVoidAsync("mdTheme.apply", s.DarkMode);
min-width: 260px; }
height: 100vh; catch { /* JS helper may not be available yet; theme defaults to light mode */ }
position: fixed;
left: 0;
top: 0;
z-index: 1000;
}
.app-main {
flex: 1;
margin-left: 260px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.app-content {
flex: 1;
padding: 1.5rem;
overflow-y: auto;
}
#blazor-error-ui {
display: none;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
}
#blazor-error-ui .error-content {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 1rem 1.5rem;
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
color: white;
font-weight: 500;
}
#blazor-error-ui .reload {
color: white;
text-decoration: underline;
}
#blazor-error-ui .dismiss-btn {
margin-left: auto;
background: none;
border: none;
color: white;
font-size: 1.5rem;
cursor: pointer;
opacity: 0.8;
}
#blazor-error-ui .dismiss-btn:hover {
opacity: 1;
}
@@media (max-width: 768px) {
.app-sidebar {
width: 100%;
height: auto;
position: relative;
}
.app-main {
margin-left: 0;
} }
} }
</style> }
+61 -250
View File
@@ -1,91 +1,81 @@
@using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Authorization
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject AuthenticationStateProvider AuthenticationStateProvider @inject AuthenticationStateProvider AuthenticationStateProvider
@inject AuctionMonitor AuctionMonitor @inject AuctionMonitor AuctionMonitor
@implements IDisposable @implements IDisposable
<div class="nav-sidebar"> <div class="md-nav-rail">
<div class="nav-header">
<a class="nav-brand" href=""> <!-- Brand icon -->
<div class="brand-icon"> <div class="md-rail-header">
<i class="bi bi-lightning-charge-fill"></i> <a class="md-rail-brand-icon" href="" title="AutoBidder">
</div> <i class="bi bi-lightning-charge-fill"></i>
<span class="brand-text">AutoBidder</span>
</a> </a>
</div> </div>
<nav class="nav-menu"> <!-- Main navigation destinations -->
<div class="nav-section"> <nav class="md-rail-nav">
<NavLink class="nav-menu-item" href="" Match="NavLinkMatch.All"> <NavLink class="md-rail-item" href="" Match="NavLinkMatch.All" title="Monitor Aste">
<i class="bi bi-display"></i> <div class="md-rail-indicator"><i class="bi bi-display"></i></div>
<span>Monitor Aste</span> <span class="md-rail-label">Monitor</span>
</NavLink> </NavLink>
<NavLink class="nav-menu-item" href="browser">
<i class="bi bi-search"></i>
<span>Esplora Aste</span>
</NavLink>
<NavLink class="nav-menu-item" href="statistics">
<i class="bi bi-bar-chart"></i>
<span>Statistiche</span>
</NavLink>
<NavLink class="nav-menu-item" href="settings">
<i class="bi bi-gear"></i>
<span>Impostazioni</span>
</NavLink>
</div>
<div class="nav-footer"> <NavLink class="md-rail-item" href="browser" title="Esplora Aste">
<!-- Info Sessione Utente --> <div class="md-rail-indicator"><i class="bi bi-search"></i></div>
@if (!string.IsNullOrEmpty(sessionUsername)) <span class="md-rail-label">Esplora</span>
{ </NavLink>
<div class="session-stats">
<div class="session-stat"> <NavLink class="md-rail-item" href="statistics" title="Statistiche">
<i class="bi bi-hand-index-thumb-fill"></i> <div class="md-rail-indicator"><i class="bi bi-bar-chart"></i></div>
<div class="stat-content"> <span class="md-rail-label">Statistiche</span>
<span class="stat-label">Puntate</span> </NavLink>
<span class="stat-value @GetBidsClass()">@sessionRemainingBids</span>
</div> <NavLink class="md-rail-item" href="settings" title="Impostazioni">
</div> <div class="md-rail-indicator"><i class="bi bi-gear"></i></div>
<div class="session-stat"> <span class="md-rail-label">Impostazioni</span>
<i class="bi bi-wallet2"></i> </NavLink>
<div class="stat-content">
<span class="stat-label">Credito</span>
<span class="stat-value text-success">€@sessionShopCredit.ToString("F2")</span>
</div>
</div>
</div>
}
<AuthorizeView>
<Authorized>
<div class="user-badge @(string.IsNullOrEmpty(sessionUsername) ? "disconnected" : "connected")">
<i class="bi bi-person-circle"></i>
<span>@(string.IsNullOrEmpty(sessionUsername) ? "Non connesso" : sessionUsername)</span>
</div>
<a href="/Account/Logout" class="nav-menu-item logout-item">
<i class="bi bi-box-arrow-right"></i>
<span>Logout</span>
</a>
</Authorized>
</AuthorizeView>
</div>
</nav> </nav>
<!-- Footer: session stats + user + logout -->
<div class="md-rail-footer">
@if (!string.IsNullOrEmpty(sessionUsername))
{
<div class="md-rail-stat @GetBidsClass()" title="Puntate rimanenti: @sessionRemainingBids">
<i class="bi bi-hand-index-thumb-fill"></i>
<span>@sessionRemainingBids</span>
</div>
<div class="md-rail-stat" style="color:var(--md-success);" title="Credito: €@sessionShopCredit.ToString("F2")">
<i class="bi bi-wallet2"></i>
<span style="font-size:10px;">€@sessionShopCredit.ToString("F0")</span>
</div>
}
<AuthorizeView>
<Authorized>
<div class="md-rail-user @(string.IsNullOrEmpty(sessionUsername) ? "disconnected" : "connected")"
title="@(string.IsNullOrEmpty(sessionUsername) ? "Non connesso" : sessionUsername)">
<i class="bi bi-person-circle"></i>
</div>
<a href="/Account/Logout" class="md-rail-logout" title="Logout">
<div class="md-rail-indicator"><i class="bi bi-box-arrow-right"></i></div>
<span class="md-rail-label">Esci</span>
</a>
</Authorized>
</AuthorizeView>
</div>
</div> </div>
@code { @code {
private string? sessionUsername; private string? sessionUsername;
private int sessionRemainingBids; private int sessionRemainingBids;
private double sessionShopCredit; private double sessionShopCredit;
private System.Threading.Timer? refreshTimer; private System.Threading.Timer? refreshTimer;
protected override void OnInitialized() protected override void OnInitialized()
{ {
LoadSessionInfo(); LoadSessionInfo();
// Refresh ogni 5 secondi
refreshTimer = new System.Threading.Timer(async _ => refreshTimer = new System.Threading.Timer(async _ =>
{ {
LoadSessionInfo(); LoadSessionInfo();
@@ -120,182 +110,3 @@ private System.Threading.Timer? refreshTimer;
refreshTimer?.Dispose(); refreshTimer?.Dispose();
} }
} }
<style>
.nav-sidebar {
display: flex;
flex-direction: column;
height: 100%;
background: linear-gradient(180deg, #1a1d23 0%, #13151a 100%);
border-right: 1px solid rgba(255, 255, 255, 0.06);
}
.nav-header {
padding: 1.25rem 1.5rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.nav-brand {
display: flex;
align-items: center;
gap: 0.75rem;
text-decoration: none;
transition: opacity 0.2s;
}
.nav-brand:hover {
opacity: 0.9;
}
.brand-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
border-radius: 10px;
font-size: 1.25rem;
color: white;
}
.brand-text {
font-size: 1.25rem;
font-weight: 700;
color: white;
letter-spacing: -0.02em;
}
.nav-menu {
display: flex;
flex-direction: column;
flex: 1;
padding: 1rem 0.75rem;
overflow-y: auto;
}
.nav-section {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.nav-menu-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 8px;
color: rgba(255, 255, 255, 0.65);
text-decoration: none;
font-size: 0.9375rem;
font-weight: 500;
transition: all 0.15s ease;
}
.nav-menu-item i {
font-size: 1.125rem;
width: 1.5rem;
text-align: center;
}
.nav-menu-item:hover {
background: rgba(255, 255, 255, 0.06);
color: white;
}
.nav-menu-item.active {
background: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(139, 92, 246, 0.15) 100%);
color: #a5b4fc;
}
.nav-menu-item.active i {
color: #818cf8;
}
.nav-footer {
margin-top: auto;
padding-top: 1rem;
border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.session-stats {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.75rem;
margin-bottom: 0.75rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.session-stat {
display: flex;
align-items: center;
gap: 0.625rem;
padding: 0.375rem 0;
}
.session-stat i {
font-size: 0.875rem;
width: 1.25rem;
text-align: center;
color: rgba(255, 255, 255, 0.5);
}
.session-stat .stat-content {
display: flex;
justify-content: space-between;
flex: 1;
align-items: center;
}
.session-stat .stat-label {
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.5);
}
.session-stat .stat-value {
font-size: 0.875rem;
font-weight: 600;
}
.session-stat .text-success { color: #22c55e; }
.session-stat .text-warning { color: #f59e0b; }
.session-stat .text-danger { color: #ef4444; }
.user-badge {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
margin-bottom: 0.5rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 8px;
color: rgba(255, 255, 255, 0.5);
font-size: 0.875rem;
}
.user-badge.connected {
border-left: 3px solid #22c55e;
}
.user-badge.disconnected {
border-left: 3px solid #ef4444;
color: rgba(255, 255, 255, 0.4);
}
.user-badge i {
font-size: 1.25rem;
}
.logout-item {
color: rgba(248, 113, 113, 0.8) !important;
}
.logout-item:hover {
background: rgba(248, 113, 113, 0.1) !important;
color: #f87171 !important;
}
</style>
+10
View File
@@ -429,6 +429,16 @@ namespace AutoBidder.Utilities
/// Default: true /// Default: true
/// </summary> /// </summary>
public bool SaveBidMetricsToDatabase { get; set; } = true; public bool SaveBidMetricsToDatabase { get; set; } = true;
// ═══════════════════════════════════════════════════════════════════
// INTERFACCIA
// ═══════════════════════════════════════════════════════════════════
/// <summary>
/// Abilita la modalità scura dell'interfaccia.
/// Default: false (modalità chiara)
/// </summary>
public bool DarkMode { get; set; } = false;
} }
public static class SettingsManager public static class SettingsManager
File diff suppressed because it is too large Load Diff