mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 01:47:41 +00:00
Initial report
This commit is contained in:
@@ -621,4 +621,48 @@ public class StudentController : BaseController
|
||||
Message = updated ? "Changes applied successfully." : "No changes were applied."
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{idStudent:guid}/progress-report")]
|
||||
[Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.Paraeducator},{UserRoles.ProgramAdmin}")]
|
||||
[ProducesResponseType(typeof(ResponseResult<string>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ResponseResult<string>), StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ResponseResult<string>>> GetProgressReport(
|
||||
Guid idStudent, [FromQuery] DateTime fromDate, [FromQuery] DateTime toDate, [FromQuery] string? goalIds = null)
|
||||
{
|
||||
var (userId, email, programId, role, error) = GetProgramUserFromClaims();
|
||||
if (error is not null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
var students = await _studentRepository.GetMyStudentsAsync(userId, programId, role);
|
||||
|
||||
if (!students.Select(s => s.StudentId).Contains(idStudent))
|
||||
{
|
||||
return NotFound(new ResponseResult<string>
|
||||
{
|
||||
Success = false,
|
||||
Message = "Student not found."
|
||||
});
|
||||
}
|
||||
|
||||
var report = await _studentRepository.GetProgressReportAsync(idStudent, fromDate, toDate, goalIds);
|
||||
if (report is null)
|
||||
{
|
||||
return NotFound(new ResponseResult<string>
|
||||
{
|
||||
Success = false,
|
||||
Message = "Student not found."
|
||||
});
|
||||
}
|
||||
|
||||
var markdown = ProgressReportBuilder.BuildMarkdown(report, fromDate, toDate);
|
||||
|
||||
return Ok(new ResponseResult<string>
|
||||
{
|
||||
Success = true,
|
||||
Message = "Progress report generated successfully.",
|
||||
Data = markdown
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace WinStudentGoalTracker.DataAccess;
|
||||
|
||||
public class dbProgressReportGoalRow
|
||||
{
|
||||
public required Guid GoalId { get; set; }
|
||||
public string? Category { get; set; }
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace WinStudentGoalTracker.DataAccess;
|
||||
|
||||
public class dbProgressReportRow
|
||||
{
|
||||
public required Guid GoalId { get; set; }
|
||||
public required Guid ProgressEventId { get; set; }
|
||||
public string? Content { get; set; }
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
public string? BenchmarkNames { get; set; }
|
||||
}
|
||||
@@ -351,4 +351,54 @@ public class StudentRepository
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
// *****************************************************************
|
||||
// Returns a full progress report for a student within the given
|
||||
// date range. Calls sp_ProgressReport_GetByStudentId which returns
|
||||
// two result sets: goals and progress events with benchmark names.
|
||||
// *****************************************************************
|
||||
public async Task<StudentProgressReportResponse?> GetProgressReportAsync(
|
||||
Guid studentId, DateTime fromDate, DateTime toDate, string? goalIds = null)
|
||||
{
|
||||
var student = await GetByIdAsync(studentId);
|
||||
if (student is null) return null;
|
||||
|
||||
using var db = Connection;
|
||||
using var multi = await db.QueryMultipleAsync(
|
||||
"sp_ProgressReport_GetByStudentId",
|
||||
new
|
||||
{
|
||||
p_id_student = studentId.ToString(),
|
||||
p_from_date = fromDate.ToString("yyyy-MM-dd"),
|
||||
p_to_date = toDate.ToString("yyyy-MM-dd"),
|
||||
p_goal_ids = goalIds
|
||||
},
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
var goalRows = (await multi.ReadAsync<dbProgressReportGoalRow>()).ToList();
|
||||
var eventRows = (await multi.ReadAsync<dbProgressReportRow>()).ToList();
|
||||
|
||||
var eventsByGoal = eventRows.GroupBy(e => e.GoalId)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
return new StudentProgressReportResponse
|
||||
{
|
||||
StudentIdentifier = student.Identifier,
|
||||
Goals = goalRows.Select(g => new ProgressReportGoal
|
||||
{
|
||||
GoalId = g.GoalId,
|
||||
Category = g.Category,
|
||||
Description = g.Description,
|
||||
ProgressEvents = eventsByGoal.TryGetValue(g.GoalId, out var events)
|
||||
? events.Select(e => new ProgressReportEvent
|
||||
{
|
||||
ProgressEventId = e.ProgressEventId,
|
||||
Content = e.Content,
|
||||
CreatedAt = e.CreatedAt,
|
||||
BenchmarkNames = e.BenchmarkNames
|
||||
}).ToList()
|
||||
: []
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace WinStudentGoalTracker.Models;
|
||||
|
||||
public class StudentProgressReportResponse
|
||||
{
|
||||
public string? StudentIdentifier { get; set; }
|
||||
public List<ProgressReportGoal> Goals { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ProgressReportGoal
|
||||
{
|
||||
public Guid GoalId { get; set; }
|
||||
public string? Category { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public List<ProgressReportEvent> ProgressEvents { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ProgressReportEvent
|
||||
{
|
||||
public Guid ProgressEventId { get; set; }
|
||||
public string? Content { get; set; }
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
public string? BenchmarkNames { get; set; }
|
||||
}
|
||||
@@ -5,6 +5,7 @@ public class StudentResponse
|
||||
public Guid StudentId { get; set; }
|
||||
public string? Identifier { get; set; }
|
||||
public DateTime? NextIepDate { get; set; }
|
||||
public DateTime? FirstEntryDate { get; set; }
|
||||
public DateTime? LastEntryDate { get; set; }
|
||||
public int GoalCount { get; set; }
|
||||
public int ProgressEventCount { get; set; }
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using WinStudentGoalTracker.Models;
|
||||
|
||||
namespace WinStudentGoalTracker.Services;
|
||||
|
||||
public static class ProgressReportBuilder
|
||||
{
|
||||
// *****************************************************************
|
||||
// Builds a markdown document from a StudentProgressReportResponse.
|
||||
// Returns the complete markdown string ready for download.
|
||||
// *****************************************************************
|
||||
public static string BuildMarkdown(StudentProgressReportResponse report, DateTime fromDate, DateTime toDate)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
|
||||
var fromDisplay = fromDate.ToString("MMMM d, yyyy");
|
||||
var toDisplay = toDate.ToString("MMMM d, yyyy");
|
||||
|
||||
lines.Add("# Student Progress Report");
|
||||
lines.Add("");
|
||||
lines.Add($"**Student:** {report.StudentIdentifier}");
|
||||
lines.Add($"**Report Period:** {fromDisplay} – {toDisplay}");
|
||||
lines.Add("");
|
||||
lines.Add("---");
|
||||
|
||||
if (report.Goals.Count == 0)
|
||||
{
|
||||
lines.Add("");
|
||||
lines.Add("*No progress events found in the selected date range.*");
|
||||
return string.Join("\n", lines);
|
||||
}
|
||||
|
||||
var goalIndex = 0;
|
||||
foreach (var goal in report.Goals)
|
||||
{
|
||||
goalIndex++;
|
||||
lines.Add("");
|
||||
lines.Add($"## {goalIndex}. {goal.Category}");
|
||||
if (!string.IsNullOrWhiteSpace(goal.Description))
|
||||
{
|
||||
lines.Add("");
|
||||
lines.Add(goal.Description);
|
||||
}
|
||||
lines.Add("");
|
||||
|
||||
foreach (var ev in goal.ProgressEvents)
|
||||
{
|
||||
var eventDate = ev.CreatedAt?.ToString("MMMM d, yyyy") ?? "Unknown date";
|
||||
lines.Add($"### {eventDate}");
|
||||
lines.Add("");
|
||||
lines.Add(ev.Content ?? "");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ev.BenchmarkNames))
|
||||
{
|
||||
lines.Add("");
|
||||
lines.Add($"**Benchmarks:** {ev.BenchmarkNames}");
|
||||
}
|
||||
lines.Add("");
|
||||
}
|
||||
|
||||
lines.Add("---");
|
||||
}
|
||||
|
||||
return string.Join("\n", lines);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user