feat: stitch page-printer tables into one roster and add chapter team-count tokens

When New page per record is off, a single-table template shares one header across records. Event and team pages can print regional/state team counts and nationals eligibility.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-02 21:37:49 -04:00
co-authored by Cursor
parent 3712dba974
commit 1337d9833d
6 changed files with 327 additions and 19 deletions
+73 -16
View File
@@ -74,16 +74,16 @@ public class NotePrintService : INotePrintService
rows.Add((student, ImportedTokens(parsed, request.ImportedFieldCatalog)));
}
return
[
.. rows.Select(row => MergePage(
return FinishPreview(
request,
template,
chapter,
rows.Count == 1 ? "1 student" : $"{rows.Count} students",
rows.Select(row => new RecordMerge(
row.Student.LastNameFirstName,
template,
row.Imported,
StudentTokens(row.Student),
chapter,
StudentHtmlFragments(row.Student)))
];
StudentHtmlFragments(row.Student))));
}
private async Task<IReadOnlyList<NotePrintPage>> PreviewTeamsAsync(
@@ -110,10 +110,12 @@ public class NotePrintService : INotePrintService
.ThenBy(t => t.Identifier)
.ToListAsync(cancellationToken);
return
[
.. teams.Select(team => MergePage(team.ToString(), template, null, TeamTokens(team), chapter))
];
return FinishPreview(
request,
template,
chapter,
teams.Count == 1 ? "1 team" : $"{teams.Count} teams",
teams.Select(team => new RecordMerge(team.ToString(), null, TeamTokens(team), null)));
}
private async Task<IReadOnlyList<NotePrintPage>> PreviewEventsAsync(
@@ -153,15 +155,67 @@ public class NotePrintService : INotePrintService
.GroupBy(r => r.EventDefinition.Id)
.ToDictionary(g => g.Key, g => g.ToList());
return
[
.. events.Select(evt => MergePage(
return FinishPreview(
request,
template,
chapter,
events.Count == 1 ? "1 event" : $"{events.Count} events",
events.Select(evt => new RecordMerge(
evt.Name,
template,
null,
EventTokens(evt),
EventHtmlFragments(rankingsByEventId.GetValueOrDefault(evt.Id)))));
}
private readonly record struct RecordMerge(
string DisplayName,
Dictionary<string, string?>? Imported,
Dictionary<string, string?> Entity,
IReadOnlyDictionary<string, string>? HtmlFragments);
private static IReadOnlyList<NotePrintPage> FinishPreview(
NotePrintRequest request,
string template,
Dictionary<string, string?> chapter,
string combinedDisplayName,
IEnumerable<RecordMerge> records)
{
var list = records.ToList();
if (list.Count == 0)
return [];
if (!request.Filters.NewPagePerRecord
&& MarkdownTableStencil.TryParse(template, out var stencil)
&& stencil is not null)
{
var chapterMap = PrintTokenMap.Build(null, null, chapter);
var prefix = NoteTemplateMerger.Merge(stencil.Prefix, chapterMap);
var suffix = NoteTemplateMerger.Merge(stencil.Suffix, chapterMap);
var bodies = list.Select(record =>
NoteTemplateMerger.Merge(
stencil.Body,
PrintTokenMap.Build(record.Imported, record.Entity, chapter)));
return
[
new NotePrintPage
{
DisplayName = combinedDisplayName,
Html = NoteTemplateMerger.ApplyLayout(
MarkdownHelper.ToHtml(stencil.Stitch(prefix, bodies, suffix)))
}
];
}
return
[
.. list.Select(record => MergePage(
record.DisplayName,
template,
record.Imported,
record.Entity,
chapter,
EventHtmlFragments(rankingsByEventId.GetValueOrDefault(evt.Id))))
record.HtmlFragments))
];
}
@@ -261,7 +315,10 @@ public class NotePrintService : INotePrintService
{
["EventFormat"] = evt?.EventFormat.ToString(),
["TeamSize"] = evt?.TeamSize,
["NationalEligibility"] = evt?.Eligibility,
["Eligibility"] = evt?.Eligibility,
["RegionalTeamCount"] = evt?.ChapterEligibilityCountRegionals.ToString(),
["StateTeamCount"] = evt?.ChapterEligibilityCountState.ToString(),
["Description"] = evt?.Description,
["Theme"] = evt?.Theme,
["EventAttributes"] = EventAttributeMarks.For(evt)