1c7e704ad3
Implements a flexible validation framework for student rankings, team assignments, and team composition with administrator-configurable rules and thresholds. Validation System: - Reflection-based rule discovery eliminates manual registration - Base classes (RequiredEventTypeRuleBase, EventCountThresholdRuleBase) reduce code duplication by ~250 lines - 12 validation rules covering event requirements, counts, and team constraints - Configurable severity levels (Warning/Error) per rule type - ValidationService with caching for optimal performance - Rules apply contextually (StudentRanking, StudentAssignment, TeamComposition) Configuration & Admin UI: - ValidationSettings admin page for editing thresholds and severity levels - ChapterSettings admin page for editing chapter information - Settings stored in Data/appsettings.json for runtime configuration - JSON config works in both development and production environments - Auto-creates Data directory and config template on first run User Experience: - ValidationWarnings component displays inline warnings/errors - Integrated in Event Ranking, Registration, and Team Assignment pages - Color-coded severity indicators (warning yellow, error red) - Includes "Too Many Regional Events" rule (max 3 recommended) Technical Improvements: - Template Method pattern for rule base classes - Singleton rule instances with lazy initialization - Configuration loaded via IConfiguration with fallback to defaults - Safe JSON updates preserve other appsettings sections
145 lines
4.8 KiB
C#
145 lines
4.8 KiB
C#
using Core.Entities;
|
|
using MudBlazor;
|
|
|
|
namespace WebApp.Models
|
|
{
|
|
public static class AppIcons
|
|
{
|
|
public static string EventRank = Icons.Material.Filled.AddChart;
|
|
public static string Teams = Icons.Material.Filled.Group;
|
|
public static string Student = Icons.Material.Filled.Person;
|
|
public static string TeamAssignment = Icons.Material.Filled.GroupAdd;
|
|
public static string Events = Icons.Material.Filled.Dashboard;
|
|
public static string Scheduler = Icons.Material.Filled.CalendarMonth;
|
|
public static string Captain = Icons.Material.Filled.Star;
|
|
public static string LevelOfEffortIcon(int? loe)
|
|
{
|
|
|
|
return loe switch
|
|
{
|
|
1 => "1",
|
|
2 => "2",
|
|
3 => "3",
|
|
_ => Icons.Material.Filled.QuestionMark
|
|
};
|
|
}
|
|
|
|
/*https://unicodeplus.com/search*/
|
|
public static string OnSiteActivity = "ⓐ";
|
|
public static string RegionalEvent = "ⓡ";
|
|
public static string IndividualEvent = "ⓘ";
|
|
public static string PresubmissionEvent = "ⓟ";
|
|
public static string PresentationEvent = "";
|
|
public static string QuestionMark = "❔";
|
|
|
|
// Tooltip mapping for icon unicode characters
|
|
public static Dictionary<string, string> IconTooltips => new()
|
|
{
|
|
{ OnSiteActivity, "On-Site Activity" },
|
|
{ RegionalEvent, "Regional Event" },
|
|
{ IndividualEvent, "Individual Event" },
|
|
{ PresubmissionEvent, "Presubmission" },
|
|
{ PresentationEvent, "Presentation/Interview" },
|
|
{ "①", "Level of Effort: 1" },
|
|
{ "②", "Level of Effort: 2" },
|
|
{ "③", "Level of Effort: 3" }
|
|
};
|
|
|
|
public static string EventEffort(EventDefinition eventDefinition)
|
|
{
|
|
return LevelOfEffortIcon(eventDefinition.LevelOfEffort);
|
|
}
|
|
|
|
public static string EventAttributes(EventDefinition eventDefinition)
|
|
{
|
|
var v = new List<string>();
|
|
|
|
if (eventDefinition.EventFormat == EventFormat.Individual)
|
|
v.Add(IndividualEvent);
|
|
if (eventDefinition.RegionalEvent)
|
|
v.Add(RegionalEvent);
|
|
if (eventDefinition.InterviewOrPresentation)
|
|
v.Add(PresentationEvent);
|
|
if (eventDefinition.Presubmission)
|
|
v.Add(PresubmissionEvent);
|
|
if (eventDefinition.OnSiteActivity)
|
|
v.Add(OnSiteActivity);
|
|
|
|
return string.Join(" ", v);
|
|
}
|
|
|
|
public static string OfficerRoleIcon(OfficerRole officerRole)
|
|
{
|
|
return officerRole switch
|
|
{
|
|
OfficerRole.President => Icons.Material.Filled.Gavel,
|
|
OfficerRole.VicePresident => Icons.Material.Filled.StarBorderPurple500,
|
|
OfficerRole.Secretary => Icons.Material.Filled.Draw,
|
|
OfficerRole.Treasurer => Icons.Material.Filled.Key,
|
|
OfficerRole.Reporter => Icons.Material.Filled.Mic,
|
|
OfficerRole.SergeantAtArms => Icons.Material.Filled.Handshake,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(officerRole), officerRole, null)
|
|
};
|
|
}
|
|
|
|
public static string RankedEventColor(int rank)
|
|
{
|
|
return rank switch
|
|
{
|
|
1 => "#dd7e6b",
|
|
2 => "#ea9999",
|
|
3 => "#f9cb9c",
|
|
4 => "#ffe599",
|
|
5 => "#fff2cc",
|
|
6 => "#fffaea",
|
|
7 => "#fffefa",
|
|
8 => "#fffefc",
|
|
9 => "#fffffd",
|
|
10 => "#fffffe",
|
|
_ => "#ddd"
|
|
};
|
|
}
|
|
|
|
public static string GetOrdinal(int num)
|
|
{
|
|
if (num <= 0) return num.ToString();
|
|
|
|
switch (num % 100)
|
|
{
|
|
case 11:
|
|
case 12:
|
|
case 13:
|
|
return num + "th";
|
|
}
|
|
|
|
switch (num % 10)
|
|
{
|
|
case 1:
|
|
return num + "st";
|
|
case 2:
|
|
return num + "nd";
|
|
case 3:
|
|
return num + "rd";
|
|
default:
|
|
return num + "th";
|
|
}
|
|
}
|
|
|
|
public static string GetOrdinalSuperscript(int number)
|
|
{
|
|
var suffix = number switch
|
|
{
|
|
11 or 12 or 13 => "th",
|
|
_ => (number % 10) switch
|
|
{
|
|
1 => "st",
|
|
2 => "nd",
|
|
3 => "rd",
|
|
_ => "th"
|
|
}
|
|
};
|
|
return $"{number}<sup>{suffix}</sup>";
|
|
}
|
|
}
|
|
}
|