mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
Dashboard (#3530)
* Add dashboard and chart basics * More widgets * Make widgets responsive * Layout dashboard * Prepare ExplorerClient * Switch to Chartist * Dynamic data for store numbers and recent transactions tiles * Dynamic data for recent invoices tile * Improvements * Plug NBXPlorer DB * Properly filter by code * Reorder cheat mode button * AJAX update for graph data * Fix create invoice button * Retry connection on transient issues * App Top Items stats * Design updates * App Sales stats * Add points for weekly histogram, set last point to current balance Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
@@ -90,9 +90,9 @@ namespace BTCPayServer.Services.Apps
|
||||
}
|
||||
|
||||
var invoices = await GetInvoicesForApp(appData, lastResetDate);
|
||||
var completeInvoices = invoices.Where(entity => entity.Status == InvoiceStatusLegacy.Complete || entity.Status == InvoiceStatusLegacy.Confirmed).ToArray();
|
||||
var pendingInvoices = invoices.Where(entity => !(entity.Status == InvoiceStatusLegacy.Complete || entity.Status == InvoiceStatusLegacy.Confirmed)).ToArray();
|
||||
var paidInvoices = invoices.Where(entity => entity.Status == InvoiceStatusLegacy.Complete || entity.Status == InvoiceStatusLegacy.Confirmed || entity.Status == InvoiceStatusLegacy.Paid).ToArray();
|
||||
var completeInvoices = invoices.Where(IsComplete).ToArray();
|
||||
var pendingInvoices = invoices.Where(IsPending).ToArray();
|
||||
var paidInvoices = invoices.Where(IsPaid).ToArray();
|
||||
|
||||
var pendingPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, pendingInvoices, !settings.EnforceTargetAmount);
|
||||
var currentPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, completeInvoices, !settings.EnforceTargetAmount);
|
||||
@@ -102,11 +102,12 @@ namespace BTCPayServer.Services.Apps
|
||||
.GroupBy(entity => entity.Metadata.ItemCode)
|
||||
.ToDictionary(entities => entities.Key, entities => entities.Count());
|
||||
|
||||
Dictionary<string, decimal> perkValue = new Dictionary<string, decimal>();
|
||||
Dictionary<string, decimal> perkValue = new();
|
||||
if (settings.DisplayPerksValue)
|
||||
{
|
||||
perkValue = paidInvoices
|
||||
.Where(entity => entity.Currency.Equals(settings.TargetCurrency, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(entity.Metadata.ItemCode))
|
||||
.Where(entity => entity.Currency.Equals(settings.TargetCurrency, StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.IsNullOrEmpty(entity.Metadata.ItemCode))
|
||||
.GroupBy(entity => entity.Metadata.ItemCode)
|
||||
.ToDictionary(entities => entities.Key, entities =>
|
||||
entities.Sum(entity => entity.GetPayments(true).Sum(pay =>
|
||||
@@ -117,6 +118,7 @@ namespace BTCPayServer.Services.Apps
|
||||
return rate * value;
|
||||
})));
|
||||
}
|
||||
|
||||
var perks = Parse(settings.PerksTemplate, settings.TargetCurrency);
|
||||
if (settings.SortPerksByPopularity)
|
||||
{
|
||||
@@ -160,10 +162,9 @@ namespace BTCPayServer.Services.Apps
|
||||
Sounds = settings.Sounds,
|
||||
AnimationColors = settings.AnimationColors,
|
||||
CurrencyData = _Currencies.GetCurrencyData(settings.TargetCurrency, true),
|
||||
CurrencyDataPayments = currentPayments.Select(pair => pair.Key)
|
||||
.Concat(pendingPayments.Select(pair => pair.Key))
|
||||
.Select(id => _Currencies.GetCurrencyData(id.CryptoCode, true))
|
||||
.DistinctBy(data => data.Code)
|
||||
CurrencyDataPayments = Enumerable.DistinctBy(currentPayments.Select(pair => pair.Key)
|
||||
.Concat(pendingPayments.Select(pair => pair.Key))
|
||||
.Select(id => _Currencies.GetCurrencyData(id.CryptoCode, true)), data => data.Code)
|
||||
.ToDictionary(data => data.Code, data => data),
|
||||
Info = new CrowdfundInfo
|
||||
{
|
||||
@@ -181,12 +182,101 @@ namespace BTCPayServer.Services.Apps
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsPending(InvoiceEntity entity)
|
||||
{
|
||||
return !(entity.Status == InvoiceStatusLegacy.Complete || entity.Status == InvoiceStatusLegacy.Confirmed);
|
||||
}
|
||||
|
||||
private static bool IsComplete(InvoiceEntity entity)
|
||||
{
|
||||
return entity.Status == InvoiceStatusLegacy.Complete || entity.Status == InvoiceStatusLegacy.Confirmed;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ItemStats>> GetPerkStats(AppData appData)
|
||||
{
|
||||
var settings = appData.GetSettings<CrowdfundSettings>();
|
||||
var invoices = await GetInvoicesForApp(appData);
|
||||
var paidInvoices = invoices.Where(IsPaid).ToArray();
|
||||
var currencyData = _Currencies.GetCurrencyData(settings.TargetCurrency, true);
|
||||
var perks = Parse(settings.PerksTemplate, settings.TargetCurrency);
|
||||
var perkCount = paidInvoices
|
||||
.Where(entity => !string.IsNullOrEmpty(entity.Metadata.ItemCode) &&
|
||||
entity.Currency.Equals(settings.TargetCurrency, StringComparison.OrdinalIgnoreCase))
|
||||
.GroupBy(entity => entity.Metadata.ItemCode)
|
||||
.Select(entities =>
|
||||
{
|
||||
var total = entities
|
||||
.Sum(entity => entity.GetPayments(true)
|
||||
.Sum(pay => {
|
||||
var paymentMethodId = pay.GetPaymentMethodId();
|
||||
var value = pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee;
|
||||
var rate = entity.GetPaymentMethod(paymentMethodId).Rate;
|
||||
return rate * value;
|
||||
}));
|
||||
var itemCode = entities.Key;
|
||||
var perk = perks.First(p => p.Id == itemCode);
|
||||
return new ItemStats
|
||||
{
|
||||
ItemCode = itemCode,
|
||||
Title = perk.Title,
|
||||
SalesCount = entities.Count(),
|
||||
Total = total,
|
||||
TotalFormatted = $"{total.ShowMoney(currencyData.Divisibility)} {settings.TargetCurrency}"
|
||||
};
|
||||
})
|
||||
.OrderByDescending(stats => stats.SalesCount);
|
||||
|
||||
return perkCount;
|
||||
}
|
||||
|
||||
public async Task<SalesStats> GetSalesStats(AppData appData, int numberOfDays = 7)
|
||||
{
|
||||
var invoices = await GetInvoicesForApp(appData);
|
||||
var paidInvoices = invoices.Where(IsPaid).ToArray();
|
||||
var series = paidInvoices
|
||||
.Where(entity => !string.IsNullOrEmpty(entity.Metadata.ItemCode) &&
|
||||
entity.InvoiceTime > DateTimeOffset.UtcNow - TimeSpan.FromDays(numberOfDays))
|
||||
.GroupBy(entity => entity.InvoiceTime.Date)
|
||||
.Select(entities => new SalesStatsItem
|
||||
{
|
||||
Date = entities.Key,
|
||||
Label = entities.Key.ToString("MMM dd", CultureInfo.InvariantCulture),
|
||||
SalesCount = entities.Count()
|
||||
});
|
||||
|
||||
// fill up the gaps
|
||||
foreach (var i in Enumerable.Range(0, numberOfDays))
|
||||
{
|
||||
var date = (DateTimeOffset.UtcNow - TimeSpan.FromDays(i)).Date;
|
||||
if (!series.Any(e => e.Date == date))
|
||||
{
|
||||
series = series.Append(new SalesStatsItem
|
||||
{
|
||||
Date = date,
|
||||
Label = date.ToString("MMM dd", CultureInfo.InvariantCulture)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new SalesStats
|
||||
{
|
||||
SalesCount = paidInvoices.Length,
|
||||
Series = series.OrderBy(i => i.Label)
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsPaid(InvoiceEntity entity)
|
||||
{
|
||||
return entity.Status == InvoiceStatusLegacy.Complete || entity.Status == InvoiceStatusLegacy.Confirmed || entity.Status == InvoiceStatusLegacy.Paid;
|
||||
}
|
||||
|
||||
public static string GetCrowdfundOrderId(string appId) => $"crowdfund-app_{appId}";
|
||||
public static string GetAppInternalTag(string appId) => $"APP#{appId}";
|
||||
public static string[] GetAppInternalTags(InvoiceEntity invoice)
|
||||
{
|
||||
return invoice.GetInternalTags("APP#");
|
||||
}
|
||||
|
||||
private async Task<InvoiceEntity[]> GetInvoicesForApp(AppData appData, DateTime? startDate = null)
|
||||
{
|
||||
var invoices = await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
@@ -572,4 +662,26 @@ namespace BTCPayServer.Services.Apps
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class ItemStats
|
||||
{
|
||||
public string ItemCode { get; set; }
|
||||
public string Title { get; set; }
|
||||
public int SalesCount { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
public string TotalFormatted { get; set; }
|
||||
}
|
||||
|
||||
public class SalesStats
|
||||
{
|
||||
public int SalesCount { get; set; }
|
||||
public IEnumerable<SalesStatsItem> Series { get; set; }
|
||||
}
|
||||
|
||||
public class SalesStatsItem
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public string Label { get; set; }
|
||||
public int SalesCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user