✨ feat(admin): improve search and owner selection

- debounce admin searches to reduce API calls while typing
- apply 300ms debounce to alerts, audit, devices, posts
- allow clearing selected owner in device creation form

🐛 fix(admin): correct admin data and filters

- use invariant culture for CSV numeric report values
- align default report end date with UTC day boundary
- prevent duplicate owner row in device member list
- make user growth date filtering EF translatable
- clear report preset when user picks a custom date range
- send zero-valued admin filter parameters in API client
This commit is contained in:
2026-09-25 19:04:57 +03:30
parent 69c0381530
commit b557590303
8 changed files with 122 additions and 51 deletions

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Text;
using GreenHome.Api.Filters;
using GreenHome.Application;
@@ -261,7 +262,7 @@ public sealed class AdminController : ControllerBase
if (csv)
return ToCsvFile("report-users",
new[] { "تاریخ", "کاربران جدید", "کاربران فعال" },
rows.Select(r => new[] { r.Date.ToString("yyyy-MM-dd"), r.NewUsers.ToString(), r.ActiveUsers.ToString() }), t);
rows.Select(r => new[] { r.Date.ToString("yyyy-MM-dd"), r.NewUsers.ToString(CultureInfo.InvariantCulture), r.ActiveUsers.ToString(CultureInfo.InvariantCulture) }), t);
return Ok(rows);
}
@@ -274,7 +275,7 @@ public sealed class AdminController : ControllerBase
if (csv)
return ToCsvFile("report-devices-uptime",
new[] { "دستگاه", "مالک", "کل روزها", "روزهای آنلاین", "درصد آنلاین" },
rows.Select(r => new[] { r.DeviceName, r.OwnerName, r.TotalDays.ToString(), r.OnlineDays.ToString(), r.OnlinePercent.ToString() }), t);
rows.Select(r => new[] { r.DeviceName, r.OwnerName, r.TotalDays.ToString(CultureInfo.InvariantCulture), r.OnlineDays.ToString(CultureInfo.InvariantCulture), r.OnlinePercent.ToString(CultureInfo.InvariantCulture) }), t);
return Ok(rows);
}
@@ -287,13 +288,14 @@ public sealed class AdminController : ControllerBase
if (csv)
return ToCsvFile("report-usage-by-user",
new[] { "نام", "نام خانوادگی", "موبایل", "پیامک", "تماس صوتی", "درخواست هوش مصنوعی", "توکن هوش مصنوعی" },
rows.Select(r => new[] { r.Name, r.Family, r.Mobile, r.Sms.ToString(), r.VoiceCall.ToString(), r.AiQueries.ToString(), r.AiTokens.ToString() }), t);
rows.Select(r => new[] { r.Name, r.Family, r.Mobile, r.Sms.ToString(CultureInfo.InvariantCulture), r.VoiceCall.ToString(CultureInfo.InvariantCulture), r.AiQueries.ToString(CultureInfo.InvariantCulture), r.AiTokens.ToString(CultureInfo.InvariantCulture) }), t);
return Ok(rows);
}
// ponytail: day boundary is UTC (whole system is UTC); if user timezone matters later, make report range timezone-aware.
private static (DateTime From, DateTime To) ResolveReportRange(DateTime? from, DateTime? to)
{
var t = (to ?? DateTime.Today).Date;
var t = (to ?? DateTime.UtcNow).Date;
var f = (from ?? t.AddDays(-6)).Date;
return (f, t);
}