|
- using Campaign_Tracker.Server.Municipalities;
-
- namespace Campaign_Tracker.Server.ElectionCycles;
-
- public interface IElectionCycleKanbanReadModel
- {
- Task<ElectionCycleKanbanBoard> GetAsync(
- CancellationToken cancellationToken = default);
- }
-
- public sealed class ElectionCycleKanbanReadModel : IElectionCycleKanbanReadModel
- {
- public const string UnassignedCycleId = "__unassigned__";
- public const string UnassignedCycleName = "Unassigned";
- private readonly IMunicipalityProfileRepository _profiles;
- private readonly IElectionCycleJobRepository _jobs;
-
- public ElectionCycleKanbanReadModel(
- IMunicipalityProfileRepository profiles,
- IElectionCycleJobRepository jobs)
- {
- _profiles = profiles;
- _jobs = jobs;
- }
-
- public async Task<ElectionCycleKanbanBoard> GetAsync(
- CancellationToken cancellationToken = default)
- {
- var profiles = await _profiles.GetAllAsync(cancellationToken);
- var activeJobs = (await _jobs.GetAllAsync(cancellationToken))
- .Where(job => job.IsActive)
- .ToArray();
-
- var jobsByJCode = activeJobs
- .GroupBy(job => job.JCode, StringComparer.OrdinalIgnoreCase)
- .ToDictionary(group => group.Key, group => group.ToArray(), StringComparer.OrdinalIgnoreCase);
-
- var cardsByCycle = new Dictionary<string, List<ElectionCycleKanbanCard>>(
- StringComparer.OrdinalIgnoreCase);
- var lanesByCycle = activeJobs
- .GroupBy(job => job.CycleId, StringComparer.OrdinalIgnoreCase)
- .ToDictionary(
- group => group.Key,
- group => new ElectionCycleLaneHeader(
- group.Key,
- group.Select(job => job.CycleName)
- .OrderBy(name => name, StringComparer.OrdinalIgnoreCase)
- .First()),
- StringComparer.OrdinalIgnoreCase);
-
- var unassignedCards = new List<ElectionCycleKanbanCard>();
- var matchedJobIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
- foreach (var profile in profiles)
- {
- if (!jobsByJCode.TryGetValue(profile.Profile.JCode, out var profileJobs))
- {
- unassignedCards.Add(BuildUnassignedCard(profile));
- continue;
- }
-
- foreach (var job in profileJobs.OrderBy(job => job.CycleName, StringComparer.OrdinalIgnoreCase))
- {
- if (!cardsByCycle.TryGetValue(job.CycleId, out var cycleCards))
- {
- cycleCards = [];
- cardsByCycle[job.CycleId] = cycleCards;
- }
-
- cycleCards.Add(BuildAssignedCard(profile, job));
- matchedJobIds.Add(job.JobId);
- }
- }
-
- foreach (var orphan in activeJobs.Where(job => !matchedJobIds.Contains(job.JobId)))
- {
- if (!cardsByCycle.TryGetValue(orphan.CycleId, out var cycleCards))
- {
- cycleCards = [];
- cardsByCycle[orphan.CycleId] = cycleCards;
- }
-
- cycleCards.Add(BuildOrphanedCard(orphan));
- }
-
- var lanes = cardsByCycle
- .OrderBy(pair => lanesByCycle[pair.Key].CycleName, StringComparer.OrdinalIgnoreCase)
- .Select(pair => new ElectionCycleKanbanLane(
- pair.Key,
- lanesByCycle[pair.Key].CycleName,
- SortCards(pair.Value)))
- .Append(new ElectionCycleKanbanLane(
- UnassignedCycleId,
- UnassignedCycleName,
- SortCards(unassignedCards)))
- .ToArray();
-
- return new ElectionCycleKanbanBoard(lanes);
- }
-
- private static IReadOnlyList<ElectionCycleKanbanCard> SortCards(
- IReadOnlyList<ElectionCycleKanbanCard> cards)
- => cards
- .OrderBy(card => card.MunicipalityName, StringComparer.OrdinalIgnoreCase)
- .ThenBy(card => card.JCode, StringComparer.OrdinalIgnoreCase)
- .ToArray();
-
- private static ElectionCycleKanbanCard BuildAssignedCard(
- MunicipalityProfileView profile,
- ElectionCycleJobAssignment job)
- => new(
- CardId: job.JobId,
- MunicipalityName: MunicipalityName(profile),
- JCode: profile.Profile.JCode,
- CycleId: job.CycleId,
- CycleName: job.CycleName,
- CycleJobStatus: job.Status,
- LegacyJoinKey: profile.Profile.JCode,
- QuickOpenHref: $"/election-cycles/jobs/{job.JobId}");
-
- private static ElectionCycleKanbanCard BuildOrphanedCard(
- ElectionCycleJobAssignment job)
- => new(
- CardId: job.JobId,
- MunicipalityName: $"(unmapped JCode {job.JCode})",
- JCode: job.JCode,
- CycleId: job.CycleId,
- CycleName: job.CycleName,
- CycleJobStatus: job.Status,
- LegacyJoinKey: job.JCode,
- QuickOpenHref: $"/election-cycles/jobs/{job.JobId}");
-
- private static ElectionCycleKanbanCard BuildUnassignedCard(
- MunicipalityProfileView profile)
- => new(
- CardId: $"unassigned-{profile.Profile.JCode}",
- MunicipalityName: MunicipalityName(profile),
- JCode: profile.Profile.JCode,
- CycleId: UnassignedCycleId,
- CycleName: UnassignedCycleName,
- CycleJobStatus: UnassignedCycleName,
- LegacyJoinKey: profile.Profile.JCode,
- QuickOpenHref: $"/election-cycles/jobs/new?jCode={Uri.EscapeDataString(profile.Profile.JCode)}");
-
- private static string MunicipalityName(MunicipalityProfileView profile)
- => profile.Profile.DisplayName
- ?? profile.LegacyName
- ?? profile.Profile.JCode;
-
- private sealed record ElectionCycleLaneHeader(string CycleId, string CycleName);
- }
-
- public sealed record ElectionCycleKanbanBoard(
- IReadOnlyList<ElectionCycleKanbanLane> Lanes);
-
- public sealed record ElectionCycleKanbanLane(
- string CycleId,
- string CycleName,
- IReadOnlyList<ElectionCycleKanbanCard> Cards);
-
- public sealed record ElectionCycleKanbanCard(
- string CardId,
- string MunicipalityName,
- string JCode,
- string CycleId,
- string CycleName,
- string CycleJobStatus,
- string LegacyJoinKey,
- string QuickOpenHref);
|