Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions SnakeAid.Api/Pages/Admin/SnakeLibs/Index.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@page "/admin/snakelibs"
@model SnakeAid.Api.Pages.Admin.SnakeLibs.IndexModel
@{
ViewData["Title"] = "Snake species library";
}

<h1>Snake Library - Loài rắn (UI) ✅</h1>

<table class="table table-striped">
<thead>
<tr>
<th>Id</th>
<th>Scientific name</th>
<th>Common name</th>
<th>Venomous</th>
<th>Primary venom</th>
<th>Alt names</th>
<th>Media</th>
<th>Tariffs</th>
</tr>
</thead>
<tbody>
@foreach (var s in Model.Species)
{
<tr>
<td>@s.Id</td>
<td>@s.ScientificName</td>
<td>@(string.IsNullOrWhiteSpace(s.CommonName) ? "-" : s.CommonName)</td>
<td>@(s.IsVenomous ? "Yes" : "No")</td>
<td>@(s.PrimaryVenomType?.ToString() ?? "-")</td>
<td>@(s.AlternativeNames?.Count ?? 0)</td>
<td>@(s.LibraryMedias?.Count ?? 0)</td>
<td>@(s.SnakeCatchingTariffs?.Count ?? 0)</td>
</tr>
<tr>
<td colspan="8">
<strong>Details:</strong>
<div>
<em>Alternative names:</em>
@if (s.AlternativeNames?.Any() == true) {
<ul>
@foreach (var n in s.AlternativeNames)
{
<li>@n.Name</li>
}
</ul>
} else { <span>—</span> }
</div>
<div>
<em>Venom types:</em>
@if (s.SpeciesVenoms?.Any() == true) {
<ul>
@foreach (var v in s.SpeciesVenoms)
{
<li>@(v.VenomType?.Name ?? "-")</li>
}
</ul>
} else { <span>—</span> }
</div>
</td>
</tr>
}
</tbody>
</table>
32 changes: 32 additions & 0 deletions SnakeAid.Api/Pages/Admin/SnakeLibs/Index.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using SnakeAid.Core.Domains;
using SnakeAid.Repository.Data;
using SnakeAid.Repository.Interfaces;

namespace SnakeAid.Api.Pages.Admin.SnakeLibs
{
public class IndexModel : PageModel
{
private readonly IUnitOfWork<SnakeAidDbContext> _unitOfWork;

public IList<SnakeSpecies> Species { get; set; } = new List<SnakeSpecies>();

public IndexModel(IUnitOfWork<SnakeAidDbContext> unitOfWork)
{
_unitOfWork = unitOfWork;
}

public async Task OnGetAsync()
{
// Eager-load alternative names, media, venoms (+ venom type), and catching tariffs
Func<IQueryable<SnakeSpecies>, IQueryable<SnakeSpecies>> include = q => q
.Include(s => s.AlternativeNames)
.Include(s => s.LibraryMedias)
.Include(s => s.SpeciesVenoms).ThenInclude(sv => sv.VenomType)
.Include(s => s.SnakeCatchingTariffs);

Species = (await _unitOfWork.GetRepository<SnakeSpecies>().GetListAsync(include: include, asNoTracking: true)).ToList();
}
}
}
50 changes: 50 additions & 0 deletions SnakeAid.Api/Pages/Admin/SnakebiteIncidents/Index.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@page "/admin/incidents"
@model SnakeAid.Api.Pages.Admin.SnakebiteIncidents.IndexModel
@{
ViewData["Title"] = "Snakebite incidents";
}

<h1>Snakebite Incidents (UI) ✅</h1>

<table class="table table-striped">
<thead>
<tr>
<th>Id</th>
<th>User</th>
<th>Assigned Rescuer</th>
<th>Status</th>
<th>Sessions</th>
<th>Media</th>
</tr>
</thead>
<tbody>
@foreach (var incident in Model.Incidents)
{
<tr>
<td>@incident.Id</td>
<td>@(incident.User?.Account?.FullName ?? "-")<br/><small>@(incident.User?.Account?.Email ?? "")</small></td>
<td>@(incident.AssignedRescuer?.Account?.FullName ?? "-")</td>
<td>@incident.Status</td>
<td>@(incident.Sessions?.Count ?? 0)</td>
<td>@(incident.Media?.Count ?? 0)</td>
</tr>
<tr>
<td colspan="6">
<strong>Sessions details:</strong>
<ul>
@foreach (var s in incident.Sessions ?? Enumerable.Empty<SnakeAid.Core.Domains.RescueRequestSession>())
{
<li>Session #@s.SessionNumber - Requests: @s.Requests?.Count</li>
<ul>
@foreach (var r in s.Requests ?? Enumerable.Empty<SnakeAid.Core.Domains.RescuerRequest>())
{
<li>@(r.Rescuer?.Account?.FullName ?? "Unknown") - @r.Status</li>
}
</ul>
}
</ul>
</td>
</tr>
}
</tbody>
</table>
32 changes: 32 additions & 0 deletions SnakeAid.Api/Pages/Admin/SnakebiteIncidents/Index.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using SnakeAid.Core.Domains;
using SnakeAid.Repository.Data;
using SnakeAid.Repository.Interfaces;

namespace SnakeAid.Api.Pages.Admin.SnakebiteIncidents
{
public class IndexModel : PageModel
{
private readonly IUnitOfWork<SnakeAidDbContext> _unitOfWork;

public IList<SnakebiteIncident> Incidents { get; set; } = new List<SnakebiteIncident>();

public IndexModel(IUnitOfWork<SnakeAidDbContext> unitOfWork)
{
_unitOfWork = unitOfWork;
}

public async Task OnGetAsync()
{
// Eager-load common related entities (user -> account, assigned rescuer, sessions -> requests -> rescuer -> account, media)
Func<IQueryable<SnakebiteIncident>, IQueryable<SnakebiteIncident>> include = query => query
.Include(i => i.User).ThenInclude(u => u.Account)
.Include(i => i.AssignedRescuer).ThenInclude(r => r.Account)
.Include(i => i.Sessions).ThenInclude(s => s.Requests).ThenInclude(r => r.Rescuer).ThenInclude(rp => rp.Account)
.Include(i => i.Media);

Incidents = (await _unitOfWork.GetRepository<SnakebiteIncident>().GetListAsync(include: include, asNoTracking: true)).ToList();
}
}
}
6 changes: 6 additions & 0 deletions SnakeAid.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ public static async Task Main(string[] args)
});

builder.Services.AddControllers();

// Add Razor Pages for lightweight UI admin pages
builder.Services.AddRazorPages();

// Add SignalR
builder.Services.AddSignalR(options =>
Expand Down Expand Up @@ -304,6 +307,9 @@ public static async Task Main(string[] args)
// Map SignalR Hub with specific CORS policy
app.MapHub<TestChatHub>("/chat-hub").RequireCors("SignalRCorsPolicy");

// Map Razor pages
app.MapRazorPages();

app.MapControllers();

// Health checks endpoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ namespace SnakeAid.Core.Requests.SymptomConfig
public class CreateSymptomConfigRequest
{
/// <summary>
/// Khóa thuộc tính (VD: "BITE_LOCATION", "AGE_GROUP", "SYMPTOMS")
/// Nhóm thuộc tính (VD: "GENERAL", "LOCAL", "CRITICAL")
/// </summary>
[Required]
[MaxLength(100, ErrorMessage = "GroupName must not exceed 100 characters")]
public string GroupName { get; set; }

/// <summary>
/// Khóa thuộc tính (VD: "BITE_LOCATION", "CORE_SIGNS")
/// </summary>
[Required(ErrorMessage = "AttributeKey is required")]
[MaxLength(100, ErrorMessage = "AttributeKey must not exceed 100 characters")]
Expand Down Expand Up @@ -39,6 +46,17 @@ public class CreateSymptomConfigRequest
[MaxLength(1000, ErrorMessage = "Description must not exceed 1000 characters")]
public string? Description { get; set; }

/// <summary>
/// Đánh dấu triệu chứng nguy kịch (hiển thị popup cảnh báo)
/// </summary>
public bool IsCritical { get; set; } = false;

/// <summary>
/// Nội dung cảnh báo khi triệu chứng nguy kịch được chọn
/// </summary>
[MaxLength(1000, ErrorMessage = "AlertMessage must not exceed 1000 characters")]
public string? AlertMessage { get; set; }

/// <summary>
/// Trạng thái kích hoạt
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions SnakeAid.Core/Requests/SymptomConfig/GetSymptomConfigRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ namespace SnakeAid.Core.Requests.SymptomConfig
{
public class GetSymptomConfigRequest : PaginationRequest
{
/// <summary>
/// Tìm kiếm theo GroupName
/// </summary>
public string? GroupName { get; set; }

/// <summary>
/// Tìm kiếm theo AttributeKey
/// </summary>
Expand All @@ -25,6 +30,11 @@ public class GetSymptomConfigRequest : PaginationRequest
/// </summary>
public bool? IsActive { get; set; }

/// <summary>
/// Lọc theo triệu chứng nguy kịch
/// </summary>
public bool? IsCritical { get; set; }

/// <summary>
/// Lọc theo VenomTypeId
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions SnakeAid.Core/Requests/SymptomConfig/UpdateSymptomConfigRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ namespace SnakeAid.Core.Requests.SymptomConfig
{
public class UpdateSymptomConfigRequest
{
/// <summary>
/// Nhóm thuộc tính (VD: "GENERAL", "LOCAL", "CRITICAL")
/// </summary>
[Required]
[MaxLength(100, ErrorMessage = "GroupName must not exceed 100 characters")]
public string GroupName { get; set; }

/// <summary>
/// Khóa thuộc tính (VD: "BITE_LOCATION", "AGE_GROUP", "SYMPTOMS")
/// </summary>
Expand Down Expand Up @@ -35,6 +42,17 @@ public class UpdateSymptomConfigRequest
[MaxLength(1000, ErrorMessage = "Description must not exceed 1000 characters")]
public string? Description { get; set; }

/// <summary>
/// Đánh dấu triệu chứng nguy kịch (hiển thị popup cảnh báo)
/// </summary>
public bool? IsCritical { get; set; }

/// <summary>
/// Nội dung cảnh báo khi triệu chứng nguy kịch được chọn
/// </summary>
[MaxLength(1000, ErrorMessage = "AlertMessage must not exceed 1000 characters")]
public string? AlertMessage { get; set; }

/// <summary>
/// Trạng thái kích hoạt
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ namespace SnakeAid.Core.Responses.SymptomConfig
public class SymptomConfigResponse
{
public int Id { get; set; }
public string GroupName { get; set; }
public string AttributeKey { get; set; }
public string AttributeLabel { get; set; }
public string UIHintDisplay { get; set; } // Friendly display name
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
public bool IsCritical { get; set; }
public string? AlertMessage { get; set; }
public bool IsActive { get; set; }
public SymptomCategory Category { get; set; }
public string CategoryDisplay { get; set; } // Friendly display name
Expand Down
3 changes: 3 additions & 0 deletions SnakeAid.Service/Implements/SnakebiteIncidentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,9 @@ public async Task<ApiResponse<UpdateSymptomReportResponse>> UpdateSymptomReportA
severityLevel += modifierSymptomScores.Sum();
}

if (severityLevel > 100)
severityLevel = 100;

// Update symptom report and severity level
var jsonOptions = new System.Text.Json.JsonSerializerOptions
{
Expand Down
20 changes: 20 additions & 0 deletions SnakeAid.Service/Implements/SymptomConfigService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,14 @@ public async Task<ApiResponse<SymptomConfigResponse>> CreateSymptomConfigAsync(C

var symptomConfig = new SymptomConfig
{
GroupName = request.GroupName,
AttributeKey = request.AttributeKey,
AttributeLabel = request.AttributeLabel,
DisplayOrder = request.DisplayOrder,
Name = request.Name,
Description = request.Description,
IsCritical = request.IsCritical,
AlertMessage = request.AlertMessage,
IsActive = request.IsActive,
Category = request.Category,
TimeScoresJson = request.TimeScoreList != null && request.TimeScoreList.Any()
Expand Down Expand Up @@ -128,10 +131,12 @@ public async Task<ApiResponse<PagedData<SymptomConfigResponse>>> FilterSymptomCo

// Build combined predicate
predicate = sc =>
(string.IsNullOrWhiteSpace(request.GroupName) || sc.GroupName.Contains(request.GroupName)) &&
(string.IsNullOrWhiteSpace(request.AttributeKey) || sc.AttributeKey.Contains(request.AttributeKey)) &&
(string.IsNullOrWhiteSpace(request.Name) || sc.Name.Contains(request.Name)) &&
(!request.Category.HasValue || sc.Category == request.Category.Value) &&
(!request.IsActive.HasValue || sc.IsActive == request.IsActive.Value) &&
(!request.IsCritical.HasValue || sc.IsCritical == request.IsCritical.Value) &&
(!request.VenomTypeId.HasValue || sc.VenomTypeId == request.VenomTypeId.Value);

// Get paginated data
Expand Down Expand Up @@ -197,6 +202,11 @@ public async Task<ApiResponse<SymptomConfigResponse>> UpdateSymptomConfigAsync(i
}

// Update only provided fields
if (!string.IsNullOrWhiteSpace(request.GroupName))
{
symptomConfig.GroupName = request.GroupName;
}

if (!string.IsNullOrWhiteSpace(request.AttributeKey))
{
symptomConfig.AttributeKey = request.AttributeKey;
Expand All @@ -223,6 +233,16 @@ public async Task<ApiResponse<SymptomConfigResponse>> UpdateSymptomConfigAsync(i
symptomConfig.Description = request.Description;
}

if (request.IsCritical.HasValue)
{
symptomConfig.IsCritical = request.IsCritical.Value;
}

if (request.AlertMessage != null)
{
symptomConfig.AlertMessage = request.AlertMessage;
}

if (request.IsActive.HasValue)
{
symptomConfig.IsActive = request.IsActive.Value;
Expand Down