using ClosedXML.Excel;
using SportsDivision.Application.DTOs;
using SportsDivision.Application.DTOs.ReportDtos;
namespace SportsDivision.Web.Reports;
///
/// Builds Microsoft Excel (.xlsx) workbooks for each report. One worksheet per
/// report, with a title, subtitle (tournament name), a styled header row, and
/// flattened data rows for the nested reports.
///
public static class ReportExcelExporter
{
public const string MimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
private static int WriteHeader(IXLWorksheet ws, string title, string subtitle, string[] columns)
{
ws.Cell(1, 1).Value = title;
ws.Cell(1, 1).Style.Font.Bold = true;
ws.Cell(1, 1).Style.Font.FontSize = 14;
ws.Range(1, 1, 1, Math.Max(1, columns.Length)).Merge();
ws.Cell(2, 1).Value = subtitle;
ws.Cell(2, 1).Style.Font.Italic = true;
ws.Range(2, 1, 2, Math.Max(1, columns.Length)).Merge();
const int headerRow = 4;
for (int i = 0; i < columns.Length; i++)
{
var c = ws.Cell(headerRow, i + 1);
c.Value = columns[i];
c.Style.Font.Bold = true;
c.Style.Fill.BackgroundColor = XLColor.FromHtml("#003366");
c.Style.Font.FontColor = XLColor.White;
}
return headerRow + 1;
}
private static byte[] Finish(XLWorkbook wb, params IXLWorksheet[] sheets)
{
foreach (var ws in sheets)
{
ws.SheetView.FreezeRows(4);
ws.Columns().AdjustToContents();
}
using var ms = new MemoryStream();
wb.SaveAs(ms);
return ms.ToArray();
}
// Missing values stay blank so the column keeps a uniform numeric type —
// writing "-" would make Excel treat the column as text for sorting/aggregation.
private static void SetInt(IXLCell cell, int? v) { if (v.HasValue) cell.Value = v.Value; }
private static void SetDec(IXLCell cell, decimal? v) { if (v.HasValue) cell.Value = v.Value; }
public static byte[] PopularEvents(IEnumerable data, string tournamentName)
{
using var wb = new XLWorkbook();
var ws = wb.AddWorksheet("Popular Events");
int r = WriteHeader(ws, "Popular Events Report", tournamentName,
new[] { "Event", "Category", "Total", "Male", "Female" });
foreach (var e in data)
{
ws.Cell(r, 1).Value = e.EventName;
ws.Cell(r, 2).Value = e.Category;
ws.Cell(r, 3).Value = e.RegistrationCount;
ws.Cell(r, 4).Value = e.MaleCount;
ws.Cell(r, 5).Value = e.FemaleCount;
r++;
}
return Finish(wb, ws);
}
public static byte[] RegistrationByGender(IEnumerable data, string tournamentName)
{
using var wb = new XLWorkbook();
var ws = wb.AddWorksheet("Registration by Gender");
int r = WriteHeader(ws, "Registration by Gender", tournamentName,
new[] { "Event", "Level", "Male", "Female", "Total" });
foreach (var e in data)
{
ws.Cell(r, 1).Value = e.EventName;
ws.Cell(r, 2).Value = e.EventLevelName;
ws.Cell(r, 3).Value = e.MaleCount;
ws.Cell(r, 4).Value = e.FemaleCount;
ws.Cell(r, 5).Value = e.TotalCount;
r++;
}
return Finish(wb, ws);
}
public static byte[] EventSchoolReport(IEnumerable data, string tournamentName)
{
using var wb = new XLWorkbook();
var ws = wb.AddWorksheet("Event School Report");
int r = WriteHeader(ws, "Event School Report", tournamentName,
new[] { "Event", "Level", "School", "Student", "Performance", "Placement", "Points" });
foreach (var ev in data)
foreach (var school in ev.Schools)
foreach (var s in school.Students)
{
ws.Cell(r, 1).Value = ev.EventName;
ws.Cell(r, 2).Value = ev.EventLevelName;
ws.Cell(r, 3).Value = school.SchoolName;
ws.Cell(r, 4).Value = s.StudentName;
SetDec(ws.Cell(r, 5), s.RawPerformance);
SetInt(ws.Cell(r, 6), s.Placement);
ws.Cell(r, 7).Value = s.PlacementPoints;
r++;
}
return Finish(wb, ws);
}
public static byte[] StudentsBySchool(IEnumerable data, string tournamentName)
{
using var wb = new XLWorkbook();
var ws = wb.AddWorksheet("Students by School");
int r = WriteHeader(ws, "Students by School", tournamentName,
new[] { "School", "Zone", "Student", "Sex", "Events" });
foreach (var school in data)
foreach (var s in school.Students)
{
ws.Cell(r, 1).Value = school.SchoolName;
ws.Cell(r, 2).Value = school.ZoneName;
ws.Cell(r, 3).Value = s.StudentName;
ws.Cell(r, 4).Value = s.Sex;
ws.Cell(r, 5).Value = string.Join(", ", s.Events);
r++;
}
return Finish(wb, ws);
}
public static byte[] ScoresByEvent(IEnumerable data, string tournamentName)
{
using var wb = new XLWorkbook();
var ws = wb.AddWorksheet("Scores by Event");
int r = WriteHeader(ws, "Scores by Event", tournamentName,
new[] { "Event", "Level", "Category", "Placement", "Student", "School", "Performance", "WA Points", "Placement Points" });
foreach (var ev in data)
foreach (var s in ev.Scores)
{
ws.Cell(r, 1).Value = ev.EventName;
ws.Cell(r, 2).Value = ev.EventLevelName;
ws.Cell(r, 3).Value = ev.Category;
SetInt(ws.Cell(r, 4), s.Placement);
ws.Cell(r, 5).Value = s.StudentName;
ws.Cell(r, 6).Value = s.SchoolName;
ws.Cell(r, 7).Value = s.RawPerformance;
ws.Cell(r, 8).Value = s.CalculatedPoints;
ws.Cell(r, 9).Value = s.PlacementPoints;
r++;
}
return Finish(wb, ws);
}
public static byte[] StudentPoints(IEnumerable data, string tournamentName)
{
using var wb = new XLWorkbook();
var ws = wb.AddWorksheet("Student Points");
int r = WriteHeader(ws, "Student Points", tournamentName,
new[] { "Student", "School", "Sex", "Total Placement Points", "Events" });
foreach (var s in data)
{
ws.Cell(r, 1).Value = s.StudentName;
ws.Cell(r, 2).Value = s.SchoolName;
ws.Cell(r, 3).Value = s.Sex;
ws.Cell(r, 4).Value = s.TotalPlacementPoints;
ws.Cell(r, 5).Value = s.EventCount;
r++;
}
return Finish(wb, ws);
}
public static byte[] SchoolStandings(IEnumerable data, string tournamentName)
{
using var wb = new XLWorkbook();
var ws = wb.AddWorksheet("School Standings");
int r = WriteHeader(ws, "School Standings", tournamentName,
new[] { "Rank", "School", "Short Name", "Total Points", "1st Place", "2nd Place", "3rd Place" });
int rank = 1;
foreach (var s in data)
{
ws.Cell(r, 1).Value = rank++;
ws.Cell(r, 2).Value = s.SchoolName;
ws.Cell(r, 3).Value = s.ShortName ?? string.Empty;
ws.Cell(r, 4).Value = s.TotalPoints;
ws.Cell(r, 5).Value = s.FirstPlaceCount;
ws.Cell(r, 6).Value = s.SecondPlaceCount;
ws.Cell(r, 7).Value = s.ThirdPlaceCount;
r++;
}
return Finish(wb, ws);
}
}