Add Team functions
This commit is contained in:
@@ -57,7 +57,6 @@ namespace Core.Calculation
|
|||||||
_twoTeams = events;
|
_twoTeams = events;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<EventAssignmentSolution> Solve()
|
public async Task<EventAssignmentSolution> Solve()
|
||||||
{
|
{
|
||||||
Debug.WriteLine(_parameters);
|
Debug.WriteLine(_parameters);
|
||||||
@@ -78,21 +77,25 @@ namespace Core.Calculation
|
|||||||
LimitStudentAssignment(model, x);
|
LimitStudentAssignment(model, x);
|
||||||
|
|
||||||
// set the range for level of effort
|
// set the range for level of effort
|
||||||
var eventEffortCoefficients = GetEventEffortCoefficients(_events);
|
SetLevelOfEffort(model, x);
|
||||||
SetLevelOfEffort(model, x, eventEffortCoefficients);
|
|
||||||
|
|
||||||
// each student should be assigned at least one on site activity eventDefinition
|
// each student should be assigned at least one on site activity
|
||||||
if (_parameters.RequireOnSite)
|
if (_parameters.RequireOnSite)
|
||||||
RequireOnSiteActivity(model, x);
|
RequireOnSiteActivity(model, x);
|
||||||
|
|
||||||
// students should have at maximum one individual eventDefinition
|
|
||||||
LimitIndividualEvent(model, x);
|
|
||||||
|
|
||||||
// students should have at least one regional event
|
// students should have at least one regional event
|
||||||
if (_parameters.RequireRegional)
|
if (_parameters.RequireRegional)
|
||||||
RequireRegionalEvent(model, x);
|
RequireRegionalEvent(model, x);
|
||||||
|
|
||||||
OptimizeStudentEventRankings(model, x);
|
// students should have at maximum one individual event
|
||||||
|
AtMostOneIndividualEvent(model, x);
|
||||||
|
|
||||||
|
//EventHasInterestedStudent(model, x);
|
||||||
|
|
||||||
|
IndividualEventsMustBeRanked(model, x);
|
||||||
|
|
||||||
|
OptimizeStudentEventRankings(model, x);
|
||||||
|
|
||||||
|
|
||||||
Debug.WriteLine("Starting optimization");
|
Debug.WriteLine("Starting optimization");
|
||||||
var solver = new CpSolver();
|
var solver = new CpSolver();
|
||||||
@@ -114,36 +117,34 @@ namespace Core.Calculation
|
|||||||
return eventAssignmentSolution;
|
return eventAssignmentSolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Take the solution and map it back to the entities
|
||||||
private List<Team> GetEventAssignments(BoolVar[,] x, CpSolver solver, CpSolverStatus cpSolverStatus)
|
private List<Team> GetEventAssignments(BoolVar[,] x, CpSolver solver, CpSolverStatus cpSolverStatus)
|
||||||
{
|
{
|
||||||
var eventAssignmentsList = new List<Team>();
|
if (cpSolverStatus is not (CpSolverStatus.Optimal or CpSolverStatus.Feasible))
|
||||||
|
return [];
|
||||||
|
|
||||||
if (cpSolverStatus == CpSolverStatus.Optimal || cpSolverStatus == CpSolverStatus.Feasible)
|
var eventAssignments =
|
||||||
{
|
from e in _allEvents
|
||||||
foreach (var e in _allEvents)
|
let students =
|
||||||
|
from s in _allStudents
|
||||||
|
where solver.BooleanValue(x[e, s])
|
||||||
|
select _students[s]
|
||||||
|
where students.Any()
|
||||||
|
select new Team
|
||||||
{
|
{
|
||||||
var students = new List<Student>();
|
TeamId = _events[e].Name,
|
||||||
foreach (var s in _allStudents)
|
Event = _events[e],
|
||||||
{
|
Students = students.ToList()
|
||||||
if (solver.BooleanValue(x[e, s]))
|
};
|
||||||
{
|
|
||||||
students.Add(_students[s]);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (students.Count > 0)
|
|
||||||
eventAssignmentsList.Add(new Team { TeamId = _events[e].Name, Event = _events[e], Students = students});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return eventAssignmentsList;
|
return eventAssignments.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Maximize student event rankings
|
||||||
private void OptimizeStudentEventRankings(CpModel model, BoolVar[,] x)
|
private void OptimizeStudentEventRankings(CpModel model, BoolVar[,] x)
|
||||||
{
|
{
|
||||||
var maximizePicks = LinearExpr.NewBuilder();
|
var maximizePicks = LinearExpr.NewBuilder();
|
||||||
|
|
||||||
// optimize student event rankings
|
|
||||||
foreach (var s in _allStudents)
|
foreach (var s in _allStudents)
|
||||||
{
|
{
|
||||||
var eventPickCoefficients = GetEventPickCoefficients(_events, _students[s].EventRankings);
|
var eventPickCoefficients = GetEventPickCoefficients(_events, _students[s].EventRankings);
|
||||||
@@ -167,13 +168,13 @@ namespace Core.Calculation
|
|||||||
regionalEvent.Add(x[e, s]);
|
regionalEvent.Add(x[e, s]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (_parameters.RequireRegional)
|
// between 1 and 2 regional events
|
||||||
model.AddLinearConstraint(LinearExpr.Sum(regionalEvent), 1, 2);
|
model.AddLinearConstraint(LinearExpr.Sum(regionalEvent), 1, 2);
|
||||||
regionalEvent.Clear();
|
regionalEvent.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LimitIndividualEvent(CpModel model, BoolVar[,] x)
|
private void AtMostOneIndividualEvent(CpModel model, BoolVar[,] x)
|
||||||
{
|
{
|
||||||
foreach (var s in _allStudents)
|
foreach (var s in _allStudents)
|
||||||
{
|
{
|
||||||
@@ -206,8 +207,30 @@ namespace Core.Calculation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetLevelOfEffort(CpModel model, BoolVar[,] x, long[] eventEffortCoefficients)
|
private void IndividualEventsMustBeRanked(CpModel model, BoolVar[,] x)
|
||||||
{
|
{
|
||||||
|
foreach (var s in _allStudents)
|
||||||
|
{
|
||||||
|
var student = _students[s];
|
||||||
|
foreach (var e in _allEvents)
|
||||||
|
{
|
||||||
|
var evt = _events[e];
|
||||||
|
|
||||||
|
var prohibitVar = new List<IntVar> { x[e, s] };
|
||||||
|
if (evt.EventFormat == EventFormat.Individual
|
||||||
|
&& student.EventRankings.Find(er => er.EventDefinition == evt) == null)
|
||||||
|
model.AddLinearConstraint(LinearExpr.Sum(prohibitVar), 0, 0);
|
||||||
|
prohibitVar.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetLevelOfEffort(CpModel model, BoolVar[,] x)
|
||||||
|
{
|
||||||
|
long[] eventEffortCoefficients = _events.Select(
|
||||||
|
e => e.LevelOfEffort ?? 1L
|
||||||
|
).ToArray();
|
||||||
|
|
||||||
foreach (var s in _allStudents)
|
foreach (var s in _allStudents)
|
||||||
{
|
{
|
||||||
var effortVar = new BoolVar[_allEvents.Length];
|
var effortVar = new BoolVar[_allEvents.Length];
|
||||||
@@ -234,9 +257,9 @@ namespace Core.Calculation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Limit the number of events a student is assigned
|
||||||
private void LimitStudentAssignment(CpModel model, BoolVar[,] x)
|
private void LimitStudentAssignment(CpModel model, BoolVar[,] x)
|
||||||
{
|
{
|
||||||
// Limit the number of events a student is assigned
|
|
||||||
foreach (var s in _allStudents)
|
foreach (var s in _allStudents)
|
||||||
{
|
{
|
||||||
var studentCapacity = new List<IntVar>();
|
var studentCapacity = new List<IntVar>();
|
||||||
@@ -251,6 +274,25 @@ namespace Core.Calculation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void EventHasInterestedStudent(CpModel model, BoolVar[,] x)
|
||||||
|
{
|
||||||
|
foreach (var e in _allEvents)
|
||||||
|
{
|
||||||
|
var evt = _events[e];
|
||||||
|
var studentInterest = new List<IntVar>();
|
||||||
|
|
||||||
|
foreach (var s in _allStudents)
|
||||||
|
{
|
||||||
|
var student = _students[s];
|
||||||
|
if (student.EventRankings.Find(er => er.EventDefinition == evt) != null)
|
||||||
|
studentInterest.Add(x[e, s]);
|
||||||
|
}
|
||||||
|
model.AddLinearConstraint(
|
||||||
|
LinearExpr.Sum(studentInterest), 1, 10);
|
||||||
|
studentInterest.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<EventAssignmentThresholds> AddEventAssignmentThresholds(CpModel model, BoolVar[,] x)
|
private List<EventAssignmentThresholds> AddEventAssignmentThresholds(CpModel model, BoolVar[,] x)
|
||||||
{
|
{
|
||||||
var assignmentThresholdsList = new List<EventAssignmentThresholds>();
|
var assignmentThresholdsList = new List<EventAssignmentThresholds>();
|
||||||
@@ -280,6 +322,9 @@ namespace Core.Calculation
|
|||||||
)
|
)
|
||||||
teamCount = 1;
|
teamCount = 1;
|
||||||
|
|
||||||
|
if (_twoTeams.Contains(evt))
|
||||||
|
teamCount = 2;
|
||||||
|
|
||||||
if (evt.Name == "Tech Bowl")
|
if (evt.Name == "Tech Bowl")
|
||||||
teamCount = 1;
|
teamCount = 1;
|
||||||
|
|
||||||
@@ -292,6 +337,9 @@ namespace Core.Calculation
|
|||||||
if (_droppedEvents != null && _droppedEvents.Contains(evt))
|
if (_droppedEvents != null && _droppedEvents.Contains(evt))
|
||||||
teamCount = 0;
|
teamCount = 0;
|
||||||
|
|
||||||
|
if (evt.EventFormat == EventFormat.Individual)
|
||||||
|
evtMinTeamSize = 0;
|
||||||
|
|
||||||
var lb = evtMinTeamSize * teamCount;
|
var lb = evtMinTeamSize * teamCount;
|
||||||
var ub = Math.Min(evtMaxTeamSize * teamCount, _parameters.TeamSizeLimit * teamCount);
|
var ub = Math.Min(evtMaxTeamSize * teamCount, _parameters.TeamSizeLimit * teamCount);
|
||||||
|
|
||||||
@@ -347,22 +395,11 @@ namespace Core.Calculation
|
|||||||
return
|
return
|
||||||
eventRank == null
|
eventRank == null
|
||||||
? 0L
|
? 0L
|
||||||
|
// TODO: MaxRank can be calculated
|
||||||
: StudentEventRanking.MaxRank - eventRank.Rank; // inverse
|
: StudentEventRanking.MaxRank - eventRank.Rank; // inverse
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private long[] GetEventEffortCoefficients(IEnumerable<EventDefinition> events)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
events.Select(
|
|
||||||
e =>
|
|
||||||
//e.Name == "Tech Bowl"
|
|
||||||
// ? 0 // Tech bowl gets requires no effort?
|
|
||||||
//:
|
|
||||||
e.LevelOfEffort ?? 10L
|
|
||||||
).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SolutionPrinter : CpSolverSolutionCallback
|
public class SolutionPrinter : CpSolverSolutionCallback
|
||||||
{
|
{
|
||||||
private readonly IList<EventDefinition> _events;
|
private readonly IList<EventDefinition> _events;
|
||||||
|
|||||||
@@ -1,41 +1,42 @@
|
|||||||
using Core.Entities;
|
using System.Diagnostics;
|
||||||
|
using Core.Entities;
|
||||||
using Google.OrTools.Sat;
|
using Google.OrTools.Sat;
|
||||||
|
|
||||||
namespace Core.Calculation;
|
namespace Core.Calculation;
|
||||||
public class TeamScheduler
|
public class TeamScheduler
|
||||||
{
|
{
|
||||||
private readonly IList<Student> _studentObjs;
|
private readonly IList<Student> _studentObjects;
|
||||||
private readonly IList<Team> _teamObjs;
|
private readonly IList<Team> _teamObjects;
|
||||||
|
|
||||||
private readonly int[] _students;
|
private readonly int[] _students;
|
||||||
private readonly int[] _teams;
|
private readonly int[] _teams;
|
||||||
private readonly int[] _timeSlots;
|
private readonly int[] _timeSlots;
|
||||||
|
|
||||||
private readonly List<Tuple<int,int>> _scheduleSeparateTeams = new ();
|
private readonly List<Tuple<int,int>> _scheduleSeparateTeams = [];
|
||||||
|
|
||||||
public TeamScheduler(IList<Team> teams, int numTimeSlots)
|
public TeamScheduler(Team[] teams, int numTimeSlots)
|
||||||
{
|
{
|
||||||
_teamObjs = teams;
|
_teamObjects = teams;
|
||||||
_studentObjs = teams.SelectMany(t => t.Students).Distinct().ToList();
|
_studentObjects = teams.SelectMany(t => t.Students).Distinct().ToList();
|
||||||
|
|
||||||
_students = Enumerable.Range(0, _studentObjs.Count).ToArray();
|
_students = Enumerable.Range(0, _studentObjects.Count).ToArray();
|
||||||
_teams = Enumerable.Range(0, _teamObjs.Count).ToArray();
|
_teams = Enumerable.Range(0, _teamObjects.Count).ToArray();
|
||||||
_timeSlots = Enumerable.Range(0, numTimeSlots).ToArray();
|
_timeSlots = Enumerable.Range(0, numTimeSlots).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ScheduleSeparate(Team team1, Team team2)
|
public void ScheduleSeparate(Team team1, Team team2)
|
||||||
{
|
{
|
||||||
var one = _teamObjs.IndexOf(team1);
|
var one = _teamObjects.IndexOf(team1);
|
||||||
var two = _teamObjs.IndexOf(team2);
|
var two = _teamObjects.IndexOf(team2);
|
||||||
_scheduleSeparateTeams.Add(Tuple.Create(one,two));
|
_scheduleSeparateTeams.Add(Tuple.Create(one,two));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TeamScheduler CreateInstance(IList<Team> teams, int numTimeSlots)
|
public static TeamScheduler CreateInstance(Team[] teams, int numTimeSlots)
|
||||||
{
|
{
|
||||||
return new TeamScheduler(teams, numTimeSlots);
|
return new TeamScheduler(teams, numTimeSlots);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<Team>[] Solve()
|
public TeamSchedulerSolution Solve()
|
||||||
{
|
{
|
||||||
// Model.
|
// Model.
|
||||||
var model = new CpModel();
|
var model = new CpModel();
|
||||||
@@ -44,7 +45,7 @@ public class TeamScheduler
|
|||||||
var m = new int[_students.Length,_teams.Length];
|
var m = new int[_students.Length,_teams.Length];
|
||||||
foreach (var i in _students)
|
foreach (var i in _students)
|
||||||
foreach (var t in _teams)
|
foreach (var t in _teams)
|
||||||
m[i, t] = _studentObjs[i].Teams.Contains(_teamObjs[t]) ? 1 : 0;
|
m[i, t] = _studentObjects[i].Teams.Contains(_teamObjects[t]) ? 1 : 0;
|
||||||
|
|
||||||
// Variables.
|
// Variables.
|
||||||
// x - 1 if meeting of team t takes place at time slot s, else 0
|
// x - 1 if meeting of team t takes place at time slot s, else 0
|
||||||
@@ -82,55 +83,24 @@ public class TeamScheduler
|
|||||||
foreach (var ts in _scheduleSeparateTeams)
|
foreach (var ts in _scheduleSeparateTeams)
|
||||||
foreach (var s in _timeSlots)
|
foreach (var s in _timeSlots)
|
||||||
model.Add(x[ts.Item1, s] != x[ts.Item2, s]);
|
model.Add(x[ts.Item1, s] != x[ts.Item2, s]);
|
||||||
//model.Add(
|
|
||||||
// new BoundedLinearExpression(
|
|
||||||
// x[ts.Item1, s],
|
|
||||||
// x[ts.Item2, s],
|
|
||||||
// false));
|
|
||||||
|
|
||||||
var solver = new CpSolver();
|
var solver = new CpSolver();
|
||||||
|
|
||||||
var cpSolverStatus = solver.Solve(model);
|
var cpSolverStatus = solver.Solve(model);
|
||||||
Console.WriteLine($"Solver status: {cpSolverStatus}");
|
Debug.WriteLine($"Solver status: {cpSolverStatus}");
|
||||||
|
var timeSlotTeams = new Team[_timeSlots.Length][];
|
||||||
|
|
||||||
if (cpSolverStatus is CpSolverStatus.Optimal or CpSolverStatus.Feasible)
|
if (cpSolverStatus is not (CpSolverStatus.Optimal or CpSolverStatus.Feasible))
|
||||||
{
|
return new TeamSchedulerSolution(timeSlotTeams, cpSolverStatus.ToString());
|
||||||
Console.WriteLine($"Total cost: {solver.ObjectiveValue}\n");
|
|
||||||
|
|
||||||
//foreach (var t in _teams)
|
Debug.WriteLine($"Total cost: {solver.ObjectiveValue}\n");
|
||||||
//foreach (var s in _timeSlots)
|
|
||||||
//{
|
|
||||||
// if (solver.Value(x[t, s]) > 0)
|
|
||||||
// Console.WriteLine($"{_teamObjs[t].Name} : {s}");
|
|
||||||
//}
|
|
||||||
|
|
||||||
//foreach (var i in _students)
|
|
||||||
//foreach (var s in _timeSlots)
|
|
||||||
//{
|
|
||||||
// if (solver.Value(y[i, s]) > 0)
|
|
||||||
// Console.WriteLine($"{_studentObjs[i].Name} : {s}");
|
|
||||||
//}
|
|
||||||
|
|
||||||
var timeSlotTeams = new List<Team>[_timeSlots.Length];
|
|
||||||
foreach (var s in _timeSlots)
|
|
||||||
{
|
|
||||||
var teams = new List<Team>();
|
|
||||||
foreach (var t in _teams)
|
|
||||||
{
|
|
||||||
if (solver.Value(x[t, s]) > 0)
|
|
||||||
{
|
|
||||||
teams.Add(_teamObjs[t]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
timeSlotTeams[s] = teams;
|
|
||||||
}
|
|
||||||
return timeSlotTeams;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Console.WriteLine("No solution found.");
|
|
||||||
return Array.Empty<IList<Team>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
foreach (var s in _timeSlots)
|
||||||
|
{
|
||||||
|
var teams = (from t in _teams where solver.Value(x[t, s]) > 0 select _teamObjects[t]).ToArray();
|
||||||
|
timeSlotTeams[s] = teams;
|
||||||
|
}
|
||||||
|
//Debug.WriteLine("No solution found.");
|
||||||
|
return new TeamSchedulerSolution(timeSlotTeams, cpSolverStatus.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace Core.Calculation;
|
||||||
|
|
||||||
|
public class TeamSchedulerOptions(
|
||||||
|
int timeSlots = 3,
|
||||||
|
string[]? absentStudents = null,
|
||||||
|
string[]? extended = null,
|
||||||
|
string[]? omittedEvents = null,
|
||||||
|
string[]? mustIncludeEvents = null,
|
||||||
|
DateTime date = new()
|
||||||
|
)
|
||||||
|
{
|
||||||
|
public int TimeSlots = timeSlots;
|
||||||
|
public string[]? AbsentStudents = absentStudents;
|
||||||
|
public string[]? ExtendedTeams = extended;
|
||||||
|
public string[]? OmittedEvents = omittedEvents;
|
||||||
|
public string[]? MustIncludeEvents = mustIncludeEvents;
|
||||||
|
public DateTime Date { get; } = date == new DateTime() ? DateTime.Today : date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Core.Entities;
|
||||||
|
|
||||||
|
namespace Core.Calculation;
|
||||||
|
|
||||||
|
public class TeamSchedulerSolution(
|
||||||
|
Team[][] timeSlots,
|
||||||
|
string status)
|
||||||
|
{
|
||||||
|
public Team[][] TimeSlots { get; set; } = timeSlots;
|
||||||
|
public string Status { get; set; } = status;
|
||||||
|
|
||||||
|
|
||||||
|
public static int GetStudentTeamOverlapCount(Team[][] timeSlots)
|
||||||
|
{
|
||||||
|
return timeSlots.Sum(GetStudentTeamOverlapCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetStudentTeamOverlapCount(Team[] timeSlot)
|
||||||
|
{
|
||||||
|
return GetStudentTeamOverlaps(timeSlot).Count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Tuple<Student, IEnumerable<Team>>> GetStudentTeamOverlaps(Team[] timeSlot)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
from s in timeSlot.SelectMany(ts => ts.Students).Distinct()
|
||||||
|
group s by timeSlot.Where(t => t.Students.Contains(s))
|
||||||
|
into gs
|
||||||
|
where gs.Key.Count() > 1
|
||||||
|
select Tuple.Create(gs.First(), gs.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,19 +3,18 @@
|
|||||||
namespace Core.Calculation;
|
namespace Core.Calculation;
|
||||||
public class TeamScheduler_DecisionTree
|
public class TeamScheduler_DecisionTree
|
||||||
{
|
{
|
||||||
private readonly IList<Student> _students;
|
private readonly Team[] _teams;
|
||||||
private readonly IList<Team> _teams;
|
|
||||||
|
|
||||||
private readonly int _timeSlotCount;
|
private readonly int _timeSlotCount;
|
||||||
|
|
||||||
public TeamScheduler_DecisionTree(IList<Team> teams, int timeSlotCount)
|
public TeamScheduler_DecisionTree(Team[] teams, int timeSlotCount)
|
||||||
{
|
{
|
||||||
_timeSlotCount = timeSlotCount;
|
_timeSlotCount = timeSlotCount;
|
||||||
_teams = teams;
|
_teams = teams;
|
||||||
_students = teams.SelectMany(t => t.Students).Distinct().ToList();
|
//_students = teams.SelectMany(t => t.Students).Distinct().ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<Team>[] Solve()
|
public TeamSchedulerSolution Solve()
|
||||||
{
|
{
|
||||||
var timeSlots = new IList<Team>[_timeSlotCount];
|
var timeSlots = new IList<Team>[_timeSlotCount];
|
||||||
for (var i = 0; i < _timeSlotCount; i++)
|
for (var i = 0; i < _timeSlotCount; i++)
|
||||||
@@ -34,68 +33,68 @@ public class TeamScheduler_DecisionTree
|
|||||||
|
|
||||||
timeSlots[overlaps.First().Item1].Add(team);
|
timeSlots[overlaps.First().Item1].Add(team);
|
||||||
}
|
}
|
||||||
return timeSlots;
|
return new TeamSchedulerSolution(timeSlots.Select(e => e.ToArray()).ToArray(), "Success?");
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<Team>[] SolveRecursive()
|
//public Team[][] SolveRecursive()
|
||||||
{
|
//{
|
||||||
// initialize time slots
|
// // initialize time slots
|
||||||
var timeSlots = new IList<Team>[_timeSlotCount];
|
// var timeSlots = new Team[][_timeSlotCount];
|
||||||
for (var i = 0; i < _timeSlotCount; i++)
|
// for (var i = 0; i < _timeSlotCount; i++)
|
||||||
timeSlots[i] = new List<Team>();
|
// timeSlots[i] = [];
|
||||||
|
|
||||||
return
|
// return
|
||||||
(from i in Enumerable.Range(1, 5)
|
// (from i in Enumerable.Range(1, 5)
|
||||||
let solution = Recursive(timeSlots, _teams, i)
|
// let solution = Recursive(timeSlots, _teams, i)
|
||||||
let overlapCount = Team.GetStudentTeamOverlapCount(solution)
|
// let overlapCount = TeamSchedulerSolution.GetStudentTeamOverlapCount(solution.Select(e => e.ToArray()).ToArray())
|
||||||
orderby overlapCount
|
// orderby overlapCount
|
||||||
select solution).First();
|
// select solution).First();
|
||||||
}
|
//}
|
||||||
|
|
||||||
private static IList<Team>[] CopyTimeSlots(IList<Team>[] timeSlots)
|
//private static Team[][] CopyTimeSlots(Team[][] timeSlots)
|
||||||
{
|
//{
|
||||||
return timeSlots.Select(ts => (IList<Team>)new List<Team>(ts)).ToArray();
|
// return timeSlots.Select(Team[] (ts) => ts.ToArray()).ToArray();
|
||||||
}
|
//}
|
||||||
|
|
||||||
private static IList<Team>[] Recursive(IList<Team>[] timeSlots, IList<Team> teams, int overlapTriesStudents = 4)
|
//private static Team[][] Recursive(Team[][] timeSlots, Team[] teams, int overlapTriesStudents = 4)
|
||||||
{
|
//{
|
||||||
if (!teams.Any())
|
// if (!teams.Any())
|
||||||
return timeSlots;
|
// return timeSlots;
|
||||||
|
|
||||||
var overlapsTries =
|
|
||||||
(from team in teams.OrderByDescending(t => t.Students.Count).Take(overlapTriesStudents)
|
|
||||||
//from ts in timeSlots
|
|
||||||
from tsi in Enumerable.Range(0, timeSlots.Length)
|
|
||||||
let ts = timeSlots[tsi]
|
|
||||||
let tss = ts.SelectMany(t => t.Students)
|
|
||||||
select Tuple.Create(ts, tsi, team, team.Students.Count(tss.Contains)))
|
|
||||||
.OrderBy(t => t.Item4)
|
|
||||||
.ThenBy(t => t.Item1.Count);
|
|
||||||
|
|
||||||
var minOverlaps =
|
// var overlapsTries =
|
||||||
(from o in overlapsTries
|
// (from team in teams.OrderByDescending(t => t.Students.Count).Take(overlapTriesStudents)
|
||||||
group o by o.Item4
|
// //from ts in timeSlots
|
||||||
into oo
|
// from tsi in Enumerable.Range(0, timeSlots.Length)
|
||||||
orderby oo.Key
|
// let ts = timeSlots[tsi]
|
||||||
select oo).First();
|
// let tss = ts.SelectMany(t => t.Students)
|
||||||
|
// select Tuple.Create(ts, tsi, team, team.Students.Count(tss.Contains)))
|
||||||
|
// .OrderBy(t => t.Item4)
|
||||||
|
// .ThenBy(t => t.Item1.Length);
|
||||||
|
|
||||||
var first = minOverlaps.First();
|
// var minOverlaps =
|
||||||
|
// (from o in overlapsTries
|
||||||
|
// group o by o.Item4
|
||||||
|
// into oo
|
||||||
|
// orderby oo.Key
|
||||||
|
// select oo).First();
|
||||||
|
|
||||||
var results = new List<Tuple<IList<Team>[], int>>();
|
// var first = minOverlaps.First();
|
||||||
|
|
||||||
foreach (var minOverlap in minOverlaps)
|
// var results = new List<Tuple<List<Team>[]>, int>>();
|
||||||
{
|
|
||||||
var timeSlotsAfterAdd = CopyTimeSlots(timeSlots);
|
|
||||||
timeSlotsAfterAdd[first.Item2].Add(first.Item3);
|
|
||||||
var remainingTeams = teams.Where(t => t != first.Item3).ToList();
|
|
||||||
|
|
||||||
var result = Recursive(timeSlotsAfterAdd, remainingTeams);
|
// foreach (var minOverlap in minOverlaps)
|
||||||
|
// {
|
||||||
|
// var timeSlotsAfterAdd = CopyTimeSlots(timeSlots);
|
||||||
|
// timeSlotsAfterAdd[first.Item2].Add(first.Item3);
|
||||||
|
// var remainingTeams = teams.Where(t => t != first.Item3).ToArray();
|
||||||
|
|
||||||
results.Add(Tuple.Create(result, Team.GetStudentTeamOverlapCount(result)));
|
// var result = Recursive(timeSlotsAfterAdd.Select(e => e.ToArray()).ToArray(), remainingTeams);
|
||||||
if (minOverlap.Item4 == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return results.OrderByDescending(r => r.Item2).First().Item1;
|
// results.Add(Tuple.Create(result, TeamSchedulerSolution.GetStudentTeamOverlapCount(result)));
|
||||||
}
|
// if (minOverlap.Item4 == 0)
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return results.OrderByDescending(r => r.Item2).First().Item1;
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
@@ -4,49 +4,42 @@ namespace Core.Calculation;
|
|||||||
|
|
||||||
public class UnassignedStudentScheduler
|
public class UnassignedStudentScheduler
|
||||||
{
|
{
|
||||||
private readonly IList<Student> _students;
|
private readonly Student[] _students;
|
||||||
private readonly IList<Team> _teams;
|
private readonly Team[] _teams;
|
||||||
private readonly IList<Team>[] _timeslots;
|
private readonly IList<Team>[] _timeSlots;
|
||||||
|
|
||||||
public UnassignedStudentScheduler(IList<Team> teams, IList<Team>[] timeslots)
|
public UnassignedStudentScheduler(Team[] teams, Team[][] timeslots)
|
||||||
{
|
{
|
||||||
_teams = teams;
|
_teams = teams;
|
||||||
_students = teams.SelectMany(t => t.Students).Distinct().ToList();
|
_students = teams.SelectMany(t => t.Students).Distinct().ToArray();
|
||||||
_timeslots = timeslots.Select(ts => ts.Select(t => t.Clone()).ToList()).ToArray();
|
_timeSlots = timeslots.Select(ts => ts.Select(t => t.Clone()).ToArray()).ToArray();
|
||||||
}
|
}
|
||||||
public static IEnumerable<Student> UnassignedStudents(IList<Student> students, IList<Team> timeSlot)
|
public static IEnumerable<Student> UnassignedStudents(IList<Student> students, IList<Team> timeSlot)
|
||||||
=> students.Where(s => !timeSlot.SelectMany(t => t.Students).Contains(s));
|
=> students.Where(s => !timeSlot.SelectMany(t => t.Students).Contains(s));
|
||||||
|
|
||||||
|
|
||||||
public static IEnumerable<Student>[] UnassignedStudents(IList<Student> students, IList<Team>[] schedule)
|
public static IEnumerable<Student>[] UnassignedStudents(IList<Student> students, IList<Team>[] schedule)
|
||||||
=> schedule.Select(ts => UnassignedStudents(students, ts)).ToArray();
|
=> schedule.Select(ts => UnassignedStudents(students, ts)).ToArray();
|
||||||
|
|
||||||
|
public TeamSchedulerSolution ScheduleStrategy(UnassignedScheduleStrategy scheduleStrategy)
|
||||||
|
{
|
||||||
|
var ss = scheduleStrategy switch
|
||||||
|
{
|
||||||
|
UnassignedScheduleStrategy.BiggestGroup => ScheduleStrategy(GetAvailableTeams_BiggestGroup),
|
||||||
|
UnassignedScheduleStrategy.IndividualEvents => ScheduleStrategy(GetAvailableTeams_Individual),
|
||||||
|
UnassignedScheduleStrategy.AnyNotMeetingAlready => ScheduleStrategy(GetAvailableTeams_AnyNotMeetingAlready),
|
||||||
|
UnassignedScheduleStrategy.Any => ScheduleStrategy(GetAvailableTeams_Any),
|
||||||
|
UnassignedScheduleStrategy.LevelOfEffort => ScheduleStrategy(GetAvailableTeams_LevelOfEffort),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(scheduleStrategy), scheduleStrategy, null)
|
||||||
|
};
|
||||||
|
|
||||||
public IList<Team>[] ScheduleStrategy(UnassignedScheduleStrategy scheduleStrategy)
|
return new TeamSchedulerSolution(ss, "Success?");
|
||||||
{
|
}
|
||||||
switch (scheduleStrategy)
|
|
||||||
{
|
|
||||||
case UnassignedScheduleStrategy.BiggestGroup:
|
|
||||||
return ScheduleStrategy(GetAvailableTeams_BiggestGroup);
|
|
||||||
case UnassignedScheduleStrategy.IndividualEvents:
|
|
||||||
return ScheduleStrategy(GetAvailableTeams_Individual);
|
|
||||||
case UnassignedScheduleStrategy.AnyNotMeetingAlready:
|
|
||||||
return ScheduleStrategy(GetAvailableTeams_AnyNotMeetingAlready);
|
|
||||||
case UnassignedScheduleStrategy.Any:
|
|
||||||
return ScheduleStrategy(GetAvailableTeams_Any);
|
|
||||||
case UnassignedScheduleStrategy.LevelOfEffort:
|
|
||||||
return ScheduleStrategy(GetAvailableTeams_LevelOfEffort);
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(scheduleStrategy), scheduleStrategy, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public Team[][] ScheduleStrategy(Func<IEnumerable<Team>, IEnumerable<Student>, IEnumerable<Team>> availableTeamSelector)
|
||||||
public IList<Team>[] ScheduleStrategy(Func<IEnumerable<Team>, IEnumerable<Student>, IEnumerable<Team>> availableTeamSelector)
|
|
||||||
{
|
{
|
||||||
// Find stuff for unassigned students in each timeslot
|
// Find stuff for unassigned students in each timeslot
|
||||||
var scheduledTeams = _timeslots.SelectMany(list => list).Distinct().ToList();
|
var scheduledTeams = _timeSlots.SelectMany(list => list).Distinct().ToList();
|
||||||
foreach (var slot in _timeslots)
|
foreach (var slot in _timeSlots)
|
||||||
{
|
{
|
||||||
var unassigned = UnassignedStudents(_students, slot).ToList();
|
var unassigned = UnassignedStudents(_students, slot).ToList();
|
||||||
while (unassigned.Count > 0)
|
while (unassigned.Count > 0)
|
||||||
@@ -67,15 +60,14 @@ public class UnassignedStudentScheduler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _timeslots;
|
return _timeSlots.Select(e => e.ToArray()).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// find teams where several unassigned students can work together
|
// find teams where several unassigned students can work together
|
||||||
private IEnumerable<Team> GetAvailableTeams_BiggestGroup(
|
private IEnumerable<Team> GetAvailableTeams_BiggestGroup(
|
||||||
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
||||||
_teams
|
_teams
|
||||||
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
|
.Where(t => scheduledTeams.All(st => st.TeamId != t.TeamId))
|
||||||
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
||||||
.Where(t => t.Students.Count > 1) //|| t.Event.EventFormat is EventFormat.Individual
|
.Where(t => t.Students.Count > 1) //|| t.Event.EventFormat is EventFormat.Individual
|
||||||
//.OrderBy(t => scheduledTeams.Count(st => st.Name == t.Name))
|
//.OrderBy(t => scheduledTeams.Count(st => st.Name == t.Name))
|
||||||
@@ -86,7 +78,7 @@ public class UnassignedStudentScheduler
|
|||||||
private IEnumerable<Team> GetAvailableTeams_Individual(
|
private IEnumerable<Team> GetAvailableTeams_Individual(
|
||||||
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
||||||
_teams
|
_teams
|
||||||
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
|
.Where(t => scheduledTeams.All(st => st.TeamId != t.TeamId))
|
||||||
.Where(t => t.Event.EventFormat == EventFormat.Individual || t.Students.Count == 1)
|
.Where(t => t.Event.EventFormat == EventFormat.Individual || t.Students.Count == 1)
|
||||||
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
||||||
.Where(t => t.Students.Count > 0);
|
.Where(t => t.Students.Count > 0);
|
||||||
@@ -95,7 +87,7 @@ public class UnassignedStudentScheduler
|
|||||||
private IEnumerable<Team> GetAvailableTeams_AnyNotMeetingAlready(
|
private IEnumerable<Team> GetAvailableTeams_AnyNotMeetingAlready(
|
||||||
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
||||||
_teams
|
_teams
|
||||||
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
|
.Where(t => scheduledTeams.All(st => st.TeamId != t.TeamId))
|
||||||
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
||||||
.Where(t => t.Students.Count > 0);
|
.Where(t => t.Students.Count > 0);
|
||||||
|
|
||||||
@@ -110,7 +102,7 @@ public class UnassignedStudentScheduler
|
|||||||
private IEnumerable<Team> GetAvailableTeams_LevelOfEffort(
|
private IEnumerable<Team> GetAvailableTeams_LevelOfEffort(
|
||||||
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
||||||
_teams
|
_teams
|
||||||
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
|
.Where(t => scheduledTeams.All(st => st.TeamId != t.TeamId))
|
||||||
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
||||||
.Where(t => t.Students.Count > 1) //|| t.Event.EventFormat is EventFormat.Individual
|
.Where(t => t.Students.Count > 1) //|| t.Event.EventFormat is EventFormat.Individual
|
||||||
//.OrderBy(t => scheduledTeams.Count(st => st.Name == t.Name))
|
//.OrderBy(t => scheduledTeams.Count(st => st.Name == t.Name))
|
||||||
@@ -120,38 +112,38 @@ public class UnassignedStudentScheduler
|
|||||||
public IList<Team>[] AddAdditionalTimeSlot(Team team)
|
public IList<Team>[] AddAdditionalTimeSlot(Team team)
|
||||||
{
|
{
|
||||||
// sort by how many teammembers are already in that timeslot, descending
|
// sort by how many teammembers are already in that timeslot, descending
|
||||||
foreach (var timeslot in _timeslots.OrderBy(ts => ts.SelectMany(t => t.Students).Count(team.Students.Contains)))
|
foreach (var timeslot in _timeSlots.OrderBy(ts => ts.SelectMany(t => t.Students).Count(team.Students.Contains)))
|
||||||
{
|
{
|
||||||
if (timeslot.Any(t => t.Name == team.Name))
|
if (timeslot.Any(t => t.TeamId == team.TeamId))
|
||||||
continue;
|
continue;
|
||||||
timeslot.Add(team);
|
timeslot.Add(team);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _timeslots;
|
return _timeSlots;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the current events rolling for the other time slots
|
// Keep the current events rolling for the other time slots
|
||||||
public IList<Team>[] ExtendEvents()
|
public IList<Team>[] ExtendEvents()
|
||||||
{
|
{
|
||||||
var scheduledTeams = _timeslots.SelectMany(list => list).Distinct().ToList();
|
var scheduledTeams = _timeSlots.SelectMany(list => list).Distinct().ToList();
|
||||||
|
|
||||||
// get all the students in each time slot
|
// get all the students in each time slot
|
||||||
var timeslotStudents = _timeslots.Select(ts => ts.SelectMany(t => t.Students).Distinct()).ToArray();
|
var timeslotStudents = _timeSlots.Select(ts => ts.SelectMany(t => t.Students).Distinct()).ToArray();
|
||||||
|
|
||||||
// clone teams from timeslot for each other timeslot, removing the students
|
// clone teams from timeslot for each other timeslot, removing the students
|
||||||
|
|
||||||
//var copiedTeams = new Dictionary<int, IList<Team>>();
|
//var copiedTeams = new Dictionary<int, IList<Team>>();
|
||||||
|
|
||||||
for (var i = 0; i < _timeslots.Length; i++)
|
for (var i = 0; i < _timeSlots.Length; i++)
|
||||||
{
|
{
|
||||||
var sourceTimeslot = _timeslots[i];
|
var sourceTimeslot = _timeSlots[i];
|
||||||
|
|
||||||
for (var j = 0; j < _timeslots.Length; j++)
|
for (var j = 0; j < _timeSlots.Length; j++)
|
||||||
{
|
{
|
||||||
if (i == j)
|
if (i == j)
|
||||||
continue;
|
continue;
|
||||||
var targetTimeslot = _timeslots[j];
|
var targetTimeslot = _timeSlots[j];
|
||||||
var targetTimeslotStudents = targetTimeslot.SelectMany(t => t.Students).Distinct();
|
var targetTimeslotStudents = targetTimeslot.SelectMany(t => t.Students).Distinct();
|
||||||
var clonedTeams = sourceTimeslot.Select(t => t.CloneWithOmittedStudents(targetTimeslotStudents));
|
var clonedTeams = sourceTimeslot.Select(t => t.CloneWithOmittedStudents(targetTimeslotStudents));
|
||||||
foreach (var clonedTeam in clonedTeams.Where(t => t.Students.Any()))
|
foreach (var clonedTeam in clonedTeams.Where(t => t.Students.Any()))
|
||||||
@@ -161,6 +153,6 @@ public class UnassignedStudentScheduler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _timeslots;
|
return _timeSlots;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,10 @@ public class EventDefinition
|
|||||||
|| SemifinalistActivity.Contains("Speech")
|
|| SemifinalistActivity.Contains("Speech")
|
||||||
|| SemifinalistActivity.Contains("Test")
|
|| SemifinalistActivity.Contains("Test")
|
||||||
|| SemifinalistActivity.Contains("Flight")
|
|| SemifinalistActivity.Contains("Flight")
|
||||||
|
|| Name.Contains("Leadership")
|
||||||
|
|| Name.Contains("Forensic")
|
||||||
|| Name.Contains("Flight")
|
|| Name.Contains("Flight")
|
||||||
|
|| Name.Contains("Coding")
|
||||||
|| SemifinalistActivity.Contains("Debate")
|
|| SemifinalistActivity.Contains("Debate")
|
||||||
|| SemifinalistActivity.Contains("Photography")
|
|| SemifinalistActivity.Contains("Photography")
|
||||||
|| SemifinalistActivity.Contains("Build")
|
|| SemifinalistActivity.Contains("Build")
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ public class PartialTeam : Team
|
|||||||
{
|
{
|
||||||
var remainingStudents = Students.Where(s => !studentsToOmit.Contains(s)).ToList();
|
var remainingStudents = Students.Where(s => !studentsToOmit.Contains(s)).ToList();
|
||||||
var omittedStudents = OmittedStudents.Union(Students.Where(studentsToOmit.Contains)).Distinct().ToList();
|
var omittedStudents = OmittedStudents.Union(Students.Where(studentsToOmit.Contains)).Distinct().ToList();
|
||||||
return new PartialTeam{TeamId = Name, Event = Event, Students = remainingStudents, OmittedStudents = omittedStudents };
|
return new PartialTeam{TeamId = TeamId, Event = Event, Students = remainingStudents, OmittedStudents = omittedStudents };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ using static System.Text.RegularExpressions.Regex;
|
|||||||
|
|
||||||
namespace Core.Entities;
|
namespace Core.Entities;
|
||||||
|
|
||||||
public class Student
|
public class Student : IEquatable<Student>
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
@@ -74,4 +74,23 @@ public class Student
|
|||||||
|
|
||||||
public bool VotingDelegate => OfficerRole is Entities.OfficerRole.President or Entities.OfficerRole.VicePresident;
|
public bool VotingDelegate => OfficerRole is Entities.OfficerRole.President or Entities.OfficerRole.VicePresident;
|
||||||
|
|
||||||
|
public bool Equals(Student? other)
|
||||||
|
{
|
||||||
|
if (other is null) return false;
|
||||||
|
if (ReferenceEquals(this, other)) return true;
|
||||||
|
return Id == other.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is null) return false;
|
||||||
|
if (ReferenceEquals(this, obj)) return true;
|
||||||
|
if (obj.GetType() != GetType()) return false;
|
||||||
|
return Equals((Student)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,6 @@ public class StudentEventRanking
|
|||||||
public EventDefinition EventDefinition { get; set; } = null!;
|
public EventDefinition EventDefinition { get; set; } = null!;
|
||||||
public int Rank { get; set; }
|
public int Rank { get; set; }
|
||||||
|
|
||||||
public const int MaxRank = 6;
|
public const int MaxRank = 10;
|
||||||
|
|
||||||
}
|
}
|
||||||
+11
-37
@@ -5,10 +5,15 @@ public class Team
|
|||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Display(Name = "Team Name")]
|
[Display(Name = "Team Name")]
|
||||||
public string Name { get; set; }
|
public int? Number { get; set; }
|
||||||
public EventDefinition Event { get; set; }
|
public EventDefinition Event
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
public List<Student> Students { get; set; } = [];
|
public List<Student> Students { get; set; } = [];
|
||||||
|
|
||||||
public Student? Captain { get; set; }
|
public Student? Captain { get; set; }
|
||||||
@@ -16,8 +21,6 @@ public class Team
|
|||||||
[Display(Name = "Team Id")]
|
[Display(Name = "Team Id")]
|
||||||
public string? TeamId { get; set; }
|
public string? TeamId { get; set; }
|
||||||
|
|
||||||
//public string? RegionalTimeSlot { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
// public Tuple<DateTime,DateTime?>? RegionalTimeSlotObj
|
// public Tuple<DateTime,DateTime?>? RegionalTimeSlotObj
|
||||||
//{
|
//{
|
||||||
@@ -51,46 +54,17 @@ public class Team
|
|||||||
var studentsToOmitList = studentsToOmit.ToList();
|
var studentsToOmitList = studentsToOmit.ToList();
|
||||||
var omittedStudents = Students.Where(studentsToOmitList.Contains).ToList();
|
var omittedStudents = Students.Where(studentsToOmitList.Contains).ToList();
|
||||||
if (!omittedStudents.Any())
|
if (!omittedStudents.Any())
|
||||||
return new Team{Captain = Captain, Event = Event, Students = Students.ToList(), TeamId = Name};
|
return new Team{Captain = Captain, Event = Event, Students = Students.ToList(), TeamId = TeamId, Number = Number};
|
||||||
|
|
||||||
var remainingStudents = Students.Where(s => !studentsToOmitList.Contains(s)).ToList();
|
var remainingStudents = Students.Where(s => !studentsToOmitList.Contains(s)).ToList();
|
||||||
return new PartialTeam { Name = Name, Event = Event, Students = remainingStudents, OmittedStudents = omittedStudents};
|
return new PartialTeam { Number = Number, Event = Event, Students = remainingStudents, OmittedStudents = omittedStudents};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Team Clone() => CloneWithOmittedStudents([]);
|
public Team Clone() => CloneWithOmittedStudents([]);
|
||||||
|
|
||||||
public static int GetStudentTeamOverlapCount(IList<Team>[] timeSlots)
|
|
||||||
{
|
|
||||||
return timeSlots.Sum(GetStudentTeamOverlapCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetStudentTeamOverlapCount(IList<Team> timeSlot)
|
|
||||||
{
|
|
||||||
return GetStudentTeamOverlaps(timeSlot).Count();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<Tuple<Student, IEnumerable<Team>>> GetStudentTeamOverlaps(IList<Team> timeSlot)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
from s in timeSlot.SelectMany(ts => ts.Students).Distinct()
|
|
||||||
group s by timeSlot.Where(t => t.Students.Contains(s))
|
|
||||||
into gs
|
|
||||||
where gs.Key.Count() > 1
|
|
||||||
select Tuple.Create(gs.First(), gs.Key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Name;
|
return $"{Event.Name} {(Number != null ? $"(#{Number})" : "")}";
|
||||||
}
|
|
||||||
|
|
||||||
public string ToStringWithIndividualAndRegional()
|
|
||||||
{
|
|
||||||
var ind = Event.EventFormat is EventFormat.Individual ? " (Ind.)" : string.Empty;
|
|
||||||
var regional= Event.RegionalEvent ? " (Reg.)" : string.Empty;
|
|
||||||
//var regional= Event.RegionalEvent ? " (Reg.)" : string.Empty;
|
|
||||||
|
|
||||||
var eventAttributes = Event.EventAttributes();
|
|
||||||
return string.IsNullOrEmpty(eventAttributes) ? Name : Name + " (" + eventAttributes + ")";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ public class EventDefinitionParser : CsvParserBase
|
|||||||
var theme = CsvReader.GetField("Theme");
|
var theme = CsvReader.GetField("Theme");
|
||||||
var description = CsvReader.GetField("Description");
|
var description = CsvReader.GetField("Description");
|
||||||
var levelOfEffort = CsvReader.GetField<int?>("Level of Effort");
|
var levelOfEffort = CsvReader.GetField<int?>("Level of Effort");
|
||||||
//var regionalTeams = CsvReader.GetField<int>("Regional Teams");
|
//var regionalTeams = CsvReader.GetField<int>("Regional TimeSlots");
|
||||||
|
|
||||||
var competitiveEvent = new EventDefinition
|
var competitiveEvent = new EventDefinition
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Core.Parsers
|
|||||||
|
|
||||||
foreach (var team in _teams)
|
foreach (var team in _teams)
|
||||||
{
|
{
|
||||||
csv.WriteField(team.Name);
|
csv.WriteField(team.TeamId);
|
||||||
csv.WriteField(team.Event.Name);
|
csv.WriteField(team.Event.Name);
|
||||||
foreach (var teamStudent in team.Students)
|
foreach (var teamStudent in team.Students)
|
||||||
{
|
{
|
||||||
@@ -146,14 +146,14 @@ namespace Core.Parsers
|
|||||||
{
|
{
|
||||||
if (@event.EventFormat is EventFormat.Team)
|
if (@event.EventFormat is EventFormat.Team)
|
||||||
{
|
{
|
||||||
teams.Add(new Team { Event = @event, Students = teamStudents, Captain = captain, Name = teamName, TeamId = teamNumber});
|
teams.Add(new Team { Event = @event, Students = teamStudents, Captain = captain, TeamId = teamNumber});
|
||||||
}
|
}
|
||||||
else if (@event.EventFormat is EventFormat.Individual)
|
else if (@event.EventFormat is EventFormat.Individual)
|
||||||
{
|
{
|
||||||
foreach (var student in teamStudents)
|
foreach (var student in teamStudents)
|
||||||
{
|
{
|
||||||
teams.Add(new Team{Name = $"{teamName} - {student.FirstName}", Event = @event,
|
teams.Add(new Team{Event = @event,
|
||||||
Students = new List<Student> { student }, Captain = student, TeamId = teamNumber});
|
Students = [student], Captain = student, TeamId = teamNumber});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ namespace Data
|
|||||||
.WithMany(e => e.Teams);
|
.WithMany(e => e.Teams);
|
||||||
|
|
||||||
builder.HasOne(e => e.Captain);
|
builder.HasOne(e => e.Captain);
|
||||||
|
|
||||||
|
builder.Property(e => e.Number).IsRequired(false);
|
||||||
|
//builder.i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public class StudentEventRankingConfiguration : IEntityTypeConfiguration<StudentEventRanking>
|
public class StudentEventRankingConfiguration : IEntityTypeConfiguration<StudentEventRanking>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace Data.Migrations
|
|||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Teams",
|
name: "TimeSlots",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
@@ -91,7 +91,7 @@ namespace Data.Migrations
|
|||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_StudentTeam_Teams_TeamsId",
|
name: "FK_StudentTeam_Teams_TeamsId",
|
||||||
column: x => x.TeamsId,
|
column: x => x.TeamsId,
|
||||||
principalTable: "Teams",
|
principalTable: "TimeSlots",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
@@ -108,7 +108,7 @@ namespace Data.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Teams_CaptainId",
|
name: "IX_Teams_CaptainId",
|
||||||
table: "Teams",
|
table: "TimeSlots",
|
||||||
column: "CaptainId");
|
column: "CaptainId");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ namespace Data.Migrations
|
|||||||
name: "StudentTeam");
|
name: "StudentTeam");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Teams");
|
name: "TimeSlots");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Students");
|
name: "Students");
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Data.Migrations
|
|||||||
{
|
{
|
||||||
migrationBuilder.RenameColumn(
|
migrationBuilder.RenameColumn(
|
||||||
name: "TeamNumber",
|
name: "TeamNumber",
|
||||||
table: "Teams",
|
table: "TimeSlots",
|
||||||
newName: "TeamId");
|
newName: "TeamId");
|
||||||
|
|
||||||
migrationBuilder.RenameColumn(
|
migrationBuilder.RenameColumn(
|
||||||
@@ -36,7 +36,7 @@ namespace Data.Migrations
|
|||||||
{
|
{
|
||||||
migrationBuilder.RenameColumn(
|
migrationBuilder.RenameColumn(
|
||||||
name: "TeamId",
|
name: "TeamId",
|
||||||
table: "Teams",
|
table: "TimeSlots",
|
||||||
newName: "TeamNumber");
|
newName: "TeamNumber");
|
||||||
|
|
||||||
migrationBuilder.RenameColumn(
|
migrationBuilder.RenameColumn(
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ namespace Data.Migrations
|
|||||||
{
|
{
|
||||||
migrationBuilder.AddColumn<int>(
|
migrationBuilder.AddColumn<int>(
|
||||||
name: "EventId",
|
name: "EventId",
|
||||||
table: "Teams",
|
table: "TimeSlots",
|
||||||
type: "INTEGER",
|
type: "INTEGER",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
defaultValue: 0);
|
defaultValue: 0);
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
migrationBuilder.AddColumn<string>(
|
||||||
name: "Name",
|
name: "Name",
|
||||||
table: "Teams",
|
table: "TimeSlots",
|
||||||
type: "TEXT",
|
type: "TEXT",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
defaultValue: "");
|
defaultValue: "");
|
||||||
@@ -47,12 +47,12 @@ namespace Data.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Teams_EventId",
|
name: "IX_Teams_EventId",
|
||||||
table: "Teams",
|
table: "TimeSlots",
|
||||||
column: "EventId");
|
column: "EventId");
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
migrationBuilder.AddForeignKey(
|
||||||
name: "FK_Teams_Events_EventId",
|
name: "FK_Teams_Events_EventId",
|
||||||
table: "Teams",
|
table: "TimeSlots",
|
||||||
column: "EventId",
|
column: "EventId",
|
||||||
principalTable: "Events",
|
principalTable: "Events",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
@@ -64,19 +64,19 @@ namespace Data.Migrations
|
|||||||
{
|
{
|
||||||
migrationBuilder.DropForeignKey(
|
migrationBuilder.DropForeignKey(
|
||||||
name: "FK_Teams_Events_EventId",
|
name: "FK_Teams_Events_EventId",
|
||||||
table: "Teams");
|
table: "TimeSlots");
|
||||||
|
|
||||||
migrationBuilder.DropIndex(
|
migrationBuilder.DropIndex(
|
||||||
name: "IX_Teams_EventId",
|
name: "IX_Teams_EventId",
|
||||||
table: "Teams");
|
table: "TimeSlots");
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
migrationBuilder.DropColumn(
|
||||||
name: "EventId",
|
name: "EventId",
|
||||||
table: "Teams");
|
table: "TimeSlots");
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
migrationBuilder.DropColumn(
|
||||||
name: "Name",
|
name: "Name",
|
||||||
table: "Teams");
|
table: "TimeSlots");
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
migrationBuilder.DropColumn(
|
||||||
name: "Email",
|
name: "Email",
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20250915175030_AddTeamNumber")]
|
||||||
|
partial class AddTeamNumber
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.8");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.EventDefinition", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Documentation")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Eligibility")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("EventFormat")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("LevelOfEffort")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("MaxTeamCountState")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("MaxTeamSize")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("MinTeamSize")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("RegionalEvent")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RegionalPresubmit")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SemifinalistActivity")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ShortName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("StatePreliminaryRound")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("StatePresubmission")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("StatePretesting")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Theme")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name");
|
||||||
|
|
||||||
|
b.ToTable("Events");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.Student", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("FirstName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Grade")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NationalId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("OfficerRole")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RegionalId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("StateId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("TsaYear")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Students");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.StudentEventRanking", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("EventDefinitionId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("StudentId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Rank")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("EventDefinitionId", "StudentId");
|
||||||
|
|
||||||
|
b.HasIndex("StudentId");
|
||||||
|
|
||||||
|
b.ToTable("StudentEventRanking");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.Team", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("CaptainId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("EventId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Number")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("TeamId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CaptainId");
|
||||||
|
|
||||||
|
b.HasIndex("EventId");
|
||||||
|
|
||||||
|
b.ToTable("Teams");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("StudentTeam", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("StudentsId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("TeamsId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("StudentsId", "TeamsId");
|
||||||
|
|
||||||
|
b.HasIndex("TeamsId");
|
||||||
|
|
||||||
|
b.ToTable("StudentTeam");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.StudentEventRanking", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Core.Entities.EventDefinition", "EventDefinition")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("EventDefinitionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Core.Entities.Student", "Student")
|
||||||
|
.WithMany("EventRankings")
|
||||||
|
.HasForeignKey("StudentId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("EventDefinition");
|
||||||
|
|
||||||
|
b.Navigation("Student");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.Team", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Core.Entities.Student", "Captain")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CaptainId");
|
||||||
|
|
||||||
|
b.HasOne("Core.Entities.EventDefinition", "Event")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("EventId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Captain");
|
||||||
|
|
||||||
|
b.Navigation("Event");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("StudentTeam", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Core.Entities.Student", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("StudentsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Core.Entities.Team", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TeamsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.Student", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("EventRankings");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddTeamNumber : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Name",
|
||||||
|
table: "TimeSlots");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "Number",
|
||||||
|
table: "TimeSlots",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Number",
|
||||||
|
table: "TimeSlots");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Name",
|
||||||
|
table: "TimeSlots",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20250917165027_NullableTeamNumber")]
|
||||||
|
partial class NullableTeamNumber
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.8");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.EventDefinition", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Documentation")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Eligibility")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("EventFormat")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("LevelOfEffort")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("MaxTeamCountState")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("MaxTeamSize")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("MinTeamSize")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("RegionalEvent")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RegionalPresubmit")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SemifinalistActivity")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ShortName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("StatePreliminaryRound")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("StatePresubmission")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("StatePretesting")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Theme")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name");
|
||||||
|
|
||||||
|
b.ToTable("Events");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.Student", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("FirstName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Grade")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NationalId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("OfficerRole")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RegionalId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("StateId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("TsaYear")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Students");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.StudentEventRanking", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("EventDefinitionId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("StudentId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Rank")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("EventDefinitionId", "StudentId");
|
||||||
|
|
||||||
|
b.HasIndex("StudentId");
|
||||||
|
|
||||||
|
b.ToTable("StudentEventRanking");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.Team", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("CaptainId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("EventId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("Number")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("TeamId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CaptainId");
|
||||||
|
|
||||||
|
b.HasIndex("EventId");
|
||||||
|
|
||||||
|
b.ToTable("Teams");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("StudentTeam", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("StudentsId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("TeamsId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("StudentsId", "TeamsId");
|
||||||
|
|
||||||
|
b.HasIndex("TeamsId");
|
||||||
|
|
||||||
|
b.ToTable("StudentTeam");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.StudentEventRanking", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Core.Entities.EventDefinition", "EventDefinition")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("EventDefinitionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Core.Entities.Student", "Student")
|
||||||
|
.WithMany("EventRankings")
|
||||||
|
.HasForeignKey("StudentId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("EventDefinition");
|
||||||
|
|
||||||
|
b.Navigation("Student");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.Team", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Core.Entities.Student", "Captain")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CaptainId");
|
||||||
|
|
||||||
|
b.HasOne("Core.Entities.EventDefinition", "Event")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("EventId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Captain");
|
||||||
|
|
||||||
|
b.Navigation("Event");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("StudentTeam", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Core.Entities.Student", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("StudentsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Core.Entities.Team", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TeamsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Core.Entities.Student", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("EventRankings");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class NullableTeamNumber : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "Number",
|
||||||
|
table: "TimeSlots",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "INTEGER");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "Number",
|
||||||
|
table: "TimeSlots",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "INTEGER",
|
||||||
|
oldNullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -161,9 +161,8 @@ namespace Data.Migrations
|
|||||||
b.Property<int>("EventId")
|
b.Property<int>("EventId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<int?>("Number")
|
||||||
.IsRequired()
|
.HasColumnType("INTEGER");
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("TeamId")
|
b.Property<string>("TeamId")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
@@ -174,7 +173,7 @@ namespace Data.Migrations
|
|||||||
|
|
||||||
b.HasIndex("EventId");
|
b.HasIndex("EventId");
|
||||||
|
|
||||||
b.ToTable("Teams");
|
b.ToTable("TimeSlots");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("StudentTeam", b =>
|
modelBuilder.Entity("StudentTeam", b =>
|
||||||
|
|||||||
Binary file not shown.
@@ -36,40 +36,40 @@ public class TeamSchedulerTest
|
|||||||
//var eventAssignment = new EventAssignment(events, students);
|
//var eventAssignment = new EventAssignment(events, students);
|
||||||
//var teams = eventAssignment.Solve();
|
//var teams = eventAssignment.Solve();
|
||||||
|
|
||||||
IList<Team>[] timeSlots;
|
TeamSchedulerSolution solution;
|
||||||
if (true)
|
if (true)
|
||||||
{
|
{
|
||||||
var teamScheduler = TeamScheduler.CreateInstance(teams, 3);
|
var teamScheduler = TeamScheduler.CreateInstance(teams, 3);
|
||||||
timeSlots = teamScheduler.Solve();
|
solution = teamScheduler.Solve();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var teamScheduler = new TeamScheduler_DecisionTree(teams, 3);
|
var teamScheduler = new TeamScheduler_DecisionTree(teams, 3);
|
||||||
timeSlots = teamScheduler.Solve();
|
solution = teamScheduler.Solve();
|
||||||
}
|
}
|
||||||
|
|
||||||
timeSlots = new UnassignedStudentScheduler(allTeams, timeSlots).ScheduleStrategy(UnassignedScheduleStrategy.BiggestGroup);
|
solution = new UnassignedStudentScheduler(allTeams, solution.TimeSlots).ScheduleStrategy(UnassignedScheduleStrategy.BiggestGroup);
|
||||||
timeSlots = new UnassignedStudentScheduler(allTeams, timeSlots).ScheduleStrategy(UnassignedScheduleStrategy.IndividualEvents);
|
solution = new UnassignedStudentScheduler(allTeams, solution.TimeSlots).ScheduleStrategy(UnassignedScheduleStrategy.IndividualEvents);
|
||||||
|
|
||||||
var i = 1;
|
var i = 1;
|
||||||
foreach (var slot in timeSlots)
|
foreach (var slot in solution.TimeSlots)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Time slot {i++}");
|
Console.WriteLine($"Time slot {i++}");
|
||||||
foreach (var team in slot.OrderBy(s => s.Event.Name))
|
foreach (var team in slot.OrderBy(s => s.Event.Name))
|
||||||
{
|
{
|
||||||
var names = string.Join(", ", team.Students.OrderByDescending(s => s.Grade + s.TsaYear).Select(s => s.FirstName));
|
var names = string.Join(", ", team.Students.OrderByDescending(s => s.Grade + s.TsaYear).Select(s => s.FirstName));
|
||||||
Console.WriteLine($"\t{team.Name}");
|
Console.WriteLine($"\t{team.Event.Name}");
|
||||||
Console.WriteLine($"\t\t{names}");
|
Console.WriteLine($"\t\t{names}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var overlaps = Team.GetStudentTeamOverlaps(slot).ToList();
|
var overlaps = TeamSchedulerSolution.GetStudentTeamOverlaps(slot).ToList();
|
||||||
|
|
||||||
if (overlaps.Any())
|
if (overlaps.Any())
|
||||||
{
|
{
|
||||||
Console.WriteLine("\toverlaps");
|
Console.WriteLine("\toverlaps");
|
||||||
foreach (var overlap in overlaps)
|
foreach (var overlap in overlaps)
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
$"\t\t{overlap.Item1.Name} : {string.Join(", ", overlap.Item2.Select(t => t.Name))}");
|
$"\t\t{overlap.Item1.Name} : {string.Join(", ", overlap.Item2.Select(t => t.Event.Name))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var unassigned = UnassignedStudentScheduler.UnassignedStudents(students, slot).ToList();
|
var unassigned = UnassignedStudentScheduler.UnassignedStudents(students, slot).ToList();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class TeamParser_Tests
|
|||||||
foreach (var team in teams)
|
foreach (var team in teams)
|
||||||
{
|
{
|
||||||
|
|
||||||
Console.WriteLine($"{team.Name}");
|
Console.WriteLine($"{team.Event.Name}");
|
||||||
var join = string.Join(", ", team.Students.OrderByDescending(s=> s.Grade + s.TsaYear).Select(s => $"{s.FirstNameLastName}{(team.Captain == s ? " *" : "")}"));
|
var join = string.Join(", ", team.Students.OrderByDescending(s=> s.Grade + s.TsaYear).Select(s => $"{s.FirstNameLastName}{(team.Captain == s ? " *" : "")}"));
|
||||||
Console.WriteLine($"\t{join}");
|
Console.WriteLine($"\t{join}");
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ public class TeamParser_Tests
|
|||||||
|
|
||||||
foreach (var team in teams.Where(t => t.Event.RegionalEvent))
|
foreach (var team in teams.Where(t => t.Event.RegionalEvent))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"{team.Name} {team.Event.RegionalPresubmit} team.RegionalTimeSlot");
|
Console.WriteLine($"{team.Event.Name} {team.Event.RegionalPresubmit} team.RegionalTimeSlot");
|
||||||
var join = string.Join(", ", team.Students.OrderByDescending(s => team.Captain == s).ThenByDescending(s => s.Grade + s.TsaYear).Select(s => $"{s.FirstNameLastName}{(team.Captain == s ? " *" +
|
var join = string.Join(", ", team.Students.OrderByDescending(s => team.Captain == s).ThenByDescending(s => s.Grade + s.TsaYear).Select(s => $"{s.FirstNameLastName}{(team.Captain == s ? " *" +
|
||||||
"(Cpt.)" : "")}"));
|
"(Cpt.)" : "")}"));
|
||||||
Console.WriteLine($"\t{join}");
|
Console.WriteLine($"\t{join}");
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public static class TestEntityHandler
|
|||||||
|
|
||||||
public static EventDefinition[] GetEvents()
|
public static EventDefinition[] GetEvents()
|
||||||
{
|
{
|
||||||
var fileInfo = FileUtility.GetContentFile(ContentDirectory, "2025-26 RMS TSA - Event Definitions.csv");
|
var fileInfo = FileUtility.GetContentFile(ContentDirectory, "2023-24 RMS TSA student & event - Event Definitions.csv");
|
||||||
var eventRankingsParser = new EventDefinitionParser(fileInfo);
|
var eventRankingsParser = new EventDefinitionParser(fileInfo);
|
||||||
return eventRankingsParser.Parse();
|
return eventRankingsParser.Parse();
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ public static class TestEntityHandler
|
|||||||
public static Student[] GetStudents(IList<EventDefinition> events)
|
public static Student[] GetStudents(IList<EventDefinition> events)
|
||||||
{
|
{
|
||||||
//var studentEventRankingsCsv = "Student Event Rankings.csv";
|
//var studentEventRankingsCsv = "Student Event Rankings.csv";
|
||||||
var studentEventRankingsCsv = "2025-26 RMS TSA student & eventDefinition - Nationals Student Event Rankings.csv";
|
var studentEventRankingsCsv = "2023-24 RMS TSA student & event - Student Event Rankings.csv";
|
||||||
|
|
||||||
var fileInfo = FileUtility.GetContentFile(ContentDirectory, studentEventRankingsCsv);
|
var fileInfo = FileUtility.GetContentFile(ContentDirectory, studentEventRankingsCsv);
|
||||||
var eventRankingsParser = new StudentParser(fileInfo);
|
var eventRankingsParser = new StudentParser(fileInfo);
|
||||||
@@ -35,7 +35,7 @@ public static class TestEntityHandler
|
|||||||
public static Team[] GetTeams(IList<EventDefinition> events, IList<Student> students)
|
public static Team[] GetTeams(IList<EventDefinition> events, IList<Student> students)
|
||||||
{
|
{
|
||||||
//var studentEventRankingsCsv = "Student Event Rankings.csv";
|
//var studentEventRankingsCsv = "Student Event Rankings.csv";
|
||||||
var studentEventRankingsCsv = "2025-26 RMS TSA Teams.csv";
|
var studentEventRankingsCsv = "2023-24 RMS TSA student & event - TimeSlots.csv";
|
||||||
|
|
||||||
var fileInfo = FileUtility.GetContentFile(ContentDirectory, studentEventRankingsCsv);
|
var fileInfo = FileUtility.GetContentFile(ContentDirectory, studentEventRankingsCsv);
|
||||||
var eventRankingsParser = new TeamParser(fileInfo);
|
var eventRankingsParser = new TeamParser(fileInfo);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||||
<link href="@Assets["_content/MudBlazor/MudBlazor.min.css"]" rel="stylesheet" />
|
<link href="@Assets["_content/MudBlazor/MudBlazor.min.css"]" rel="stylesheet" />
|
||||||
<link rel="stylesheet" href="app.css" />
|
<link rel="stylesheet" href="app.css" />
|
||||||
<link rel="stylesheet" href="WebApp.styles.css" />
|
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<HeadOutlet />
|
<HeadOutlet />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -0,0 +1,278 @@
|
|||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace WebApp.Components.Layout
|
||||||
|
{
|
||||||
|
public static class CustomThemes
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
!
|
||||||
|
* Bootswatch v5.3.3 (https://bootswatch.com)
|
||||||
|
* Theme: cerulean
|
||||||
|
* Copyright 2012-2024 Thomas Park
|
||||||
|
* Licensed under MIT
|
||||||
|
* Based on Bootstrap
|
||||||
|
*/
|
||||||
|
public static readonly MudTheme Ceruleantheme = new()
|
||||||
|
{
|
||||||
|
PaletteLight = new PaletteLight()
|
||||||
|
{
|
||||||
|
Black = "#000",
|
||||||
|
White = "#fff",
|
||||||
|
Primary = "#2fa4e7ff",
|
||||||
|
PrimaryContrastText = "#d5edfaff",
|
||||||
|
Secondary = "#e9ecef",
|
||||||
|
SecondaryContrastText = "#495057ff",
|
||||||
|
Tertiary = "#dee2e6ff",
|
||||||
|
TertiaryContrastText = "#495057ff",
|
||||||
|
Info = "#033c73",
|
||||||
|
InfoContrastText = "#cdd8e3",
|
||||||
|
Success = "#73a839",
|
||||||
|
SuccessContrastText = "#e3eed7",
|
||||||
|
Warning = "#dd5600",
|
||||||
|
WarningContrastText = "#f8ddcc",
|
||||||
|
Error = "rgba(244,67,54,1)",
|
||||||
|
ErrorContrastText = "rgba(255,255,255,1)",
|
||||||
|
Dark = "#343a40",
|
||||||
|
DarkContrastText = "#ced4da",
|
||||||
|
TextPrimary = "#13425cff",
|
||||||
|
TextSecondary = "#5d5e60",
|
||||||
|
TextDisabled = "rgba(0,0,0,0.3764705882352941)",
|
||||||
|
ActionDefault = "rgba(0,0,0,0.5372549019607843)",
|
||||||
|
ActionDisabled = "rgba(0,0,0,0.25882352941176473)",
|
||||||
|
ActionDisabledBackground = "rgba(0,0,0,0.11764705882352941)",
|
||||||
|
Background = "rgba(255,255,255,1)",
|
||||||
|
BackgroundGray = "rgba(245,245,245,1)",
|
||||||
|
Surface = "rgba(255,255,255,1)",
|
||||||
|
DrawerBackground = "#e9ecefff",
|
||||||
|
DrawerText = "#495057ff",
|
||||||
|
DrawerIcon = "#e9ecef",
|
||||||
|
AppbarBackground = "#2fa4e7ff",
|
||||||
|
AppbarText = "#d5edfaff",
|
||||||
|
LinesDefault = "rgba(0,0,0,0.11764705882352941)",
|
||||||
|
LinesInputs = "rgba(189,189,189,1)",
|
||||||
|
TableLines = "rgba(224,224,224,1)",
|
||||||
|
TableStriped = "rgba(0,0,0,0.0196078431372549)",
|
||||||
|
TableHover = "rgba(0,0,0,0.0392156862745098)",
|
||||||
|
Divider = "rgba(224,224,224,1)",
|
||||||
|
DividerLight = "rgba(0,0,0,0.8)",
|
||||||
|
PrimaryDarken = "#13425c",
|
||||||
|
PrimaryLighten = "#d5edfa",
|
||||||
|
SecondaryDarken = "#5d5e60",
|
||||||
|
SecondaryLighten = "#fbfbfc",
|
||||||
|
TertiaryDarken = "rgba(73, 80, 87, 0.5)",
|
||||||
|
TertiaryLighten = "rgba(73, 80, 87, 0.5)",
|
||||||
|
InfoDarken = "#01182e",
|
||||||
|
InfoLighten = "#cdd8e3",
|
||||||
|
SuccessDarken = "#2e4317",
|
||||||
|
SuccessLighten = "#e3eed7",
|
||||||
|
WarningDarken = "#582200",
|
||||||
|
WarningLighten = "#f8ddcc",
|
||||||
|
ErrorDarken = "rgb(242,28,13)",
|
||||||
|
ErrorLighten = "rgb(246,96,85)",
|
||||||
|
DarkDarken = "rgb(46,46,46)",
|
||||||
|
DarkLighten = "rgb(87,87,87)",
|
||||||
|
HoverOpacity = 0.06,
|
||||||
|
RippleOpacity = 0.1,
|
||||||
|
RippleOpacitySecondary = 0.2,
|
||||||
|
GrayDefault = "#9E9E9E",
|
||||||
|
GrayLight = "#BDBDBD",
|
||||||
|
GrayLighter = "#E0E0E0",
|
||||||
|
GrayDark = "#757575",
|
||||||
|
GrayDarker = "#616161",
|
||||||
|
OverlayDark = "rgba(33,33,33,0.4980392156862745)",
|
||||||
|
OverlayLight = "rgba(255,255,255,0.4980392156862745)",
|
||||||
|
},
|
||||||
|
PaletteDark = new PaletteDark()
|
||||||
|
{
|
||||||
|
Black = "#000",
|
||||||
|
White = "#fff",
|
||||||
|
Primary = "#2fa4e7ff",
|
||||||
|
PrimaryContrastText = "#d5edfaff",
|
||||||
|
Secondary = "#e9ecef",
|
||||||
|
SecondaryContrastText = "#2f2f30",
|
||||||
|
Tertiary = "rgba(222, 226, 230, 0.5)",
|
||||||
|
TertiaryContrastText = "#2b3035",
|
||||||
|
Info = "#026e8eff",
|
||||||
|
InfoContrastText = "#010c17",
|
||||||
|
Success = "#73a839",
|
||||||
|
SuccessContrastText = "#17220b",
|
||||||
|
Warning = "#dd5600",
|
||||||
|
WarningContrastText = "#2c1100",
|
||||||
|
Error = "rgba(244,67,54,1)",
|
||||||
|
ErrorContrastText = "rgba(255,255,255,1)",
|
||||||
|
Dark = "#343a40ff",
|
||||||
|
DarkContrastText = "#1a1d20",
|
||||||
|
TextPrimary = "#82c8f1",
|
||||||
|
TextSecondary = "#f2f4f5",
|
||||||
|
TextDisabled = "rgba(255,255,255,0.2)",
|
||||||
|
ActionDefault = "rgba(173,173,177,1)",
|
||||||
|
ActionDisabled = "rgba(255,255,255,0.25882352941176473)",
|
||||||
|
ActionDisabledBackground = "rgba(255,255,255,0.11764705882352941)",
|
||||||
|
Background = "rgba(50,51,61,1)",
|
||||||
|
BackgroundGray = "rgba(39,39,47,1)",
|
||||||
|
Surface = "#212529ff",
|
||||||
|
DrawerBackground = "#2f2f30ff",
|
||||||
|
DrawerText = "#e9ecef",
|
||||||
|
DrawerIcon = "#e9ecef",
|
||||||
|
AppbarBackground = "#2fa4e7ff",
|
||||||
|
AppbarText = "#d5edfaff",
|
||||||
|
LinesDefault = "rgba(255,255,255,0.11764705882352941)",
|
||||||
|
LinesInputs = "rgba(255,255,255,0.2980392156862745)",
|
||||||
|
TableLines = "rgba(255,255,255,0.11764705882352941)",
|
||||||
|
TableStriped = "rgba(255,255,255,0.2)",
|
||||||
|
Divider = "#ffffff73",
|
||||||
|
DividerLight = "#ffffff2e",
|
||||||
|
PrimaryDarken = "#82c8f1",
|
||||||
|
PrimaryLighten = "#09212e",
|
||||||
|
SecondaryDarken = "#f2f4f5",
|
||||||
|
SecondaryLighten = "#2f2f30",
|
||||||
|
TertiaryDarken = "rgba(222, 226, 230, 0.5)",
|
||||||
|
TertiaryLighten = "rgba(222, 226, 230, 0.5)",
|
||||||
|
InfoDarken = "#688aab",
|
||||||
|
InfoLighten = "#010c17",
|
||||||
|
SuccessDarken = "#abcb88",
|
||||||
|
SuccessLighten = "#17220b",
|
||||||
|
WarningDarken = "#eb9a66",
|
||||||
|
WarningLighten = "#2c1100",
|
||||||
|
ErrorDarken = "rgb(242,28,13)",
|
||||||
|
ErrorLighten = "rgb(246,96,85)",
|
||||||
|
DarkDarken = "rgb(23,23,28)",
|
||||||
|
DarkLighten = "rgb(56,56,67)",
|
||||||
|
},
|
||||||
|
LayoutProperties = new LayoutProperties()
|
||||||
|
{
|
||||||
|
DefaultBorderRadius = "4px",
|
||||||
|
DrawerMiniWidthLeft = "56px",
|
||||||
|
DrawerMiniWidthRight = "56px",
|
||||||
|
DrawerWidthLeft = "240px",
|
||||||
|
DrawerWidthRight = "240px",
|
||||||
|
AppbarHeight = "64px",
|
||||||
|
},
|
||||||
|
Typography = new Typography()
|
||||||
|
{
|
||||||
|
Default = new DefaultTypography
|
||||||
|
{
|
||||||
|
FontFamily = ["Roboto", "Helvetica", "Arial", "sans-serif"],
|
||||||
|
FontWeight = "400",
|
||||||
|
FontSize = ".875rem",
|
||||||
|
LineHeight = "1.43",
|
||||||
|
LetterSpacing = ".01071em",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
H1 = new H1Typography
|
||||||
|
{
|
||||||
|
FontWeight = "300",
|
||||||
|
FontSize = "6rem",
|
||||||
|
LineHeight = "1.167",
|
||||||
|
LetterSpacing = "-.01562em",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
H2 = new H2Typography
|
||||||
|
{
|
||||||
|
FontWeight = "300",
|
||||||
|
FontSize = "3.75rem",
|
||||||
|
LineHeight = "1.2",
|
||||||
|
LetterSpacing = "-.00833em",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
H3 = new H3Typography
|
||||||
|
{
|
||||||
|
FontWeight = "400",
|
||||||
|
FontSize = "3rem",
|
||||||
|
LineHeight = "1.167",
|
||||||
|
LetterSpacing = "0",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
H4 = new H4Typography
|
||||||
|
{
|
||||||
|
FontWeight = "400",
|
||||||
|
FontSize = "2.125rem",
|
||||||
|
LineHeight = "1.235",
|
||||||
|
LetterSpacing = ".00735em",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
H5 = new H5Typography
|
||||||
|
{
|
||||||
|
FontWeight = "400",
|
||||||
|
FontSize = "1.5rem",
|
||||||
|
LineHeight = "1.334",
|
||||||
|
LetterSpacing = "0",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
H6 = new H6Typography
|
||||||
|
{
|
||||||
|
FontWeight = "500",
|
||||||
|
FontSize = "1.25rem",
|
||||||
|
LineHeight = "1.6",
|
||||||
|
LetterSpacing = ".0075em",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
Subtitle1 = new Subtitle1Typography
|
||||||
|
{
|
||||||
|
FontWeight = "400",
|
||||||
|
FontSize = "1rem",
|
||||||
|
LineHeight = "1.75",
|
||||||
|
LetterSpacing = ".00938em",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
Subtitle2 = new Subtitle2Typography
|
||||||
|
{
|
||||||
|
FontWeight = "500",
|
||||||
|
FontSize = ".875rem",
|
||||||
|
LineHeight = "1.57",
|
||||||
|
LetterSpacing = ".00714em",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
Body1 = new Body1Typography
|
||||||
|
{
|
||||||
|
FontWeight = "400",
|
||||||
|
FontSize = "1rem",
|
||||||
|
LineHeight = "1.5",
|
||||||
|
LetterSpacing = ".00938em",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
Body2 = new Body2Typography
|
||||||
|
{
|
||||||
|
FontWeight = "400",
|
||||||
|
FontSize = ".875rem",
|
||||||
|
LineHeight = "1.43",
|
||||||
|
LetterSpacing = ".01071em",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
Button = new ButtonTypography
|
||||||
|
{
|
||||||
|
FontWeight = "500",
|
||||||
|
FontSize = ".875rem",
|
||||||
|
LineHeight = "1.75",
|
||||||
|
LetterSpacing = ".02857em",
|
||||||
|
TextTransform = "uppercase",
|
||||||
|
},
|
||||||
|
Caption = new CaptionTypography
|
||||||
|
{
|
||||||
|
FontWeight = "400",
|
||||||
|
FontSize = ".75rem",
|
||||||
|
LineHeight = "1.66",
|
||||||
|
LetterSpacing = ".03333em",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
Overline = new OverlineTypography
|
||||||
|
{
|
||||||
|
FontWeight = "400",
|
||||||
|
FontSize = ".75rem",
|
||||||
|
LineHeight = "2.66",
|
||||||
|
LetterSpacing = ".08333em",
|
||||||
|
TextTransform = "none",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ZIndex = new ZIndex()
|
||||||
|
{
|
||||||
|
Drawer = 1100,
|
||||||
|
Popover = 1200,
|
||||||
|
AppBar = 1300,
|
||||||
|
Dialog = 1400,
|
||||||
|
Snackbar = 1500,
|
||||||
|
Tooltip = 1600,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,32 @@
|
|||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
|
@inject IConfiguration Configuration
|
||||||
|
|
||||||
<MudThemeProvider />
|
<MudThemeProvider Theme="CustomThemes.Ceruleantheme" />
|
||||||
<MudPopoverProvider />
|
<MudPopoverProvider />
|
||||||
|
<MudDialogProvider />
|
||||||
|
|
||||||
<div class="page">
|
<MudLayout>
|
||||||
|
<MudAppBar Class="no-print">
|
||||||
<div class="sidebar">
|
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
|
||||||
|
TSA Chapter Organizer - @Configuration["ChapterSettings:Name"]
|
||||||
|
</MudAppBar>
|
||||||
|
<MudDrawer @bind-Open="@_drawerOpen" Class="no-print">
|
||||||
<NavMenu/>
|
<NavMenu/>
|
||||||
</div>
|
</MudDrawer>
|
||||||
|
<MudMainContent>
|
||||||
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge">
|
||||||
|
@Body
|
||||||
|
</MudContainer>
|
||||||
|
</MudMainContent>
|
||||||
|
</MudLayout>
|
||||||
|
@code {
|
||||||
|
bool _drawerOpen = true;
|
||||||
|
|
||||||
<main>
|
void DrawerToggle()
|
||||||
@* <div class="top-row px-4">
|
{
|
||||||
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
|
_drawerOpen = !_drawerOpen;
|
||||||
</div> *@
|
}
|
||||||
|
}
|
||||||
<article class="content px-4">
|
|
||||||
@Body
|
|
||||||
</article>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="blazor-error-ui">
|
<div id="blazor-error-ui">
|
||||||
An unhandled error has occurred.
|
An unhandled error has occurred.
|
||||||
|
|||||||
@@ -1,43 +1,20 @@
|
|||||||
<div class="top-row ps-3 navbar navbar-dark">
|
@using WebApp.Models
|
||||||
<div class="container-fluid">
|
@inject IConfiguration Configuration
|
||||||
<a class="navbar-brand" href="">WebApp</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
|
|
||||||
|
|
||||||
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
|
|
||||||
<nav class="flex-column">
|
|
||||||
<div class="nav-item px-3">
|
|
||||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
|
||||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nav-item px-3">
|
|
||||||
<NavLink class="nav-link" href="events">
|
|
||||||
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Events
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nav-item px-3">
|
|
||||||
<NavLink class="nav-link" href="students">
|
|
||||||
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Students
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="nav-item px-3">
|
|
||||||
<NavLink class="nav-link" href="teams">
|
|
||||||
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Teams
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nav-item px-3">
|
|
||||||
<NavLink class="nav-link" href="import">
|
|
||||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Import
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<MudPaper Width="250px" Class="d-inline-flex py-3" Elevation="0">
|
||||||
|
<MudNavMenu Class="mud-width-full">
|
||||||
|
<MudText Typo="Typo.h6" Class="px-4">TSA Chapter Organizer</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Class="px-4 mud-text-secondary">@Configuration["ChapterSettings:Name"]</MudText>
|
||||||
|
<MudDivider Class="my-2" />
|
||||||
|
<MudNavLink Href="/events" Icon="@AppIcons.Events">Events</MudNavLink>
|
||||||
|
<MudNavGroup Title="Students" Icon="@Icons.Material.Filled.People" Expanded="true">
|
||||||
|
<MudNavLink Href="/students" Icon="@Icons.Material.Filled.People">Students</MudNavLink>
|
||||||
|
<MudNavLink Href="/students/event-ranking" Icon="@AppIcons.EventRank">Event Ranking</MudNavLink>
|
||||||
|
</MudNavGroup>
|
||||||
|
<MudNavGroup Title="Teams" Icon="@Icons.Material.Outlined.Groups" Expanded="true">
|
||||||
|
<MudNavLink Href="/teams" Icon="@AppIcons.Teams">Teams</MudNavLink>
|
||||||
|
<MudNavLink Href="/teams/assignment" Icon="@AppIcons.TeamAssignment">Event Assignment</MudNavLink>
|
||||||
|
<MudNavLink Href="/teams/scheduler" Icon="@AppIcons.TeamAssignment">Schedule</MudNavLink>
|
||||||
|
</MudNavGroup>
|
||||||
|
</MudNavMenu>
|
||||||
|
</MudPaper>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 3.5rem;
|
width: 3.5rem;
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
color: white;
|
color: blue;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.5rem;
|
top: 0.5rem;
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
@using Core.Entities
|
|
||||||
@using Data
|
|
||||||
@using Microsoft.EntityFrameworkCore
|
|
||||||
@page "/events/descriptions"
|
|
||||||
@inject IConfiguration Configuration
|
|
||||||
@inject AppDbContext Context
|
|
||||||
@rendermode InteractiveServer
|
|
||||||
|
|
||||||
<PageTitle>TSA Events @Configuration["ChapterSettings:CompetitionYear"]</PageTitle>
|
|
||||||
|
|
||||||
<h1>TSA Events @Configuration["ChapterSettings:CompetitionYear"]</h1>
|
|
||||||
|
|
||||||
@if (_events == null)
|
|
||||||
{
|
|
||||||
<p><em>Loading...</em></p>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div>
|
|
||||||
@foreach (var evt in _events)
|
|
||||||
{
|
|
||||||
<div class="container nobrk">
|
|
||||||
@if (evt.RegionalEvent)
|
|
||||||
{
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<i>Regional Event</i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div div class="row">
|
|
||||||
<div class="col-4">
|
|
||||||
<h5>@evt.Name</h5>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
@if (evt.EventFormat is EventFormat.Team)
|
|
||||||
{
|
|
||||||
<html><strong>@evt.EventFormat</strong><br/>Size: <strong>@evt.TeamSize</strong></html>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<html>
|
|
||||||
<strong>@evt.EventFormat</strong>
|
|
||||||
</html>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
Eligibility: @evt.Eligibility
|
|
||||||
</div>
|
|
||||||
<div class="col-1">
|
|
||||||
<strong> Effort</strong>: @evt.LevelOfEffort
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<strong>Activity</strong>: @evt.SemifinalistActivity
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div div class="row mt-3">
|
|
||||||
<div class="col">@evt.Description</div></div>
|
|
||||||
@if (!string.IsNullOrEmpty(evt.Theme))
|
|
||||||
{
|
|
||||||
<div div class="row mt-2">
|
|
||||||
<div class="col-3 text-center"><i>Theme for 2025-26:</i></div>
|
|
||||||
<div class="col" style="white-space:pre-wrap;">@evt.Theme</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(evt.Documentation))
|
|
||||||
{
|
|
||||||
<div div class="row mt-2">
|
|
||||||
<div class="col-3 text-center"><i>Materials:</i></div>
|
|
||||||
<div class="col">@evt.Documentation</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<hr/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@code {
|
|
||||||
private EventDefinition[]? _events = null;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
_events = await Context.Events.OrderBy(e => e.Name).Where(e => e.Name != "Chapter Team").ToArrayAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,27 +2,25 @@
|
|||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@inject AppDbContext Context
|
@inject AppDbContext Context
|
||||||
|
|
||||||
|
|
||||||
<PageTitle>Events - TSA Chapter Organizer</PageTitle>
|
<PageTitle>Events - TSA Chapter Organizer</PageTitle>
|
||||||
|
|
||||||
<MudText Typo="Typo.h3">Events</MudText>
|
<MudText Typo="Typo.h3">Events</MudText>
|
||||||
|
|
||||||
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="events/create">Create New</MudButton>
|
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="events/create">Create New</MudButton>
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.Print" Href="events/printout">Printable Descriptions</MudButton>
|
||||||
|
|
||||||
|
<MudDataGrid T="EventDefinition" ServerData="ServerReload" @ref="_dataGrid" Filterable="true" RowsPerPage="50" >
|
||||||
<MudDataGrid T="EventDefinition" ServerData="ServerReload" @ref="_dataGrid" Filterable="true" RowsPerPage="50">
|
|
||||||
<Columns>
|
<Columns>
|
||||||
<PropertyColumn Property="@(e => e.Name)" Title="Event Name" Sortable="true" />
|
<PropertyColumn Property="@(e => e.Name)" Title="Event Name" Sortable="true" />
|
||||||
<PropertyColumn Property="@(e => e.EventFormat)" Title="Event Format" />
|
<PropertyColumn Property="@(e => e.EventFormat)" Title="Event Format" />
|
||||||
<PropertyColumn Property="@(e => e.LevelOfEffort)" Title="Level of Effort" />
|
<PropertyColumn Property="@(e => e.LevelOfEffort)" Title="Level of Effort" />
|
||||||
<PropertyColumn Property="@(e => e.SemifinalistActivity)" Title="On-site Activity" />
|
<PropertyColumn Property="@(e => e.Eligibility)" Title="Eligibility" />
|
||||||
<PropertyColumn Property="@(e => e.RegionalEvent)" Title="Regional Event" />
|
|
||||||
<TemplateColumn Title="Team Size" CellStyle="white-space:nowrap">
|
<TemplateColumn Title="Team Size" CellStyle="white-space:nowrap">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
[@context.Item.MinTeamSize - @context.Item.MaxTeamSize]
|
[@context.Item.MinTeamSize - @context.Item.MaxTeamSize]
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="Teams State #">
|
<TemplateColumn Title="TimeSlots State #">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.MaxTeamCountState
|
@context.Item.MaxTeamCountState
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
@@ -50,27 +48,6 @@
|
|||||||
</PagerContent>
|
</PagerContent>
|
||||||
</MudDataGrid>
|
</MudDataGrid>
|
||||||
|
|
||||||
@*
|
|
||||||
<QuickGrid Class="table" Items="context.Events">
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.Name" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.EventFormat" />
|
|
||||||
@* <PropertyColumn Property="eventdefinition => eventdefinition.MinTeamSize" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.MaxTeamSize" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.SemifinalistActivity" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.Notes" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.MaxTeamCountState" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.RegionalEvent" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.RegionalPresubmit" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.StatePresubmission" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.StatePretesting" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.StatePreliminaryRound" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.Documentation" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.Eligibility" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.Theme" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.Description" />
|
|
||||||
<PropertyColumn Property="eventdefinition => eventdefinition.LevelOfEffort" />
|
|
||||||
*@
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
MudDataGrid<EventDefinition> _dataGrid = null!;
|
MudDataGrid<EventDefinition> _dataGrid = null!;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@page "/events/printout"
|
||||||
|
@inject IConfiguration Configuration
|
||||||
|
@inject AppDbContext Context
|
||||||
|
|
||||||
|
<PageTitle>TSA Events @Configuration["ChapterSettings:CompetitionYear"]</PageTitle>
|
||||||
|
|
||||||
|
<MudText Typo="Typo.h3">TSA Events @Configuration["ChapterSettings:CompetitionYear"]</MudText>
|
||||||
|
<MudText Typo="Typo.h5" Class="mb-4">Yearly theme: Unity Through Community</MudText>
|
||||||
|
|
||||||
|
@if (_events == null)
|
||||||
|
{
|
||||||
|
<p><em>Loading...</em></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudContainer>
|
||||||
|
@foreach (var evt in _events)
|
||||||
|
{
|
||||||
|
<MudContainer Class="mt-3 mb-1 nobrk">
|
||||||
|
<MudGrid>
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudStack>
|
||||||
|
<MudItem>
|
||||||
|
<MudText Class="d-flex py-1" Typo="Typo.h5">@evt.Name</MudText>
|
||||||
|
</MudItem>
|
||||||
|
@if (evt.RegionalEvent)
|
||||||
|
{
|
||||||
|
<MudItem>
|
||||||
|
<MudText Class="d-flex" Typo="Typo.caption"><i>Regional Event</i></MudText>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="2">
|
||||||
|
|
||||||
|
<MudText>
|
||||||
|
@if (evt.EventFormat is EventFormat.Team)
|
||||||
|
{
|
||||||
|
<strong>@evt.EventFormat</strong>
|
||||||
|
<br/>
|
||||||
|
<p>Size: <strong>@evt.TeamSize</strong></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<strong>@evt.EventFormat</strong>
|
||||||
|
}
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="3">
|
||||||
|
Eligibility: @evt.Eligibility
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="1">
|
||||||
|
<strong> Effort</strong>: @evt.LevelOfEffort
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="2">
|
||||||
|
<strong>Activity</strong>: @evt.SemifinalistActivity
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@evt.Description</MudText>
|
||||||
|
</MudItem>
|
||||||
|
@if (!string.IsNullOrEmpty(evt.Theme))
|
||||||
|
{
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudText Class="d-flex py-1">
|
||||||
|
<i>Theme for 2025-26:</i>
|
||||||
|
</MudText>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="8">
|
||||||
|
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@evt.Theme</MudText>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(evt.Documentation))
|
||||||
|
{
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudText Class="d-flex py-1">
|
||||||
|
<i>Materials:</i>
|
||||||
|
</MudText>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="8">
|
||||||
|
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@evt.Documentation</MudText>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
</MudGrid>
|
||||||
|
</MudContainer>
|
||||||
|
<MudDivider />
|
||||||
|
}
|
||||||
|
</MudContainer>
|
||||||
|
|
||||||
|
<MudContainer>
|
||||||
|
@foreach (var evt in _events)
|
||||||
|
{
|
||||||
|
<MudContainer Class="mt-3 mb-1 nobrk">
|
||||||
|
<MudGrid>
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudStack>
|
||||||
|
<MudItem>
|
||||||
|
<MudText Class="d-flex py-1" Typo="Typo.h5">@evt.Name</MudText>
|
||||||
|
</MudItem>
|
||||||
|
@if (evt.RegionalEvent)
|
||||||
|
{
|
||||||
|
<MudItem>
|
||||||
|
<MudText Class="d-flex" Typo="Typo.caption"><i>Regional Event</i></MudText>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="2">
|
||||||
|
|
||||||
|
<MudText>
|
||||||
|
@if (evt.EventFormat is EventFormat.Team)
|
||||||
|
{
|
||||||
|
<strong>@evt.EventFormat</strong>
|
||||||
|
<br />
|
||||||
|
<p>Size: <strong>@evt.TeamSize</strong></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<strong>@evt.EventFormat</strong>
|
||||||
|
}
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="3">
|
||||||
|
Eligibility: @evt.Eligibility
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="1">
|
||||||
|
<strong> Effort</strong>: @evt.LevelOfEffort
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="2">
|
||||||
|
<strong>Activity</strong>: @evt.SemifinalistActivity
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@evt.Description</MudText>
|
||||||
|
</MudItem>
|
||||||
|
@if (!string.IsNullOrEmpty(evt.Theme))
|
||||||
|
{
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudText Class="d-flex py-1">
|
||||||
|
<i>Theme for 2025-26:</i>
|
||||||
|
</MudText>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="8">
|
||||||
|
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@evt.Theme</MudText>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(evt.Documentation))
|
||||||
|
{
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudText Class="d-flex py-1">
|
||||||
|
<i>Materials:</i>
|
||||||
|
</MudText>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="8">
|
||||||
|
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@evt.Documentation</MudText>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
</MudGrid>
|
||||||
|
</MudContainer>
|
||||||
|
<MudDivider />
|
||||||
|
}
|
||||||
|
</MudContainer>
|
||||||
|
}
|
||||||
|
@code {
|
||||||
|
private EventDefinition[]? _events;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_events = await Context.Events.OrderBy(e => e.Name).ToArrayAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<h3>Index</h3>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,16 +1,20 @@
|
|||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using WebApp.Models
|
||||||
@page "/students/event-ranking"
|
@page "/students/event-ranking"
|
||||||
@inject AppDbContext Context
|
@inject AppDbContext Context
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
|
|
||||||
<PageTitle>Student Event Ranks - TSA Chapter Organizer</PageTitle>
|
<PageTitle>Student Event Ranks - TSA Chapter Organizer</PageTitle>
|
||||||
|
|
||||||
|
<MudText Typo="Typo.h3"><MudIcon Icon="@AppIcons.EventRank"></MudIcon> Student Event Ranks</MudText>
|
||||||
|
|
||||||
@if (_students == null)
|
@if (_students == null)
|
||||||
{
|
{
|
||||||
<p><em>Loading...</em></p>
|
<p><em>Loading...</em></p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
<MudTable Items="_students" Hover="true" Breakpoint="Breakpoint.Sm" LoadingProgressColor="Color.Info">
|
<MudTable Items="_students" Hover="true" Breakpoint="Breakpoint.Sm" LoadingProgressColor="Color.Info">
|
||||||
<HeaderContent>
|
<HeaderContent>
|
||||||
|
|
||||||
@@ -23,6 +27,10 @@ else
|
|||||||
<MudTh>4th</MudTh>
|
<MudTh>4th</MudTh>
|
||||||
<MudTh>5th</MudTh>
|
<MudTh>5th</MudTh>
|
||||||
<MudTh>6th</MudTh>
|
<MudTh>6th</MudTh>
|
||||||
|
<MudTh>7th</MudTh>
|
||||||
|
<MudTh>8th</MudTh>
|
||||||
|
<MudTh>9th</MudTh>
|
||||||
|
<MudTh>10th</MudTh>
|
||||||
<MudTh></MudTh>
|
<MudTh></MudTh>
|
||||||
<MudTh>Warnings</MudTh>
|
<MudTh>Warnings</MudTh>
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
@@ -32,7 +40,7 @@ else
|
|||||||
<MudTh>@context.Grade</MudTh>
|
<MudTh>@context.Grade</MudTh>
|
||||||
<MudTh>@context.TsaYear</MudTh>
|
<MudTh>@context.TsaYear</MudTh>
|
||||||
|
|
||||||
@for (var i = 1; i <= 6; i++)
|
@for (var i = 1; i <= 10; i++)
|
||||||
{
|
{
|
||||||
var st = context.EventRankings.FirstOrDefault(e => e.Rank == i);
|
var st = context.EventRankings.FirstOrDefault(e => e.Rank == i);
|
||||||
<MudTd Class="@($"event-rank-{i})")">
|
<MudTd Class="@($"event-rank-{i})")">
|
||||||
@@ -40,8 +48,8 @@ else
|
|||||||
{
|
{
|
||||||
<span>@st.EventDefinition.ShortName
|
<span>@st.EventDefinition.ShortName
|
||||||
@if(st.EventDefinition.EventFormat == EventFormat.Individual) { <span>ⓘ</span>}
|
@if(st.EventDefinition.EventFormat == EventFormat.Individual) { <span>ⓘ</span>}
|
||||||
@if(st.EventDefinition.RegionalEvent) { <span>ⓡ</span>}
|
@if(st.EventDefinition.RegionalEvent) {<span>ⓡ</span>}
|
||||||
@if(st.EventDefinition.OnSiteActivity) { <span>ⓐ</span>}
|
@if(st.EventDefinition.OnSiteActivity) {<span>ⓐ</span>}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</MudTd>
|
</MudTd>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<PageTitle>Student Event Ranks - TSA Chapter Organizer</PageTitle>
|
<PageTitle>Student Event Ranks - TSA Chapter Organizer</PageTitle>
|
||||||
|
|
||||||
<MudText Typo="Typo.h3">Student Event Ranks</MudText>
|
<MudText Typo="Typo.h3"><MudIcon Icon="@AppIcons.EventRank"></MudIcon> Student Event Ranks</MudText>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@if (_student == null)
|
@if (_student == null)
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
<MudText Typo="Typo.h3">Students</MudText>
|
<MudText Typo="Typo.h3">Students</MudText>
|
||||||
|
|
||||||
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="students/create">Create New</MudButton>
|
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="students/create">Create New</MudButton>
|
||||||
<MudButton StartIcon="@Icons.Material.Filled.AddChart" Href="students/event-ranking">Event Rankings</MudButton>
|
<MudButton StartIcon="@AppIcons.EventRank" Href="students/event-ranking">Event Rankings</MudButton>
|
||||||
|
|
||||||
<MudDataGrid T="Student" ServerData="ServerReload" @ref="_dataGrid" Filterable="true" RowsPerPage="25">
|
<MudDataGrid T="Student" ServerData="ServerReload" @ref="_dataGrid" Filterable="true" RowsPerPage="25">
|
||||||
<Columns>
|
<Columns>
|
||||||
@* <PropertyColumn Property="@(e => e.Name)" Title="First Name" SortBy="e => e.FirstName" /> *@
|
@* <PropertyColumn Property="@(e => e.Name)" Title="First Name" SortBy="e => e.FirstName" /> *@
|
||||||
<TemplateColumn Title="Name" SortBy="e => e.FirstName" Sortable="true">
|
<TemplateColumn Title="Name" SortBy="e => e.LastName" Sortable="true" >
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.Name
|
@context.Item.LastNameFirstName
|
||||||
@if (context.Item.OfficerRole != null)
|
@if (context.Item.OfficerRole != null)
|
||||||
{
|
{
|
||||||
<MudChip T="string" Icon="@(AppIcons.OfficerRoleIcon(context.Item.OfficerRole.Value))">@context.Item.OfficerRole</MudChip>
|
<MudChip T="string" Icon="@(AppIcons.OfficerRoleIcon(context.Item.OfficerRole.Value))">@context.Item.OfficerRole</MudChip>
|
||||||
@@ -56,7 +56,9 @@
|
|||||||
private async Task<GridData<Student>> ServerReload(GridState<Student> state)
|
private async Task<GridData<Student>> ServerReload(GridState<Student> state)
|
||||||
{
|
{
|
||||||
|
|
||||||
var query = Context.Students.Where(state.FilterDefinitions).OrderBy(state.SortDefinitions);
|
var query =
|
||||||
|
Context.Students.OrderBy(e => e.LastName)
|
||||||
|
.Where(state.FilterDefinitions).OrderBy(state.SortDefinitions);
|
||||||
|
|
||||||
var totalItems = await query.CountAsync();
|
var totalItems = await query.CountAsync();
|
||||||
var pagedData = await query.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArrayAsync();
|
var pagedData = await query.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArrayAsync();
|
||||||
|
|||||||
@@ -10,11 +10,108 @@
|
|||||||
|
|
||||||
<MudText Typo="Typo.h3">Assignment</MudText>
|
<MudText Typo="Typo.h3">Assignment</MudText>
|
||||||
|
|
||||||
|
<MudGrid>
|
||||||
|
<MudItem><MudText>Optimized team assignments based on the student event rankings</MudText></MudItem>
|
||||||
|
<MudItem><MudButton StartIcon="@Icons.Material.Filled.Edit" Href="students/event-ranking">Edit Student Event Rankings</MudButton></MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
<MudText>Optimized team assignments based on the student event rankings</MudText>
|
<MudPaper Class="pa-4 mt-5">
|
||||||
|
<MudGrid>
|
||||||
|
<MudItem Style="width:160px;">
|
||||||
|
<MudNumericField @bind-Value="_parameters.TeamSizeLimit"
|
||||||
|
Label="Team Size Limit" Min="3" Max="8"></MudNumericField>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem>
|
||||||
|
<MudTooltip Text="Require at least one On-Site Event">
|
||||||
|
<MudSwitch @bind-Value="_parameters.RequireOnSite" Color="Color.Info"
|
||||||
|
Label="On-Site" />
|
||||||
|
</MudTooltip>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem>
|
||||||
|
<MudTooltip Text="Require at least one Regional Event">
|
||||||
|
<MudSwitch @bind-Value="_parameters.RequireRegional" Color="Color.Info"
|
||||||
|
Label="Regional" />
|
||||||
|
</MudTooltip>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem>
|
||||||
|
<MudStack Style="width:100px;">
|
||||||
|
<MudTooltip Text="Student Event Count Assignment Range">
|
||||||
|
<MudInputLabel>Event Count</MudInputLabel>
|
||||||
|
</MudTooltip>
|
||||||
|
<MudNumericField @bind-Value="_parameters.EventsLowerBound"
|
||||||
|
Label="At Least" Min="2" Max="4"></MudNumericField>
|
||||||
|
|
||||||
|
<MudNumericField @bind-Value="_parameters.EventsUpperBound"
|
||||||
|
Label="Up to" Min="3" Max="5"></MudNumericField>
|
||||||
|
</MudStack>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem>
|
||||||
|
<MudStack Style="width:100px;">
|
||||||
|
<MudTooltip Text="Student Level of Effort Range">
|
||||||
|
<MudInputLabel>LOE</MudInputLabel>
|
||||||
|
</MudTooltip>
|
||||||
|
<MudNumericField @bind-Value="_parameters.EffortLowerBound"
|
||||||
|
Label="At Least" Min="4" Max="7"></MudNumericField>
|
||||||
|
|
||||||
|
<MudNumericField @bind-Value="_parameters.EffortUpperBound"
|
||||||
|
Label="Up to" Min="7" Max="12"></MudNumericField>
|
||||||
|
</MudStack>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem>
|
||||||
|
<MudInputLabel>Assignment Requirements</MudInputLabel>
|
||||||
|
<MudTable T="AssignmentRequirement" ServerData="ReloadAssignmentRequirements" @ref="_assignmentRequirementData">
|
||||||
|
|
||||||
|
<RowTemplate Context="item">
|
||||||
|
<MudTd Class="align-center">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.RemoveCircle" Size="Size.Small"
|
||||||
|
OnClick="() => RemoveRequireEvent(item)"></MudIconButton>
|
||||||
|
</MudTd>
|
||||||
|
<MudTd Class="align-center">
|
||||||
|
@item.Student.FirstName
|
||||||
|
@item.EventDefinition.ShortName
|
||||||
|
@if (item.Requirement == Requirement.Include)
|
||||||
|
{
|
||||||
|
<MudIcon Class="ml-3" Icon="@Icons.Material.Filled.ThumbUp" Size="Size.Small"></MudIcon>
|
||||||
|
}
|
||||||
|
@if (item.Requirement == Requirement.Exclude)
|
||||||
|
{
|
||||||
|
<MudIcon Class="ml-3" Icon="@Icons.Material.Filled.ThumbDownAlt" Size="Size.Small"></MudIcon>
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem>
|
||||||
|
<MudInputLabel>Two Team Events</MudInputLabel>
|
||||||
|
<MudTable T="EventDefinition" ServerData="ReloadEventTwoTeam" @ref="_eventTwoTeamData">
|
||||||
|
|
||||||
|
<RowTemplate Context="item">
|
||||||
|
<MudTd Class="align-center">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.RemoveCircle" Size="Size.Small"
|
||||||
|
OnClick="() => RemoveTwoTeam(item)"></MudIconButton>
|
||||||
|
</MudTd>
|
||||||
|
<MudTd Class="align-center">@item.ShortName</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem>
|
||||||
|
<MudInputLabel>Omitted Events</MudInputLabel>
|
||||||
|
<MudTable T="EventDefinition" ServerData="ReloadOmittedEvents" @ref="_eventOmittedData">
|
||||||
|
|
||||||
|
<RowTemplate Context="item">
|
||||||
|
<MudTd Class="align-center">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.RemoveCircle" Size="Size.Small"
|
||||||
|
OnClick="() => RemoveOmitted(item)"></MudIconButton>
|
||||||
|
</MudTd>
|
||||||
|
<MudTd Class="align-center">@item.ShortName</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
<MudButton Class="ma-3" OnClick="Solve" Variant="Variant.Filled" Color="Color.Primary" Disabled="@_isSolving">Solve</MudButton>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
<MudGrid>
|
<MudGrid>
|
||||||
|
|
||||||
<MudItem xs="12" lg="8">
|
<MudItem xs="12" lg="8">
|
||||||
<MudText Typo="Typo.h4">Students</MudText>
|
<MudText Typo="Typo.h4">Students</MudText>
|
||||||
|
|
||||||
@@ -50,7 +147,7 @@
|
|||||||
</MudTd>
|
</MudTd>
|
||||||
</RowTemplate>
|
</RowTemplate>
|
||||||
<ChildRowContent>
|
<ChildRowContent>
|
||||||
<MudTr><td colspan="4">
|
<MudTr><td colspan="4">
|
||||||
@{
|
@{
|
||||||
var allStudentEvents =
|
var allStudentEvents =
|
||||||
context.Student.EventRankings
|
context.Student.EventRankings
|
||||||
@@ -68,7 +165,7 @@
|
|||||||
var eventRank = context.Student.EventRankings.Find(er => er.EventDefinition == e)?.Rank;
|
var eventRank = context.Student.EventRankings.Find(er => er.EventDefinition == e)?.Rank;
|
||||||
var isAssigned = context.Events.Contains(e);
|
var isAssigned = context.Events.Contains(e);
|
||||||
|
|
||||||
var color = AppIcons.RankedEvent(eventRank ?? 0);
|
var color = AppIcons.RankedEventColor(eventRank ?? 0);
|
||||||
var style = string.Empty;
|
var style = string.Empty;
|
||||||
|
|
||||||
if (isAssigned)
|
if (isAssigned)
|
||||||
@@ -90,7 +187,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<MudPaper Class="d-inline-flex align-center pa-2 mx-3 my-1 border-solid" Style="@(style)">
|
<MudPaper Class="d-inline-flex align-center pa-2 mx-3 my-1 border-solid" Style="@(style)">
|
||||||
@e.ShortName
|
@e.ShortName
|
||||||
|
@AppIcons.EventAttributes(e)
|
||||||
@{
|
@{
|
||||||
var isIncluded = _assignmentRequirements
|
var isIncluded = _assignmentRequirements
|
||||||
.Find(ar =>
|
.Find(ar =>
|
||||||
@@ -134,152 +232,106 @@
|
|||||||
}
|
}
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
}
|
}
|
||||||
<MudDivider Style="border-width:3px" />
|
<MudDivider Style="border-width:3px" />
|
||||||
</td></MudTr>
|
</td></MudTr>
|
||||||
</ChildRowContent>
|
</ChildRowContent>
|
||||||
</MudTable>
|
</MudTable>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" lg="4">
|
<MudItem xs="12" lg="4">
|
||||||
<MudText Typo="Typo.h4">Teams</MudText>
|
<MudText Typo="Typo.h4">TimeSlots</MudText>
|
||||||
<MudTable T="Team" ServerData="SolveAssignments" @ref="_teamData">
|
<MudTable T="Team" ServerData="SolveAssignments" @ref="_teamData">
|
||||||
<ColGroup>
|
<ColGroup>
|
||||||
<col style="width: 200px;" />
|
<col style="width: 200px;" />
|
||||||
<col style="width: 40px; white-space:nowrap" />
|
<col style="width: 40px; white-space:nowrap" />
|
||||||
</ColGroup>
|
</ColGroup>
|
||||||
<HeaderContent>
|
<HeaderContent>
|
||||||
<MudTh>Team</MudTh>
|
<MudTh>Team</MudTh>
|
||||||
<MudTh><MudTooltip Text="Number of Student Rankings, Number of Teams [Eligibility Lower Bound-Upper Bound]"><MudText Style="white-space:nowrap;">R, # [LB-UB]</MudText> </MudTooltip></MudTh>
|
<MudTh><MudTooltip Text="Number of Student Rankings, Number of TimeSlots [Eligibility Lower Bound-Upper Bound]"><MudText Style="white-space:nowrap;">R, # [LB-UB]</MudText> </MudTooltip></MudTh>
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
@{
|
@{
|
||||||
var thresholds = _eventAssignmentThresholds.First(e => e.Event == context.Event);
|
var thresholds = _eventAssignmentThresholds.First(e => e.Event == context.Event);
|
||||||
}
|
}
|
||||||
<MudTd><b>@context.Event.Name</b></MudTd>
|
<MudTd>
|
||||||
<MudTd Style="white-space:nowrap">@thresholds.StudentRankingCount, @thresholds.TeamCount × [@thresholds.LowerBound-@thresholds.UpperBound]</MudTd>
|
<b>@context.Event.Name</b>
|
||||||
|
<span>@AppIcons.EventEffort(context.Event) @AppIcons.EventAttributes(context.Event)</span>
|
||||||
</RowTemplate>
|
</MudTd>
|
||||||
<ChildRowContent>
|
<MudTd Style="white-space:nowrap">
|
||||||
<MudTr>
|
|
||||||
<td colspan="2">
|
@thresholds.StudentRankingCount, [@thresholds.LowerBound-@thresholds.UpperBound] × @thresholds.TeamCount
|
||||||
@foreach (var student in
|
|
||||||
context.Students
|
@if (_eventTwoTeams.Contains(context.Event))
|
||||||
.OrderBy(e =>
|
|
||||||
e.EventRankings
|
|
||||||
.Find(er => er.EventDefinition == context.Event)?.Rank ?? 10)
|
|
||||||
.ThenBy(s => s.Grade + s.TsaYear))
|
|
||||||
{
|
{
|
||||||
var eventRank =
|
<MudIconButton Class="ml-3" Size="Size.Small" Color="Color.Default"
|
||||||
student.EventRankings
|
Icon="@Icons.Material.Filled.PlusOne" Variant="Variant.Filled"
|
||||||
.Find(e => e.EventDefinition == context.Event)?.Rank;
|
OnClick="() => RemoveTwoTeam(context.Event)"></MudIconButton>
|
||||||
var color = AppIcons.RankedEvent(eventRank ?? 0);
|
|
||||||
|
|
||||||
<MudPaper Class="d-inline-flex pa-2 mx-3 my-1" Style="@($"background:{color};")">
|
|
||||||
@student.FirstName
|
|
||||||
</MudPaper>
|
|
||||||
}
|
}
|
||||||
|
else if (thresholds.TeamCount != 2 && context.Event.EventFormat == EventFormat.Team)
|
||||||
|
{
|
||||||
|
<MudIconButton Class="ml-3" Size="Size.Small" Color="Color.Default"
|
||||||
|
Icon="@Icons.Material.Filled.PlusOne"
|
||||||
|
OnClick="() => AddTwoTeam(context.Event)"></MudIconButton>
|
||||||
|
}
|
||||||
|
<MudIconButton Icon="@Icons.Material.Outlined.Remove" Size="Size.Small" Color="Color.Default"
|
||||||
|
OnClick="() => AddOmitted(context.Event)"></MudIconButton>
|
||||||
|
</MudTd>
|
||||||
|
|
||||||
|
</RowTemplate>
|
||||||
|
<ChildRowContent>
|
||||||
|
<MudTr>
|
||||||
|
<td colspan="2">
|
||||||
|
@foreach (var student in
|
||||||
|
context.Students
|
||||||
|
.OrderBy(e =>
|
||||||
|
e.EventRankings
|
||||||
|
.Find(er => er.EventDefinition == context.Event)?.Rank ?? 10)
|
||||||
|
.ThenBy(s => s.Grade + s.TsaYear))
|
||||||
|
{
|
||||||
|
var eventRank =
|
||||||
|
student.EventRankings
|
||||||
|
.Find(e => e.EventDefinition == context.Event)?.Rank;
|
||||||
|
var color = AppIcons.RankedEventColor(eventRank ?? 0);
|
||||||
|
|
||||||
|
<MudPaper Class="d-inline-flex pa-2 mx-3 my-1" Style="@($"background:{color};")">
|
||||||
|
@student.FirstName
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
<MudDivider Style="border-width:3px" />
|
<MudDivider Style="border-width:3px" />
|
||||||
</td>
|
</td>
|
||||||
</MudTr>
|
</MudTr>
|
||||||
</ChildRowContent>
|
</ChildRowContent>
|
||||||
<NoRecordsContent>
|
<NoRecordsContent>
|
||||||
<MudText Color="Color.Warning">Solution status: @_solutionStatus</MudText>
|
<MudText Color="Color.Warning">Solution status: @_solutionStatus</MudText>
|
||||||
</NoRecordsContent>
|
</NoRecordsContent>
|
||||||
<LoadingContent>
|
<LoadingContent>
|
||||||
<MudText>Loading...</MudText>
|
<MudText>Loading...</MudText>
|
||||||
</LoadingContent>
|
</LoadingContent>
|
||||||
</MudTable>
|
</MudTable>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
<MudPaper Class="pa-4 mt-5">
|
|
||||||
<MudGrid>
|
|
||||||
<MudItem Style="width:160px;">
|
|
||||||
<MudNumericField @bind-Value="_parameters.TeamSizeLimit"
|
|
||||||
Label="Team Size Limit" Min="3" Max="8"></MudNumericField>
|
|
||||||
</MudItem>
|
|
||||||
<MudItem>
|
|
||||||
<MudTooltip Text="Require at least one On-Site Event">
|
|
||||||
<MudSwitch @bind-Value="_parameters.RequireOnSite" Color="Color.Info"
|
|
||||||
Label="On-Site"/>
|
|
||||||
</MudTooltip>
|
|
||||||
</MudItem>
|
|
||||||
<MudItem>
|
|
||||||
<MudTooltip Text="Require at least one Regional Event">
|
|
||||||
<MudSwitch @bind-Value="_parameters.RequireRegional" Color="Color.Info"
|
|
||||||
Label="Regional"/>
|
|
||||||
</MudTooltip>
|
|
||||||
</MudItem>
|
|
||||||
<MudItem>
|
|
||||||
<MudStack Style="width:100px;">
|
|
||||||
<MudTooltip Text="Student Event Count Assignment Range">
|
|
||||||
<MudInputLabel>Event Count</MudInputLabel>
|
|
||||||
</MudTooltip>
|
|
||||||
<MudNumericField @bind-Value="_parameters.EventsLowerBound"
|
|
||||||
Label="At Least" Min="2" Max="4"></MudNumericField>
|
|
||||||
|
|
||||||
<MudNumericField @bind-Value="_parameters.EventsUpperBound"
|
|
||||||
Label="Up to" Min="3" Max="5"></MudNumericField>
|
|
||||||
</MudStack>
|
|
||||||
</MudItem>
|
|
||||||
<MudItem>
|
|
||||||
<MudStack Style="width:100px;">
|
|
||||||
<MudTooltip Text="Student Level of Effort Range">
|
|
||||||
<MudInputLabel>LOE</MudInputLabel>
|
|
||||||
</MudTooltip>
|
|
||||||
<MudNumericField @bind-Value="_parameters.EffortLowerBound"
|
|
||||||
Label="At Least" Min="4" Max="7"></MudNumericField>
|
|
||||||
|
|
||||||
<MudNumericField @bind-Value="_parameters.EffortUpperBound"
|
|
||||||
Label="Up to" Min="7" Max="12"></MudNumericField>
|
|
||||||
</MudStack>
|
|
||||||
</MudItem>
|
|
||||||
<MudItem>
|
|
||||||
<MudInputLabel>Assignment Requirements</MudInputLabel>
|
|
||||||
<MudTable T="AssignmentRequirement" ServerData="ReloadAssignmentRequirements" @ref="_assignmentRequirementData">
|
|
||||||
<HeaderContent>
|
|
||||||
<MudTh></MudTh>
|
|
||||||
<MudTh>Student</MudTh>
|
|
||||||
<MudTh>Event</MudTh>
|
|
||||||
</HeaderContent>
|
|
||||||
<RowTemplate Context="item">
|
|
||||||
<MudTd Class="align-center">
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.RemoveCircle"
|
|
||||||
OnClick="() => RemoveRequireEvent(item)"></MudIconButton>
|
|
||||||
</MudTd>
|
|
||||||
<MudTd Class="align-center">@item.Student.FirstName</MudTd>
|
|
||||||
<MudTd Class="align-center">
|
|
||||||
@item.EventDefinition.ShortName
|
|
||||||
@if (item.Requirement == Requirement.Include)
|
|
||||||
{
|
|
||||||
<MudIcon Class="ml-3" Icon="@Icons.Material.Filled.ThumbUp" Size="Size.Small"></MudIcon>
|
|
||||||
}
|
|
||||||
@if (item.Requirement == Requirement.Exclude)
|
|
||||||
{
|
|
||||||
<MudIcon Class="ml-3" Icon="@Icons.Material.Filled.ThumbDownAlt" Size="Size.Small"></MudIcon>
|
|
||||||
}
|
|
||||||
</MudTd>
|
|
||||||
</RowTemplate>
|
|
||||||
</MudTable>
|
|
||||||
</MudItem>
|
|
||||||
</MudGrid>
|
|
||||||
<MudButton OnClick="Solve" Variant="Variant.Filled" Disabled="@_isSolving">Solve</MudButton>
|
|
||||||
</MudPaper>
|
|
||||||
|
|
||||||
<MudButton StartIcon="@Icons.Material.Filled.Edit" Href="students/event-ranking">Edit Student Event Rankings</MudButton>
|
|
||||||
|
|
||||||
|
<MudTooltip Text="This will overwrite existing teams">
|
||||||
|
<MudButton Class="ma-3" StartIcon="@Icons.Material.Filled.Warning" Variant="Variant.Filled" Size="Size.Large" OnClick="SaveTeams" Color="Color.Warning">Save</MudButton>
|
||||||
|
</MudTooltip>
|
||||||
@code {
|
@code {
|
||||||
public bool TestSwitch { get; set; } = false;
|
public bool TestSwitch { get; set; } = false;
|
||||||
|
|
||||||
private readonly AssignmentParameters _parameters = new () {LimitTeamsToOne = false};
|
private readonly AssignmentParameters _parameters = new() { RequireOnSite = false, RequireRegional = false };
|
||||||
|
|
||||||
private List<EventDefinition>? _events;
|
private List<EventDefinition>? _events;
|
||||||
private List<Student>? _students;
|
private List<Student>? _students;
|
||||||
private List<EventAssignmentThresholds> _eventAssignmentThresholds = [];
|
private List<EventAssignmentThresholds> _eventAssignmentThresholds = [];
|
||||||
|
|
||||||
MudTable<Team> _teamData;
|
MudTable<Team> _teamData;
|
||||||
|
private Team[] _teams = [];
|
||||||
MudTable<StudentEventStatistics> _statisticData;
|
MudTable<StudentEventStatistics> _statisticData;
|
||||||
private List<StudentEventStatistics> _statistics = [];
|
private List<StudentEventStatistics> _statistics = [];
|
||||||
MudTable<AssignmentRequirement> _assignmentRequirementData;
|
MudTable<AssignmentRequirement> _assignmentRequirementData;
|
||||||
private List<AssignmentRequirement> _assignmentRequirements = [];
|
private List<AssignmentRequirement> _assignmentRequirements = [];
|
||||||
|
MudTable<EventDefinition> _eventTwoTeamData;
|
||||||
|
private List<EventDefinition> _eventTwoTeams = [];
|
||||||
|
MudTable<EventDefinition> _eventOmittedData;
|
||||||
|
private List<EventDefinition> _eventOmitted = [];
|
||||||
|
|
||||||
private string _solutionStatus = string.Empty;
|
private string _solutionStatus = string.Empty;
|
||||||
|
|
||||||
@@ -302,7 +354,7 @@
|
|||||||
|
|
||||||
private async Task AddTeam()
|
private async Task AddTeam()
|
||||||
{
|
{
|
||||||
//Context.Teams.Add(Team);
|
//Context.TimeSlots.Add(Team);
|
||||||
await Context.SaveChangesAsync();
|
await Context.SaveChangesAsync();
|
||||||
//NavigationManager.NavigateTo("/teams");
|
//NavigationManager.NavigateTo("/teams");
|
||||||
}
|
}
|
||||||
@@ -320,6 +372,10 @@
|
|||||||
{
|
{
|
||||||
eventAssignment.AddAssignmentRequirement(requirement);
|
eventAssignment.AddAssignmentRequirement(requirement);
|
||||||
}
|
}
|
||||||
|
eventAssignment.RemoveEvents(_eventOmitted);
|
||||||
|
|
||||||
|
eventAssignment.AllowTwoTeams(_eventTwoTeams);
|
||||||
|
|
||||||
var solution = await eventAssignment.Solve();
|
var solution = await eventAssignment.Solve();
|
||||||
_solutionStatus = solution.Status;
|
_solutionStatus = solution.Status;
|
||||||
_statistics =
|
_statistics =
|
||||||
@@ -330,7 +386,9 @@
|
|||||||
await _statisticData.ReloadServerData();
|
await _statisticData.ReloadServerData();
|
||||||
_isSolving = false;
|
_isSolving = false;
|
||||||
await InvokeAsync(StateHasChanged); // let the UI know that the solution has been found
|
await InvokeAsync(StateHasChanged); // let the UI know that the solution has been found
|
||||||
return new TableData<Team> { Items = solution.Teams };
|
_teams = solution.Teams;
|
||||||
|
|
||||||
|
return new TableData<Team> { Items = _teams };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<TableData<StudentEventStatistics>> ReloadStatistics(TableState arg1, CancellationToken arg2)
|
private async Task<TableData<StudentEventStatistics>> ReloadStatistics(TableState arg1, CancellationToken arg2)
|
||||||
@@ -343,6 +401,17 @@
|
|||||||
return new TableData<AssignmentRequirement> { Items = _assignmentRequirements };
|
return new TableData<AssignmentRequirement> { Items = _assignmentRequirements };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<TableData<EventDefinition>> ReloadEventTwoTeam(TableState arg1, CancellationToken arg2)
|
||||||
|
{
|
||||||
|
return new TableData<EventDefinition> { Items = _eventTwoTeams };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task<TableData<EventDefinition>> ReloadOmittedEvents(TableState arg1, CancellationToken arg2)
|
||||||
|
{
|
||||||
|
return new TableData<EventDefinition> { Items = _eventOmitted };
|
||||||
|
}
|
||||||
|
|
||||||
private void RequireEvent(EventDefinition evt, Student student, Requirement requirement)
|
private void RequireEvent(EventDefinition evt, Student student, Requirement requirement)
|
||||||
{
|
{
|
||||||
_assignmentRequirements.Add(new AssignmentRequirement(evt, student, requirement));
|
_assignmentRequirements.Add(new AssignmentRequirement(evt, student, requirement));
|
||||||
@@ -362,4 +431,39 @@
|
|||||||
_assignmentRequirements.Remove(assignmentRequirement);
|
_assignmentRequirements.Remove(assignmentRequirement);
|
||||||
_assignmentRequirementData.ReloadServerData();
|
_assignmentRequirementData.ReloadServerData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddTwoTeam(EventDefinition evt)
|
||||||
|
{
|
||||||
|
_eventTwoTeams.Add(evt);
|
||||||
|
_eventTwoTeamData.ReloadServerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveTwoTeam(EventDefinition evt)
|
||||||
|
{
|
||||||
|
_eventTwoTeams.Remove(evt);
|
||||||
|
_eventTwoTeamData.ReloadServerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void AddOmitted(EventDefinition evt)
|
||||||
|
{
|
||||||
|
_eventOmitted.Add(evt);
|
||||||
|
_eventOmittedData.ReloadServerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveOmitted(EventDefinition evt)
|
||||||
|
{
|
||||||
|
_eventOmitted.Remove(evt);
|
||||||
|
_eventOmittedData.ReloadServerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveTeams()
|
||||||
|
{
|
||||||
|
var teams = await Context.Teams.ExecuteDeleteAsync();
|
||||||
|
|
||||||
|
await Context.Teams.AddRangeAsync(_teams);
|
||||||
|
await Context.SaveChangesAsync();
|
||||||
|
|
||||||
|
NavigationManager.NavigateTo("/teams");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<MudSelectItem T="EventDefinition" Value="@(evt)"></MudSelectItem>
|
<MudSelectItem T="EventDefinition" Value="@(evt)"></MudSelectItem>
|
||||||
}
|
}
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
<MudTextField T="string" Label="Name" @bind-Value="Team.Name" For="@(() => Team.Name)"></MudTextField>
|
<MudTextField T="int?" Label="Number" @bind-Value="Team.Number" For="@(() => Team.Number)"></MudTextField>
|
||||||
|
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
@@ -46,7 +46,9 @@
|
|||||||
|
|
||||||
private async Task AddTeam()
|
private async Task AddTeam()
|
||||||
{
|
{
|
||||||
|
Team.TeamId = Team.Event.Name;
|
||||||
Context.Teams.Add(Team);
|
Context.Teams.Add(Team);
|
||||||
|
|
||||||
await Context.SaveChangesAsync();
|
await Context.SaveChangesAsync();
|
||||||
NavigationManager.NavigateTo("/teams");
|
NavigationManager.NavigateTo("/teams");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
@page "/teams/delete"
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@inject AppDbContext context
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
<PageTitle>Delete Team</PageTitle>
|
||||||
|
|
||||||
|
<h1>Delete</h1>
|
||||||
|
|
||||||
|
<p>Are you sure you want to delete this?</p>
|
||||||
|
<div>
|
||||||
|
<h2>Team</h2>
|
||||||
|
<hr />
|
||||||
|
@if (team is null)
|
||||||
|
{
|
||||||
|
<p><em>Loading...</em></p>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-sm-2">Team</dt>
|
||||||
|
<dd class="col-sm-10">@team.Event.Name</dd>
|
||||||
|
</dl>
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-sm-2">Students</dt>
|
||||||
|
<dd class="col-sm-10">@string.Join(",", team.Students.Select(e => e.Name))</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<EditForm method="post" Model="team" OnValidSubmit="DeleteTeam" FormName="delete" Enhance>
|
||||||
|
<button type="submit" class="btn btn-danger" disabled="@(team is null)">Delete</button> |
|
||||||
|
<a href="/teams">Back to List</a>
|
||||||
|
</EditForm>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private Team? team;
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery]
|
||||||
|
private int Id { get; set; }
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
team = await context.Teams
|
||||||
|
.Include(e => e.Event)
|
||||||
|
.Include(e => e.Students)
|
||||||
|
.FirstOrDefaultAsync(m => m.Id == Id);
|
||||||
|
|
||||||
|
if (team is null)
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo("notfound");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteTeam()
|
||||||
|
{
|
||||||
|
context.Teams.Remove(team!);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
NavigationManager.NavigateTo("/teams");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
@page "/teams/edit"
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@inject AppDbContext Context
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
<PageTitle>Edit Team - TSA Chapter Organizer</PageTitle>
|
||||||
|
|
||||||
|
<MudText Typo="Typo.h3">Edit</MudText>
|
||||||
|
<MudText Typo="Typo.h4">Team@(Team == null ? "" : $" ({Team.Event.Name} #{Team.Number})")</MudText>
|
||||||
|
|
||||||
|
@if (Team is null)
|
||||||
|
{
|
||||||
|
<p><em>Loading...</em></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<EditForm method="post" Model="Team" OnValidSubmit="UpdateTeam" FormName="edit" Enhance>
|
||||||
|
<DataAnnotationsValidator/>
|
||||||
|
<MudGrid>
|
||||||
|
<MudItem xs="12" sm="7">
|
||||||
|
<MudPaper Class="pa-4">
|
||||||
|
<MudSelect
|
||||||
|
T="Student"
|
||||||
|
MultiSelection="true"
|
||||||
|
@bind-SelectedValues="@_selectedStudents"
|
||||||
|
ToStringFunc="e => e.Name"
|
||||||
|
Label="Students">
|
||||||
|
|
||||||
|
@foreach (var student in _students.OrderBy(e => e.FirstName))
|
||||||
|
{
|
||||||
|
<MudSelectItem T="Student" Value="@student">@student.Name</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
<MudTextField T="int?" Label="Number" @bind-Value="Team.Number" For="@(() => Team.Number)" Required="false" Clearable="true"></MudTextField>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="teams">Back</MudButton>
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.Save" OnClick="UpdateTeam">Save</MudButton>
|
||||||
|
</EditForm>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[SupplyParameterFromQuery]
|
||||||
|
private int Id { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromForm]
|
||||||
|
private Team? Team { get; set; }
|
||||||
|
|
||||||
|
private IEnumerable<Student> _selectedStudents = new HashSet<Student>();
|
||||||
|
private List<Student> _students = [];
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
Team ??= await Context.Teams
|
||||||
|
.Include(e => e.Event)
|
||||||
|
.Include(e => e.Students)
|
||||||
|
.FirstOrDefaultAsync(m => m.Id == Id);
|
||||||
|
_students = await Context.Students.ToListAsync();
|
||||||
|
foreach (var s in Team.Students)
|
||||||
|
{
|
||||||
|
((HashSet<Student>)_selectedStudents).Add(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Team is null)
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo("notfound");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// To protect from overposting attacks, enable the specific properties you want to bind to.
|
||||||
|
// For more information, see https://learn.microsoft.com/aspnet/core/blazor/forms/#mitigate-overposting-attacks.
|
||||||
|
private async Task UpdateTeam()
|
||||||
|
{
|
||||||
|
//Context.Attach(Team!).Entity = EntityState.Modified;
|
||||||
|
Team.Students.Clear();
|
||||||
|
foreach (var s in _selectedStudents)
|
||||||
|
{
|
||||||
|
Team.Students.Add(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
catch (DbUpdateConcurrencyException)
|
||||||
|
{
|
||||||
|
if (!TeamExists(Team!.Id))
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo("notfound");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationManager.NavigateTo("/teams");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TeamExists(int id)
|
||||||
|
{
|
||||||
|
return Context.Teams.Any(e => e.Id == id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,26 +3,38 @@
|
|||||||
@using WebApp.Models
|
@using WebApp.Models
|
||||||
@inject AppDbContext Context
|
@inject AppDbContext Context
|
||||||
|
|
||||||
<PageTitle>Teams</PageTitle>
|
<PageTitle>TimeSlots</PageTitle>
|
||||||
|
|
||||||
<MudText Typo="Typo.h3">Teams</MudText>
|
<MudText Typo="Typo.h3">Teams</MudText>
|
||||||
|
|
||||||
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="teams/create">Create New</MudButton>
|
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="teams/create">Create New</MudButton>
|
||||||
<MudButton StartIcon="@Icons.Material.Filled.Assignment" Href="teams/assignment">Assignment</MudButton>
|
<MudButton StartIcon="@Icons.Material.Filled.Assignment" Href="teams/assignment">Assignment</MudButton>
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.Print" Href="teams/printout">Printout</MudButton>
|
||||||
|
|
||||||
<MudDataGrid T="Team" ServerData="ServerReload" @ref="_dataGrid" Filterable="true" RowsPerPage="25">
|
<MudDataGrid T="Team" ServerData="ServerReload" @ref="_dataGrid" Filterable="true" RowsPerPage="35">
|
||||||
<Columns>
|
<Columns>
|
||||||
<PropertyColumn Property="@(e => e.Name)" Title="Name" />
|
<PropertyColumn Property="@(e => e.ToString())" Title="Event" />
|
||||||
<PropertyColumn Property="@(e => e.Event.Name)" Title="Event" />
|
|
||||||
@* <TemplateColumn Title="Name" SortBy="e => e.FirstName" Sortable="true">
|
<TemplateColumn Title="Students">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.Name
|
@foreach (var student in
|
||||||
@if (context.Item.OfficerRole != null)
|
context.Item.Students
|
||||||
|
.OrderBy(e =>
|
||||||
|
e.EventRankings
|
||||||
|
.Find(er => er.EventDefinition == context.Item.Event)?.Rank ?? 10)
|
||||||
|
.ThenBy(s => s.Grade + s.TsaYear))
|
||||||
{
|
{
|
||||||
<MudChip T="string" Icon="@(AppIcons.OfficerRoleIcon(context.Item.OfficerRole.Value))">@context.Item.OfficerRole</MudChip>
|
var eventRank =
|
||||||
|
student.EventRankings
|
||||||
|
.Find(e => e.EventDefinition == context.Item.Event)?.Rank;
|
||||||
|
var color = AppIcons.RankedEventColor(eventRank ?? 0);
|
||||||
|
|
||||||
|
<MudPaper Class="d-inline-flex pa-2 mx-2 my-1" Style="@($"background:{color};")">
|
||||||
|
@student.FirstName
|
||||||
|
</MudPaper>
|
||||||
}
|
}
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn> *@
|
</TemplateColumn>
|
||||||
@* <TemplateColumn Title="Grade (TSA Year)" SortBy="e => e.Grade" Sortable="true">
|
@* <TemplateColumn Title="Grade (TSA Year)" SortBy="e => e.Grade" Sortable="true">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.Grade (@context.Item.TsaYear)
|
@context.Item.Grade (@context.Item.TsaYear)
|
||||||
@@ -60,6 +72,9 @@
|
|||||||
= Context.Teams
|
= Context.Teams
|
||||||
.Include(e => e.Event)
|
.Include(e => e.Event)
|
||||||
.Include(e => e.Students)
|
.Include(e => e.Students)
|
||||||
|
.ThenInclude(e => e.EventRankings)
|
||||||
|
.OrderBy(e => e.Event.Name)
|
||||||
|
.ThenBy(e => e.Number)
|
||||||
.Where(state.FilterDefinitions)
|
.Where(state.FilterDefinitions)
|
||||||
.OrderBy(state.SortDefinitions);
|
.OrderBy(state.SortDefinitions);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,307 @@
|
|||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using WebApp.Models
|
||||||
|
@page "/teams/printout"
|
||||||
|
@inject IConfiguration Configuration
|
||||||
|
@inject AppDbContext Context
|
||||||
|
|
||||||
|
<PageTitle>@Configuration["ChapterSettings:Shortname"] TSA Teams @Configuration["ChapterSettings:CompetitionYear"]</PageTitle>
|
||||||
|
|
||||||
|
<MudText Typo="Typo.h3">@Configuration["ChapterSettings:Shortname"] TSA Teams @Configuration["ChapterSettings:CompetitionYear"]</MudText>
|
||||||
|
<MudText Typo="Typo.h5" Class="mb-4">Yearly theme: Unity Through Community</MudText>
|
||||||
|
|
||||||
|
@if (_teams == null)
|
||||||
|
{
|
||||||
|
<p><em>Loading...</em></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudContainer Class="mt-3 mb-1 nobrk">
|
||||||
|
|
||||||
|
<MudTable Items="_teams">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>Team</MudTh>
|
||||||
|
<MudTh></MudTh>
|
||||||
|
@for (var i = 0; i <= _maxTeamSize; i++)
|
||||||
|
{
|
||||||
|
<MudTh></MudTh>
|
||||||
|
}
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
|
||||||
|
<MudTd>
|
||||||
|
@context.ToString()
|
||||||
|
</MudTd>
|
||||||
|
<MudTd>
|
||||||
|
@AppIcons.EventEffort(context.Event)
|
||||||
|
@AppIcons.EventAttributes(context.Event)
|
||||||
|
</MudTd>
|
||||||
|
|
||||||
|
@{
|
||||||
|
var students
|
||||||
|
= context.Students
|
||||||
|
.OrderBy(s => s.EventRankings.Find(e => e.EventDefinition == context.Event)?.Rank ?? int.MaxValue)
|
||||||
|
.ThenByDescending(e => e.Grade)
|
||||||
|
.ThenBy(e => e.FirstName)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@for (var i = 0; i <= _maxTeamSize; i++)
|
||||||
|
{
|
||||||
|
var student = i < students.Length ? students[i] : null;
|
||||||
|
if (student != null)
|
||||||
|
{
|
||||||
|
var rank = student.EventRankings
|
||||||
|
.Find(e => e.EventDefinition == context.Event)?.Rank ?? int.MaxValue;
|
||||||
|
|
||||||
|
<MudTd Class="@($"event-rank-{rank}")">
|
||||||
|
@student.Name
|
||||||
|
</MudTd>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTd></MudTd>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<MudTd>
|
||||||
|
@if (context.Event.EventFormat == EventFormat.Team)
|
||||||
|
{
|
||||||
|
@if (context.Students.Count < context.Event.MinTeamSize)
|
||||||
|
{
|
||||||
|
<span>Min Team Size: @context.Event.MinTeamSize</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (context.Students.Count > context.Event.MaxTeamSize)
|
||||||
|
{
|
||||||
|
<span>Max Team Size: @context.Event.MaxTeamSize</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (context.Event.EventFormat == EventFormat.Individual
|
||||||
|
&& context.Students.Count > context.Event.MaxTeamCountState)
|
||||||
|
{
|
||||||
|
<span>Max Team Count State: @context.Event.MaxTeamCountState</span>
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
|
||||||
|
</MudContainer>
|
||||||
|
|
||||||
|
<MudContainer Class="mt-3 mb-1 nobrk pagebreak">
|
||||||
|
|
||||||
|
<MudTable Items="_students">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>Student</MudTh>
|
||||||
|
<MudTh>Effort</MudTh>
|
||||||
|
@for (var i = 0; i <= 5; i++)
|
||||||
|
{
|
||||||
|
<MudTh></MudTh>
|
||||||
|
}
|
||||||
|
<MudTh></MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
|
||||||
|
<MudTd>@context.Name</MudTd>
|
||||||
|
<MudTd>@context.Teams.Sum(e => e.Event.LevelOfEffort)</MudTd>
|
||||||
|
@{
|
||||||
|
var teams = context.Teams
|
||||||
|
.OrderBy(e =>
|
||||||
|
context.EventRankings.Find(ser => ser.EventDefinition == e.Event)?.Rank ?? int.MaxValue
|
||||||
|
).ToArray();
|
||||||
|
}
|
||||||
|
@for (var i = 0; i <= 5; i++)
|
||||||
|
{
|
||||||
|
var team = i < teams.Length ? teams[i] : null;
|
||||||
|
|
||||||
|
@if (team != null)
|
||||||
|
{
|
||||||
|
var rank = context.EventRankings
|
||||||
|
.Find(e => e.EventDefinition == team.Event)?.Rank ?? int.MaxValue;
|
||||||
|
<MudTh Class="@($"event-rank-{rank}")">
|
||||||
|
@team.ToString()
|
||||||
|
@AppIcons.EventEffort(team.Event)
|
||||||
|
@AppIcons.EventAttributes(team.Event)
|
||||||
|
|
||||||
|
@if (rank == int.MaxValue)
|
||||||
|
{
|
||||||
|
<span>❔</span>
|
||||||
|
}
|
||||||
|
</MudTh>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTh></MudTh>
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
<MudTd>
|
||||||
|
@if (!context.Teams.Select(e => e.Event).Any(re => re.OnSiteActivity))
|
||||||
|
{
|
||||||
|
<span>No On-Site Activity</span>
|
||||||
|
}
|
||||||
|
@if (!context.Teams.Select(e => e.Event).Any(re => re.RegionalEvent))
|
||||||
|
{
|
||||||
|
<span>No Regional Event</span>
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
|
||||||
|
</MudContainer>
|
||||||
|
|
||||||
|
|
||||||
|
<MudContainer Class="mt-3 mb-1 nobrk pagebreak">
|
||||||
|
|
||||||
|
<MudTable Items="_students">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>Student</MudTh>
|
||||||
|
<MudTh>Grade, TSA Year</MudTh>
|
||||||
|
<MudTh>Level of Effort</MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
|
||||||
|
<MudTd>@context.Name</MudTd>
|
||||||
|
<MudTd>@AppIcons.GetOrdinal(context.Grade), @context.TsaYear</MudTd>
|
||||||
|
<MudTd>@context.Teams.Sum(e => e.Event.LevelOfEffort)
|
||||||
|
@if (!context.Teams.Select(e => e.Event).Any(re => re.OnSiteActivity))
|
||||||
|
{
|
||||||
|
<span>No On-Site Activity</span>
|
||||||
|
}
|
||||||
|
@if (!context.Teams.Select(e => e.Event).Any(re => re.RegionalEvent))
|
||||||
|
{
|
||||||
|
<span>No Regional Event</span>
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
<ChildRowContent>
|
||||||
|
<MudTr>
|
||||||
|
<MudTable Items="@context.Teams.OrderBy(e =>
|
||||||
|
context.EventRankings.Find(ser => ser.EventDefinition == e.Event)?.Rank ?? int.MaxValue
|
||||||
|
)" Context="team">
|
||||||
|
<RowTemplate>
|
||||||
|
@{ var rank = context.EventRankings
|
||||||
|
.Find(e => e.EventDefinition == team.Event)?.Rank ?? int.MaxValue; }
|
||||||
|
<MudTd Class="@($"event-rank-{rank}")">
|
||||||
|
@team.ToString()
|
||||||
|
@AppIcons.EventEffort(team.Event)
|
||||||
|
@AppIcons.EventAttributes(team.Event)
|
||||||
|
|
||||||
|
@if (rank == int.MaxValue)
|
||||||
|
{
|
||||||
|
<span>❔</span>
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
<MudTd>@string.Join(", ", team.Students.Where(e => e != context).Select(e => e.FirstName))</MudTd>
|
||||||
|
<MudTd></MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
</MudTr>
|
||||||
|
</ChildRowContent>
|
||||||
|
</MudTable>
|
||||||
|
|
||||||
|
</MudContainer>
|
||||||
|
|
||||||
|
<MudContainer>
|
||||||
|
@foreach (var studentForEvents in _students)
|
||||||
|
{
|
||||||
|
<MudContainer Class="pagebreak">
|
||||||
|
<MudText Typo="Typo.h4">@studentForEvents.Name</MudText>
|
||||||
|
@foreach (var team in studentForEvents.Teams)
|
||||||
|
{
|
||||||
|
<MudContainer Class="mt-3 mb-1 nobrk">
|
||||||
|
<MudGrid>
|
||||||
|
<MudItem xs="6">
|
||||||
|
<MudStack>
|
||||||
|
<MudItem>
|
||||||
|
<MudText Class="d-flex py-1" Typo="Typo.h5">
|
||||||
|
@team.ToString()
|
||||||
|
</MudText>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
@if (team.Event.RegionalEvent)
|
||||||
|
{
|
||||||
|
<MudItem>
|
||||||
|
<MudText Class="d-flex" Typo="Typo.caption"><i>Regional Event</i></MudText>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="2">
|
||||||
|
<MudText>
|
||||||
|
<strong>@team.Event.EventFormat</strong>
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="1">
|
||||||
|
<strong>Effort</strong>: @AppIcons.LevelOfEffortIcon(@team.Event.LevelOfEffort)
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="3">
|
||||||
|
<strong>Activity</strong>: @team.Event.SemifinalistActivity
|
||||||
|
</MudItem>
|
||||||
|
@if (team.Event.EventFormat == EventFormat.Team)
|
||||||
|
{
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudText Class="d-flex py-1" Typo="Typo.h6">
|
||||||
|
Team Members: @string.Join(", ", team.Students.OrderByDescending(e => e.Grade + e.TsaYear).Select(e => e.FirstName))
|
||||||
|
</MudText>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@team.Event.Description</MudText>
|
||||||
|
</MudItem>
|
||||||
|
@if (!string.IsNullOrEmpty(team.Event.Theme))
|
||||||
|
{
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudText Class="d-flex py-1">
|
||||||
|
<i>Theme for 2025-26:</i>
|
||||||
|
</MudText>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="8">
|
||||||
|
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@team.Event.Theme</MudText>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(team.Event.Documentation))
|
||||||
|
{
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudText Class="d-flex py-1">
|
||||||
|
<i>Materials:</i>
|
||||||
|
</MudText>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="8">
|
||||||
|
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@team.Event.Documentation</MudText>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
</MudGrid>
|
||||||
|
</MudContainer>
|
||||||
|
<MudDivider/>
|
||||||
|
}
|
||||||
|
</MudContainer>
|
||||||
|
}
|
||||||
|
</MudContainer>
|
||||||
|
}
|
||||||
|
@code {
|
||||||
|
private Team[]? _teams;
|
||||||
|
private int _maxTeamSize;
|
||||||
|
private Student[]? _students;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_teams
|
||||||
|
= await Context.Teams
|
||||||
|
.Include(e => e.Event)
|
||||||
|
.Include(e => e.Students)
|
||||||
|
.OrderBy(e => e.Event.Name)
|
||||||
|
.ThenBy(e => e.Number)
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
_maxTeamSize = _teams.Max(t => t.Students.Count);
|
||||||
|
_students =
|
||||||
|
await Context.Students
|
||||||
|
.Include(e => e.Teams)
|
||||||
|
.ThenInclude(e => e.Captain)
|
||||||
|
.Include(e => e.EventRankings)
|
||||||
|
.ThenInclude(e => e.EventDefinition)
|
||||||
|
.OrderBy(e => e.FirstName).ToArrayAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
@using Core.Calculation
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@page "/teams/scheduler"
|
||||||
|
@inject IConfiguration Configuration
|
||||||
|
@inject AppDbContext Context
|
||||||
|
|
||||||
|
<PageTitle>@Configuration["ChapterSettings:Shortname"] TSA Schedule @Configuration["ChapterSettings:CompetitionYear"]</PageTitle>
|
||||||
|
|
||||||
|
<MudText Typo="Typo.h3">@Configuration["ChapterSettings:Shortname"] TSA Schedule @Configuration["ChapterSettings:CompetitionYear"]</MudText>
|
||||||
|
|
||||||
|
<MudItem xs="12" lg="4">
|
||||||
|
<MudText Typo="Typo.h4">Time Slots</MudText>
|
||||||
|
<MudTable T="Team[]" ServerData="SolveSchedule" @ref="_solutionData">
|
||||||
|
<HeaderContent>
|
||||||
|
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd>
|
||||||
|
@foreach (var t in context)
|
||||||
|
{
|
||||||
|
<MudItem>
|
||||||
|
@t.ToString() -
|
||||||
|
@string.Join(", ", t.Students)
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
@foreach (var overlap in TeamSchedulerSolution.GetStudentTeamOverlaps(context))
|
||||||
|
{
|
||||||
|
<MudItem>
|
||||||
|
@string.Join(", ", overlap.Item1)
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private Team[]? _teams;
|
||||||
|
private Student[]? _students;
|
||||||
|
MudTable<Team[]> _solutionData;
|
||||||
|
private TeamSchedulerSolution _solution;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_teams
|
||||||
|
= await Context.Teams
|
||||||
|
.Include(e => e.Event)
|
||||||
|
.Include(e => e.Students)
|
||||||
|
.OrderBy(e => e.Event.Name)
|
||||||
|
.ThenBy(e => e.Number)
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
_students =
|
||||||
|
await Context.Students
|
||||||
|
.Include(e => e.Teams)
|
||||||
|
.ThenInclude(e => e.Captain)
|
||||||
|
.Include(e => e.EventRankings)
|
||||||
|
.ThenInclude(e => e.EventDefinition)
|
||||||
|
.OrderBy(e => e.FirstName).ToArrayAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<TableData<Team[]>> SolveSchedule(TableState arg1, CancellationToken arg2)
|
||||||
|
{
|
||||||
|
//_isSolving = true;
|
||||||
|
|
||||||
|
var scheduleOptions =
|
||||||
|
new TeamSchedulerOptions(
|
||||||
|
timeSlots: 4,
|
||||||
|
mustIncludeEvents:
|
||||||
|
[
|
||||||
|
// "Medical Technology", "Electrical Applications" , "RegionalTeam",
|
||||||
|
// ,"Dragster", "Flight"
|
||||||
|
],
|
||||||
|
extended:
|
||||||
|
[
|
||||||
|
// "Invention", "Construction Challenge", "Mechanical", "Mass", "Micro"
|
||||||
|
//"STEM"
|
||||||
|
//"Community", "Vlogging"// "Microcontroller"
|
||||||
|
],
|
||||||
|
omittedEvents:
|
||||||
|
[
|
||||||
|
// "Vlogging", "Junior", "Community Service Video", "Digital Photography",
|
||||||
|
// "STEM"
|
||||||
|
|
||||||
|
//"Leadership",// "Electrical", //"Construction"
|
||||||
|
// "Forensic",
|
||||||
|
//"CAD"
|
||||||
|
//"I&I Team 1", "I&I Team 2"//, "Website Design",
|
||||||
|
],
|
||||||
|
absentStudents:
|
||||||
|
[
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var mustIncludeTeams = _teams;
|
||||||
|
mustIncludeTeams = mustIncludeTeams.Where(t => t.Event.EventFormat == EventFormat.Team).ToArray();
|
||||||
|
|
||||||
|
var teamScheduler = new TeamScheduler(mustIncludeTeams, scheduleOptions.TimeSlots);
|
||||||
|
|
||||||
|
// teamScheduler
|
||||||
|
// .ScheduleSeparate(
|
||||||
|
// _teams.First(e => e.Event.Name.Contains("Data Science")),
|
||||||
|
// _teams.First(e => e.Event.Name.Contains("Microcontroller Design"))
|
||||||
|
// );
|
||||||
|
|
||||||
|
_solution = teamScheduler.Solve();
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged); // let the UI know that the solution has been found
|
||||||
|
|
||||||
|
return new TableData<Team[]> { Items = _solution.TimeSlots};
|
||||||
|
}
|
||||||
|
}
|
||||||
+71
-22
@@ -5,34 +5,62 @@ namespace WebApp.Models
|
|||||||
{
|
{
|
||||||
public static class AppIcons
|
public static class AppIcons
|
||||||
{
|
{
|
||||||
private const string Prefix = "@Icons.Material.Filled.";
|
public static string EventRank = Icons.Material.Filled.AddChart;
|
||||||
|
public static string Teams = Icons.Material.Filled.Group;
|
||||||
public static string LevelOfEffortIcon(int loe)
|
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 LevelOfEffortIcon(int? loe)
|
||||||
{
|
{
|
||||||
|
|
||||||
return loe switch
|
return loe switch
|
||||||
{
|
{
|
||||||
1 => MudBlazor.Icons.Material.Filled.StarBorder,
|
1 => "①",
|
||||||
2 => MudBlazor.Icons.Material.Filled.StarHalf,
|
2 => "②",
|
||||||
3 => MudBlazor.Icons.Material.Filled.Star,
|
3 => "③",
|
||||||
_ => MudBlazor.Icons.Material.Filled.QuestionMark
|
_ => Icons.Material.Filled.QuestionMark
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string OnSiteActivity = "ⓐ";
|
||||||
|
public static string RegionalEvent = "ⓡ";
|
||||||
|
public static string IndividualEvent = "ⓘ";
|
||||||
|
public static string QuestionMark = "❔";
|
||||||
|
|
||||||
|
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.OnSiteActivity)
|
||||||
|
v.Add(OnSiteActivity);
|
||||||
|
if (eventDefinition.RegionalEvent)
|
||||||
|
v.Add(RegionalEvent);
|
||||||
|
|
||||||
|
return string.Join(" ", v);
|
||||||
|
}
|
||||||
|
|
||||||
public static string OfficerRoleIcon(OfficerRole officerRole)
|
public static string OfficerRoleIcon(OfficerRole officerRole)
|
||||||
{
|
{
|
||||||
return officerRole switch
|
return officerRole switch
|
||||||
{
|
{
|
||||||
OfficerRole.President => MudBlazor.Icons.Material.Filled.Gavel,
|
OfficerRole.President => Icons.Material.Filled.Gavel,
|
||||||
OfficerRole.VicePresident => MudBlazor.Icons.Material.Filled.StarBorderPurple500,
|
OfficerRole.VicePresident => Icons.Material.Filled.StarBorderPurple500,
|
||||||
OfficerRole.Secretary => MudBlazor.Icons.Material.Filled.Draw,
|
OfficerRole.Secretary => Icons.Material.Filled.Draw,
|
||||||
OfficerRole.Treasurer => MudBlazor.Icons.Material.Filled.Key,
|
OfficerRole.Treasurer => Icons.Material.Filled.Key,
|
||||||
OfficerRole.Reporter => MudBlazor.Icons.Material.Filled.Mic,
|
OfficerRole.Reporter => Icons.Material.Filled.Mic,
|
||||||
OfficerRole.SergeantAtArms => MudBlazor.Icons.Material.Filled.Handshake,
|
OfficerRole.SergeantAtArms => Icons.Material.Filled.Handshake,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(officerRole), officerRole, null)
|
_ => throw new ArgumentOutOfRangeException(nameof(officerRole), officerRole, null)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string RankedEvent(int rank)
|
public static string RankedEventColor(int rank)
|
||||||
{
|
{
|
||||||
return rank switch
|
return rank switch
|
||||||
{
|
{
|
||||||
@@ -42,16 +70,37 @@ namespace WebApp.Models
|
|||||||
4 => "#ffe599",
|
4 => "#ffe599",
|
||||||
5 => "#fff2cc",
|
5 => "#fff2cc",
|
||||||
6 => "#fffaea",
|
6 => "#fffaea",
|
||||||
|
7 => "#fffefa",
|
||||||
|
8 => "#fffefc",
|
||||||
|
9 => "#fffffd",
|
||||||
|
10 => "#fffffe",
|
||||||
_ => "#ddd"
|
_ => "#ddd"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
* #dd7e6b;
|
public static string GetOrdinal(int num)
|
||||||
#ea9999;
|
{
|
||||||
#f9cb9c;
|
if (num <= 0) return num.ToString();
|
||||||
#ffe599;
|
|
||||||
#fff2cc;
|
switch (num % 100)
|
||||||
#fffaea;
|
{
|
||||||
*/
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,4 @@
|
|||||||
<ProjectReference Include="..\Data\Data.csproj" />
|
<ProjectReference Include="..\Data\Data.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Components\Temp\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
html, body {
|
/*html, body {
|
||||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ h1:focus {
|
|||||||
|
|
||||||
.darker-border-checkbox.form-check-input {
|
.darker-border-checkbox.form-check-input {
|
||||||
border-color: #929292;
|
border-color: #929292;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
@@ -56,20 +56,24 @@ h1:focus {
|
|||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
main {
|
div.mud-main-content > div {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
margin: 30pt;
|
margin: 30pt;
|
||||||
color: #000;
|
color: #000;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
body .sidebar, main > div.top-row {
|
.no-print {
|
||||||
display: none;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nobrk {
|
.nobrk {
|
||||||
break-inside: avoid;
|
break-inside: avoid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pagebreak {
|
||||||
|
page-break-after: always;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ranked-event-column > div:only-child{
|
.ranked-event-column > div:only-child{
|
||||||
|
|||||||
Reference in New Issue
Block a user