✨ feat(admin): add admin device, post, and report features

- add admin endpoints to create devices and update device settings
- expose device members and post detail for admin management
- add post filters by device, author, type, and date range
- add user growth, device uptime, and usage reports with CSV export
- improve admin layout with sticky header, user info, and logout
- add device and author search filters across admin pages
This commit is contained in:
2026-09-25 18:39:52 +03:30
parent 6abb5aae15
commit 69c0381530
13 changed files with 1847 additions and 62 deletions

View File

@@ -113,10 +113,86 @@ public sealed class AdminDeviceDetailDto
public string? DevicePhoneNumber { get; set; }
public int? UploadIntervalMin { get; set; }
public int? RelayCount { get; set; }
public string? NeshanLocation { get; set; }
public decimal? Latitude { get; set; }
public decimal? Longitude { get; set; }
public int? MinimumSmsIntervalMinutes { get; set; }
public int? MinimumCallIntervalMinutes { get; set; }
public decimal? AreaSquareMeters { get; set; }
public Domain.SimCardType? SimCardType { get; set; }
public string? SmsRecievers { get; set; }
public IReadOnlyList<TelemetryDto> LastTelemetry { get; set; } = [];
}
// ==================== Devices: create / settings / members ====================
public sealed class AdminDeviceCreateRequest
{
public int UserId { get; set; }
public string DeviceName { get; set; } = string.Empty;
public string Location { get; set; } = string.Empty;
public string? NeshanLocation { get; set; }
}
public enum AdminDeviceCreateOutcome
{
Ok,
UserNotFound,
ConflictDuplicateName,
Validation
}
public sealed class AdminDeviceCreateResult
{
public AdminDeviceCreateOutcome Outcome { get; set; }
public int DeviceId { get; set; }
public string DeviceName { get; set; } = string.Empty;
/// <summary>پیام فارسی برای خطاهای Validation</summary>
public string Message { get; set; } = string.Empty;
}
public sealed class AdminDeviceSettingsUpdateRequest
{
public string DeviceName { get; set; } = string.Empty;
public string Province { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public decimal? Latitude { get; set; }
public decimal? Longitude { get; set; }
public string ProductType { get; set; } = string.Empty;
public int MinimumSmsIntervalMinutes { get; set; }
public int MinimumCallIntervalMinutes { get; set; }
public decimal? AreaSquareMeters { get; set; }
public int UploadIntervalMin { get; set; }
public string DevicePhoneNumber { get; set; } = string.Empty;
public Domain.SimCardType? SimCardType { get; set; }
public int RelayCount { get; set; }
public string SmsRecievers { get; set; } = string.Empty;
}
/// <summary>
/// Success = !NotFound && Message == null
/// </summary>
public sealed class AdminSettingsUpdateResult
{
public bool NotFound { get; set; }
public string? Message { get; set; }
}
public sealed class AdminDeviceMemberDto
{
public int UserId { get; set; }
public string Name { get; set; } = string.Empty;
public string Family { get; set; } = string.Empty;
public string Mobile { get; set; } = string.Empty;
public string MemberName { get; set; } = string.Empty;
public string MemberFamily { get; set; } = string.Empty;
public Domain.DeviceMemberRole Role { get; set; }
public bool ReceiveAlerts { get; set; }
public string? AvatarPath { get; set; }
public bool IsOwner { get; set; }
}
// ==================== Usage ====================
public sealed class UsageDayDto
@@ -158,6 +234,10 @@ public sealed class AdminAlertItemDto
public sealed class AdminPostFilter
{
public int? DeviceId { get; set; }
public int? AuthorUserId { get; set; }
public Domain.DevicePostType? PostType { get; set; }
public DateTime? From { get; set; }
public DateTime? To { get; set; }
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 20;
}
@@ -167,13 +247,38 @@ public sealed class AdminPostItemDto
public int Id { get; set; }
public int DeviceId { get; set; }
public string DeviceName { get; set; } = string.Empty;
public int AuthorUserId { get; set; }
public string AuthorName { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public Domain.DevicePostType PostType { get; set; }
public int ImageCount { get; set; }
public bool HasVoice { get; set; }
public int? VoiceDurationSeconds { get; set; }
public DateTime CreatedAt { get; set; }
}
public sealed class AdminPostDetailDto
{
public int Id { get; set; }
public int DeviceId { get; set; }
public string DeviceName { get; set; } = string.Empty;
public string AuthorName { get; set; } = string.Empty;
public string AuthorFamily { get; set; } = string.Empty;
public Domain.DevicePostType PostType { get; set; }
public string Content { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public IReadOnlyList<AdminPostImageDto> Images { get; set; } = [];
public string? VoiceFilePath { get; set; }
public int? VoiceDurationSeconds { get; set; }
}
public sealed class AdminPostImageDto
{
public int Id { get; set; }
public string FileName { get; set; } = string.Empty;
public string FilePath { get; set; } = string.Empty;
}
// ==================== Device Debug ====================
public sealed class AdminDeviceTestResultDto
@@ -182,6 +287,37 @@ public sealed class AdminDeviceTestResultDto
public string Message { get; set; } = string.Empty;
}
// ==================== Reports ====================
public sealed class AdminUserGrowthRowDto
{
public DateOnly Date { get; set; }
public int NewUsers { get; set; }
public int ActiveUsers { get; set; }
}
public sealed class AdminDeviceUptimeRowDto
{
public int DeviceId { get; set; }
public string DeviceName { get; set; } = string.Empty;
public string OwnerName { get; set; } = string.Empty;
public int TotalDays { get; set; }
public int OnlineDays { get; set; }
public int OnlinePercent { get; set; }
}
public sealed class AdminUsageByUserRowDto
{
public int UserId { get; set; }
public string Name { get; set; } = string.Empty;
public string Family { get; set; } = string.Empty;
public string Mobile { get; set; } = string.Empty;
public int Sms { get; set; }
public int VoiceCall { get; set; }
public int AiQueries { get; set; }
public int AiTokens { get; set; }
}
// ==================== Audit Log ====================
public sealed class AdminAuditLogFilter