feat: medication reminders, follow-up/visit separation, health record page
Backend: - MedicationService: today-summary with missed detection (local time) - FollowUpService: doctor-initiated follow-ups filter, AddAsync supports Notes - FollowUpController: type query param (followup/recheck) - MedicationController: today-summary endpoint - Auth: UpdateProfileRequest→class, StentDate/StentType, soft-delete fix Patient frontend: - HomePage: date display, medication reminder cards with missed status - MedicationListPage: beautified with delete button, slot preview - MedicationDetailPage: redesigned with progress bars, new CSS - ProfilePage: beautified menu icons, health record link - HealthRecordPage: new page with indicators, history, meds, reports - ServicesHub: added doctor-visit card - VisitListPage: doctor-initiated follow-ups view - EditProfilePage: removed height/weight, added stent fields - Fixed getProfile field mappings (nickname, height, weight, stent) Doctor frontend: - Layout: added 随访管理 sidebar item with SVG icon - FollowUpListPage: recheck-only filter, complete/delete buttons, collapsed completed - VisitListPage/EditPage: doctor follow-up management - PatientListPage: added stentType column - Dashboard: fixed pending reports endpoint - ReportListPage/DetailPage: fixed uploadedAt field - ChatPage: SignalR real-time, dynamic hostname
This commit is contained in:
@@ -14,7 +14,18 @@ public record UserProfileResponse(
|
||||
List<string>? MedicalHistory, DateOnly? StentDate, string? StentType,
|
||||
string? Department, string? Title, List<string>? Specialty, string? Introduction);
|
||||
|
||||
public record UpdateProfileRequest(
|
||||
string? Name, string? Gender, DateOnly? Birthday,
|
||||
decimal? HeightCm, decimal? WeightKg, List<string>? MedicalHistory,
|
||||
string? Department, string? Title, string? Introduction, List<string>? Specialty);
|
||||
public class UpdateProfileRequest
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Gender { get; set; }
|
||||
public DateOnly? Birthday { get; set; }
|
||||
public decimal? HeightCm { get; set; }
|
||||
public decimal? WeightKg { get; set; }
|
||||
public List<string>? MedicalHistory { get; set; }
|
||||
public DateOnly? StentDate { get; set; }
|
||||
public string? StentType { get; set; }
|
||||
public string? Department { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? Introduction { get; set; }
|
||||
public List<string>? Specialty { get; set; }
|
||||
}
|
||||
|
||||
@@ -20,6 +20,13 @@ public class FollowUpService(AppDbContext db)
|
||||
.OrderBy(f => f.ScheduledAt)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<List<FollowUp>> GetDoctorInitiatedFollowUpsAsync(Guid doctorId)
|
||||
=> await db.FollowUps
|
||||
.Include(f => f.Patient)
|
||||
.Where(f => f.DoctorId == doctorId)
|
||||
.OrderBy(f => f.ScheduledAt)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<FollowUp> AddAsync(Guid patientId, string title, string? description, DateTime scheduledAt, bool reminderEnabled, Guid? doctorId = null, string? notes = null)
|
||||
{
|
||||
var followUp = new FollowUp
|
||||
|
||||
@@ -120,4 +120,53 @@ public class MedicationService(AppDbContext db)
|
||||
await db.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<List<object>> GetTodaySummaryAsync(Guid userId)
|
||||
{
|
||||
var today = DateTime.UtcNow.Date;
|
||||
var medications = await db.Medications
|
||||
.Where(m => m.UserId == userId && m.Status == "active")
|
||||
.OrderBy(m => m.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
var allRecords = await db.MedicationRecords
|
||||
.Where(mr => mr.UserId == userId && mr.CreatedAt.Date == today)
|
||||
.ToListAsync();
|
||||
|
||||
var now = DateTime.Now;
|
||||
return medications.Select(m =>
|
||||
{
|
||||
var slots = m.TimeSlots.Select(slot =>
|
||||
{
|
||||
var record = allRecords.FirstOrDefault(r =>
|
||||
r.MedicationId == m.Id && r.TimeSlot == slot);
|
||||
var taken = record?.IsTaken ?? false;
|
||||
|
||||
// Parse slot time and mark as missed if past due
|
||||
var parts = slot.Split(':');
|
||||
var slotHour = int.Parse(parts[0]);
|
||||
var slotMinute = int.Parse(parts[1]);
|
||||
var slotTime = today.AddHours(slotHour).AddMinutes(slotMinute);
|
||||
var missed = !taken && now > slotTime;
|
||||
|
||||
return new
|
||||
{
|
||||
time = slot,
|
||||
taken,
|
||||
missed,
|
||||
takenAt = record?.TakenAt,
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
return (object)new
|
||||
{
|
||||
m.Id,
|
||||
m.DrugName,
|
||||
m.Dosage,
|
||||
m.Frequency,
|
||||
slots,
|
||||
allTaken = slots.All(s => s.taken),
|
||||
};
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,8 @@ public class AuthController(
|
||||
if (request.HeightCm.HasValue) user.HeightCm = request.HeightCm;
|
||||
if (request.WeightKg.HasValue) user.WeightKg = request.WeightKg;
|
||||
if (request.MedicalHistory != null) user.MedicalHistory = request.MedicalHistory;
|
||||
if (request.StentDate.HasValue) user.StentDate = request.StentDate;
|
||||
if (request.StentType != null) user.StentType = request.StentType;
|
||||
if (request.Department != null) user.Department = request.Department;
|
||||
if (request.Title != null) user.Title = request.Title;
|
||||
if (request.Introduction != null) user.Introduction = request.Introduction;
|
||||
|
||||
@@ -14,11 +14,22 @@ public class FollowUpController(FollowUpService followUpService) : ControllerBas
|
||||
private string Role => User.FindFirstValue(ClaimTypes.Role)!;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetFollowUps()
|
||||
public async Task<IActionResult> GetFollowUps([FromQuery] string? type)
|
||||
{
|
||||
var followUps = Role == "doctor"
|
||||
? await followUpService.GetDoctorFollowUpsAsync(UserId)
|
||||
: await followUpService.GetPatientFollowUpsAsync(UserId);
|
||||
List<HealthManager.Domain.Entities.FollowUp> followUps;
|
||||
|
||||
if (Role == "doctor" && type == "followup")
|
||||
followUps = await followUpService.GetDoctorInitiatedFollowUpsAsync(UserId);
|
||||
else if (Role == "doctor")
|
||||
followUps = await followUpService.GetDoctorFollowUpsAsync(UserId);
|
||||
else
|
||||
{
|
||||
followUps = await followUpService.GetPatientFollowUpsAsync(UserId);
|
||||
if (type == "followup")
|
||||
followUps = followUps.Where(f => f.DoctorId != null).ToList();
|
||||
else if (type == "recheck")
|
||||
followUps = followUps.Where(f => f.DoctorId == null).ToList();
|
||||
}
|
||||
|
||||
return Ok(followUps.Select(f => new
|
||||
{
|
||||
|
||||
@@ -13,6 +13,13 @@ public class MedicationController(MedicationService medicationService) : Control
|
||||
private Guid UserId => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
private string Role => User.FindFirstValue(ClaimTypes.Role)!;
|
||||
|
||||
[HttpGet("today-summary")]
|
||||
public async Task<IActionResult> GetTodaySummary()
|
||||
{
|
||||
var summary = await medicationService.GetTodaySummaryAsync(UserId);
|
||||
return Ok(summary);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetMedications()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user