Compare commits

...
20 Commits
Author SHA1 Message Date
poprhythm 01056401e5 Remove obsolete files and components from the Web-Original project
This commit deletes various files including configuration settings, controllers, models, views, and third-party libraries that are no longer in use. The removal of these files helps streamline the project and reduces clutter, ensuring a cleaner codebase for future development. Key files removed include appsettings, controllers, views related to home and events, and Bootstrap and jQuery libraries.
2026-01-04 14:58:51 -05:00
poprhythm 83522ac52c Refactor Teams components for improved sorting and filtering functionality
Updated the Teams Index and Printout components to prioritize sorting by EventFormat, enhancing the organization of team data. Introduced a regional filter toggle in the Teams Index to allow users to view only regional teams. Adjusted the ScheduledTeamsList to sort teams by EventFormat first, ensuring consistent ordering across components. Additionally, added necessary using directives for improved code clarity.
2026-01-04 14:56:28 -05:00
poprhythm c6fb00c7f4 Enhance CareerField and CareerMapping components with descriptions
Added a Description property to the CareerField class to provide a short overview of each career field. Updated the CareerFieldDefinitions to include descriptions for all career fields. Modified the CareerMapping component to display the description of the selected career field or event, improving user experience by providing more context about each node.
2025-12-29 21:31:33 -05:00
poprhythm 3bd076afb3 Enhance CareerMapping component with node click functionality and detailed career display
Updated the CareerMapping component to allow users to click on nodes for detailed information about career fields and related careers. Introduced a new SelectedNodeInfo class to manage the display of selected node details. Improved data handling for career field and event nodes, ensuring accurate representation of related careers. Adjusted the network click event to trigger updates in the UI, enhancing interactivity and user experience.
2025-12-29 21:19:08 -05:00
poprhythm 1d3167710d Add VisNetwork integration to CareerMapping component
Updated the CareerMapping component to utilize VisNetwork for visualizing relationships between events and career fields. Replaced the previous Mermaid diagram implementation with a network graph, enhancing interactivity and visual clarity. Adjusted data generation logic to support the new network structure and updated relevant namespaces in the project files.
2025-12-29 12:58:30 -05:00
poprhythm 7266ab609b Enhance CareerMapping component to reflect career fields instead of careers
Updated the CareerMapping component to improve clarity by changing terminology from "careers" to "career fields" throughout the UI. Enhanced the diagram generation logic to filter events based on related career fields, ensuring accurate representation of relationships. Added styling for event and field nodes in the Mermaid diagram for better visual distinction.
2025-12-29 10:54:30 -05:00
poprhythm 2c9aa1c223 Add Career Mapping feature to Events section
Introduced a new CareerMapping component that visualizes relationships between events and related careers using a Mermaid diagram. Updated the Events Index page to include a navigation button for accessing the Career Mapping feature. Added Blazorade.Mermaid package for diagram rendering and updated _Imports.razor to include necessary namespaces.
2025-12-28 21:55:35 -05:00
poprhythm 065a83442c Add FormValidationService and EventDefinitionService to dependency injection
Enhanced the application's service layer by adding FormValidationService and EventDefinitionService to the dependency injection container in Program.cs. Updated Create, Edit, and other relevant components to utilize these services for improved form validation and event processing functionality.
2025-12-28 19:56:16 -05:00
poprhythm 0358763601 Refactor string length validation in EventDefinition entity properties
Removed minimum length constraints from SemifinalistActivity and Notes properties in the EventDefinition class to simplify validation requirements. This change enhances flexibility in data entry while maintaining the maximum length restrictions.
2025-12-28 18:17:51 -05:00
poprhythm 06b2db0b4c Add Related Careers functionality to EventDefinition entity and update related components
Introduced a many-to-many relationship between EventDefinition and Career entities, allowing for the association of multiple careers with an event. Updated the AppDbContext to include a DbSet for Careers and modified the EventDefinitionConfiguration to handle the new relationship. Enhanced the Create, Edit, and Details components to support input and display of related careers, including normalization and processing logic for career names. Updated the database schema to reflect these changes.
2025-12-28 15:22:03 -05:00
poprhythm 8967d0f8a4 Add Authentication Setup Guide for TSA Chapter Organizer application
This new documentation file provides comprehensive instructions on configuring login credentials, generating password hashes, and managing user roles for both development and production environments. It includes best practices for security and troubleshooting tips to enhance user experience and system integrity.
2025-12-28 15:21:21 -05:00
poprhythm d0fd7469af Replace MudText with MudChip in EventAttributes component for enhanced styling and visual consistency 2025-12-28 14:51:12 -05:00
poprhythm f144f0f8f0 Update icon representation in AppIcons model for improved visual clarity 2025-12-28 12:55:59 -05:00
poprhythm 62b6ae06fd Update icons in Calendar, Students, and Home components for consistency and improved UI 2025-12-28 10:42:53 -05:00
poprhythm b3d7577c32 Add Handout button to Teams page and remove links from NavMenu 2025-12-27 23:06:09 -05:00
poprhythm c9ef169989 Refactor Calendar and Home components for improved navigation and user experience
Updated the Calendar component's route from "/event-calendar" to "/calendar" for clarity. Enhanced the Home component to provide dynamic content based on the presence of students and teams, introducing new sections for "Getting Started" and "Team Building". Improved the DashboardCard component to support emphasized styling for better visual hierarchy. Updated the navigation menu to reflect these changes and ensure a more intuitive user experience.
2025-12-27 22:01:41 -05:00
poprhythm c462ed4561 Enhance event definitions and parsing logic for new event types
Added new event definitions for "Meet the Candidates", "Chapter Officer Meeting", "Voting Delegate Meeting", and "Social Gathering". Updated the EventOccurrenceParser to handle these new event types and modified related services and views to accommodate the changes. Improved test coverage for the new event definitions and ensured proper parsing and display in the calendar components.
2025-12-27 19:32:54 -05:00
poprhythm cd34be1f82 Add EventOccurrenceParserService and update service registrations
Registered the new EventOccurrenceParserService in Program.cs to handle event occurrence parsing. Updated the _Imports.razor file to reflect the renaming of the EventCalendar component to Calendar. Removed the obsolete EventCalendar component to streamline the codebase.
2025-12-27 18:57:56 -05:00
poprhythm 3a809f18a6 Add EventOccurrence entity and update DbContext
Created the EventOccurrence entity with properties for event details and added it to the AppDbContext. Updated the model snapshot to reflect the new entity and its relationships, ensuring proper database schema generation.
2025-12-27 15:59:48 -05:00
poprhythm eaefdfaedd Refactor Event Calendar component for improved performance and usability
Optimized the Event Calendar component by enhancing the event fetching logic and improving the rendering efficiency. Updated the user interface for better accessibility and responsiveness. Added additional tests to ensure reliability of the event occurrences service integration.
2025-12-27 13:48:04 -05:00
144 changed files with 4132 additions and 76815 deletions
+19
View File
@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
namespace Core.Entities;
public class Career
{
public int Id { get; set; }
[Required]
[StringLength(200, MinimumLength = 1)]
[Display(Name = "Career Name")]
public string Name { get; set; } = null!;
public override string ToString()
{
return Name;
}
}
+55
View File
@@ -0,0 +1,55 @@
namespace Core.Entities;
/// <summary>
/// Represents a career field cluster that groups related careers together.
/// </summary>
public class CareerField
{
/// <summary>
/// Unique identifier for the career field (1-25)
/// </summary>
public int Id { get; }
/// <summary>
/// Display name of the career field
/// </summary>
public string Name { get; }
/// <summary>
/// Short description of the career field
/// </summary>
public string Description { get; }
/// <summary>
/// Exact career names that belong to this field
/// </summary>
public IReadOnlyList<string> DirectCareerMatches { get; }
/// <summary>
/// Keywords for pattern matching (case-insensitive substring matching)
/// </summary>
public IReadOnlyList<string> PatternKeywords { get; }
/// <summary>
/// Creates a new CareerField instance
/// </summary>
/// <param name="id">Unique identifier</param>
/// <param name="name">Display name</param>
/// <param name="description">Short description of the career field</param>
/// <param name="directCareerMatches">Exact career name matches</param>
/// <param name="patternKeywords">Keywords for pattern matching</param>
public CareerField(int id, string name, string description, IReadOnlyList<string> directCareerMatches, IReadOnlyList<string> patternKeywords)
{
Id = id;
Name = name;
Description = description;
DirectCareerMatches = directCareerMatches ?? Array.Empty<string>();
PatternKeywords = patternKeywords ?? Array.Empty<string>();
}
public override string ToString()
{
return Name;
}
}
+11 -3
View File
@@ -30,7 +30,7 @@ public class EventDefinition
? MinTeamSize.ToString() ? MinTeamSize.ToString()
: $"{MinTeamSize.ToString()}-{MaxTeamSize.ToString()}"; : $"{MinTeamSize.ToString()}-{MaxTeamSize.ToString()}";
[StringLength(100, MinimumLength = 3)] [StringLength(100)]
public string? SemifinalistActivity { get; set; } public string? SemifinalistActivity { get; set; }
public bool InterviewOrPresentation public bool InterviewOrPresentation
@@ -53,7 +53,7 @@ public class EventDefinition
// || Name.Contains("Chapter") // || Name.Contains("Chapter")
// || Name.Contains("Podcast")); // || Name.Contains("Podcast"));
[StringLength(1024, MinimumLength = 3)] [StringLength(1024)]
public string? Notes { get; set; } public string? Notes { get; set; }
[Range(0, 3)] [Range(0, 3)]
@@ -79,12 +79,20 @@ public class EventDefinition
public string? Description { get; set; } public string? Description { get; set; }
public int? LevelOfEffort { get; set; } public int? LevelOfEffort { get; set; }
public ICollection<Career> RelatedCareers { get; set; } = new List<Career>();
[System.ComponentModel.DataAnnotations.Schema.NotMapped]
public string? RelatedCareersText { get; set; }
public override string ToString() public override string ToString()
{ {
return Name; return Name;
} }
public static readonly EventDefinition GeneralSchedule = new(){Name = "General Schedule"}; public static readonly EventDefinition GeneralSchedule = new(){Name = "General Schedule"};
public static readonly EventDefinition VotingDelegates = new(){Name = "Voting Delegates"}; public static readonly EventDefinition MeetTheCandidates = new(){Name = "Meet the Candidates"};
public static readonly EventDefinition ChapterOfficerMeeting = new(){Name = "Chapter Officer Meeting"};
public static readonly EventDefinition VotingDelegateMeeting = new(){Name = "Voting Delegate Meeting"};
public static readonly EventDefinition SocialGathering = new(){Name = "Social Gathering"};
} }
+11 -3
View File
@@ -1,14 +1,22 @@
namespace Core.Entities
namespace Core.Entities
{ {
public class EventOccurrence public class EventOccurrence
{ {
public int Id { get; set; }
public int? EventDefinitionId { get; set; }
public string? SpecialEventType { get; set; }
public string Name { get; set; } = null!; public string Name { get; set; } = null!;
public string Time { get; set; } = null!; public string Time { get; set; } = null!;
public string Date { get; set; } = null!; public string Date { get; set; } = null!;
public DateTime StartTime { get; set; } public DateTime StartTime { get; set; }
public DateTime? EndTime { get; set; } public DateTime? EndTime { get; set; }
public string Location { get; set; } = null!; public string? Location { get; set; }
// Navigation property
public EventDefinition? EventDefinition { get; set; }
public bool SignupSubmitPickup => public bool SignupSubmitPickup =>
Name.Contains("Sign-up") || Name.Contains("Sign-up") ||
+38
View File
@@ -0,0 +1,38 @@
using Core.Entities;
namespace Core.Models;
/// <summary>
/// Result of parsing event occurrence text data.
/// Contains parsed occurrences, errors, and warnings.
/// </summary>
public class EventOccurrenceParseResult
{
/// <summary>
/// Dictionary of parsed event occurrences, keyed by EventDefinition.
/// For special events (GeneralSchedule, MeetTheCandidates, ChapterOfficerMeeting, VotingDelegateMeeting, SocialGathering),
/// the EventDefinition key will be the static instance.
/// </summary>
public IDictionary<EventDefinition, List<EventOccurrence>> Occurrences { get; set; } = new Dictionary<EventDefinition, List<EventOccurrence>>();
/// <summary>
/// List of parsing errors (critical issues that prevented parsing).
/// </summary>
public List<string> Errors { get; set; } = new();
/// <summary>
/// List of parsing warnings (non-critical issues that occurred during parsing).
/// </summary>
public List<string> Warnings { get; set; } = new();
/// <summary>
/// Total number of event occurrences successfully parsed.
/// </summary>
public int TotalParsed => Occurrences.Values.Sum(list => list.Count);
/// <summary>
/// Indicates whether parsing was successful (no errors).
/// </summary>
public bool IsSuccess => Errors.Count == 0;
}
+39 -14
View File
@@ -52,33 +52,31 @@ public class EventOccurrenceParser
currentEventDefinition = evt; currentEventDefinition = evt;
continue; continue;
} }
if (line == "General Schedule") if (line == "General Schedule" || line == "General Session")
{ {
currentEventDefinition = EventDefinition.GeneralSchedule; currentEventDefinition = EventDefinition.GeneralSchedule;
continue; continue;
} }
if (line == "Voting Delegates") // "Voting Delegates" section header is no longer used - occurrences are categorized by name pattern
{ // Continue without setting currentEventDefinition for this section
currentEventDefinition = EventDefinition.VotingDelegates;
continue; continue;
} }
continue;
}
if (currentEventDefinition == null)
continue;
var occurrenceName = match.Groups["Name"].Captures[0].Value; var occurrenceName = match.Groups["Name"].Captures[0].Value;
var month = match.Groups["Month"].Captures[0].Value; var month = match.Groups["Month"].Captures[0].Value;
var dayOfMonth = match.Groups["DayOfMonth"].Captures[0].Value; var dayOfMonth = match.Groups["DayOfMonth"].Captures[0].Value;
var timeAndLocation = match.Groups["TimeAndLocation"].Captures[0].Value; var timeAndLocation = match.Groups["TimeAndLocation"].Captures[0].Value;
occurrenceName = Regex.Replace(occurrenceName, occurrenceName = Regex.Replace(occurrenceName,
@"(?<Weekday>Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),\s?$", "").Trim(); @"(?<Weekday>Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),\s?$", "").Trim();
// Determine event definition based on occurrence name pattern or current section
EventDefinition? eventDefinition = DetermineEventDefinition(occurrenceName, currentEventDefinition);
// Skip if we can't determine the event definition
if (eventDefinition == null)
continue;
timeAndLocation = SanitizeInput(timeAndLocation); timeAndLocation = SanitizeInput(timeAndLocation);
var timeAndLocationMatch = _timeLocationRegex.Match(timeAndLocation); var timeAndLocationMatch = _timeLocationRegex.Match(timeAndLocation);
@@ -103,14 +101,41 @@ public class EventOccurrenceParser
Location = location Location = location
}; };
if (!occurrences.ContainsKey(currentEventDefinition)) if (!occurrences.ContainsKey(eventDefinition))
occurrences.Add(currentEventDefinition, []); occurrences.Add(eventDefinition, []);
occurrences[currentEventDefinition].Add(eventOccurrence); occurrences[eventDefinition].Add(eventOccurrence);
} }
return occurrences; return occurrences;
} }
/// <summary>
/// Determines the EventDefinition for an occurrence based on its name pattern or current section context.
/// </summary>
private EventDefinition? DetermineEventDefinition(string occurrenceName, EventDefinition? currentEventDefinition)
{
// Check for special event name patterns first (regardless of current section)
if (occurrenceName.Contains("Meet the Candidates Session", StringComparison.OrdinalIgnoreCase))
return EventDefinition.MeetTheCandidates;
if (occurrenceName.Contains("Chapter Officer Meeting", StringComparison.OrdinalIgnoreCase))
return EventDefinition.ChapterOfficerMeeting;
if (occurrenceName.Contains("Voting Delegate Meeting", StringComparison.OrdinalIgnoreCase))
return EventDefinition.VotingDelegateMeeting;
// If we're in a General Schedule/Session section and no pattern matched, use GeneralSchedule
if (currentEventDefinition == EventDefinition.GeneralSchedule)
return EventDefinition.GeneralSchedule;
// If we have a current event definition from section header (e.g., regular events), use it
if (currentEventDefinition != null)
return currentEventDefinition;
// Cannot determine event definition
return null;
}
private string SanitizeInput(string input) private string SanitizeInput(string input)
{ {
@@ -0,0 +1,114 @@
using System.Text;
using Core.Entities;
using Core.Models;
using Core.Parsers;
namespace Core.Services;
/// <summary>
/// Service implementation for parsing event occurrence text data.
/// Wraps EventOccurrenceParser to support text input and error collection.
/// </summary>
public class EventOccurrenceParserService : IEventOccurrenceParserService
{
/// <inheritdoc/>
public EventOccurrenceParseResult ParseFromText(string text, ICollection<EventDefinition> events)
{
var result = new EventOccurrenceParseResult();
if (string.IsNullOrWhiteSpace(text))
{
result.Errors.Add("Input text is empty or whitespace.");
return result;
}
try
{
// Create a temporary file from the text content
var tempFile = Path.GetTempFileName();
try
{
File.WriteAllText(tempFile, text, Encoding.UTF8);
var fileInfo = new FileInfo(tempFile);
// Use the existing EventOccurrenceParser
var parser = new EventOccurrenceParser(fileInfo, events);
var parsedOccurrences = parser.Parse();
// Convert parsed occurrences to result format, handling special event types
foreach (var kvp in parsedOccurrences)
{
var eventDefinition = kvp.Key;
var occurrences = kvp.Value;
// Check if this is a special event type (not stored in database)
if (eventDefinition == EventDefinition.GeneralSchedule ||
eventDefinition == EventDefinition.MeetTheCandidates ||
eventDefinition == EventDefinition.ChapterOfficerMeeting ||
eventDefinition == EventDefinition.VotingDelegateMeeting ||
eventDefinition == EventDefinition.SocialGathering)
{
// For special events, set EventDefinitionId to null and set SpecialEventType
foreach (var occurrence in occurrences)
{
occurrence.EventDefinitionId = null;
occurrence.SpecialEventType = eventDefinition switch
{
var ed when ed == EventDefinition.GeneralSchedule => "GeneralSchedule",
var ed when ed == EventDefinition.MeetTheCandidates => "MeetTheCandidates",
var ed when ed == EventDefinition.ChapterOfficerMeeting => "ChapterOfficerMeeting",
var ed when ed == EventDefinition.VotingDelegateMeeting => "VotingDelegateMeeting",
var ed when ed == EventDefinition.SocialGathering => "SocialGathering",
_ => throw new InvalidOperationException($"Unknown special event type: {eventDefinition.Name}")
};
}
// Add to result with the special EventDefinition as key
result.Occurrences[eventDefinition] = occurrences;
}
else
{
// For regular events, set EventDefinitionId and ensure SpecialEventType is null
foreach (var occurrence in occurrences)
{
occurrence.EventDefinitionId = eventDefinition.Id;
occurrence.SpecialEventType = null;
}
result.Occurrences[eventDefinition] = occurrences;
}
}
// Track any occurrences without a matching EventDefinition
// (This would be detected if parser.Parse() returns occurrences with null EventDefinition keys,
// but the current parser implementation doesn't do this - all occurrences have a currentEventDefinition)
}
finally
{
// Clean up temporary file
try
{
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
}
catch
{
// Ignore cleanup errors
}
}
}
catch (Exception ex)
{
result.Errors.Add($"Error parsing text: {ex.Message}");
if (ex.InnerException != null)
{
result.Errors.Add($"Inner exception: {ex.InnerException.Message}");
}
}
return result;
}
}
@@ -0,0 +1,19 @@
using Core.Entities;
using Core.Models;
namespace Core.Services;
/// <summary>
/// Service interface for parsing event occurrence text data.
/// </summary>
public interface IEventOccurrenceParserService
{
/// <summary>
/// Parses event occurrence text data and returns parse results with occurrences, errors, and warnings.
/// </summary>
/// <param name="text">The text content to parse (typically multi-line text from paste/import)</param>
/// <param name="events">Collection of EventDefinitions to match against during parsing</param>
/// <returns>ParseResult containing parsed occurrences, errors, and warnings</returns>
EventOccurrenceParseResult ParseFromText(string text, ICollection<EventDefinition> events);
}
+306
View File
@@ -0,0 +1,306 @@
using Core.Entities;
namespace Core.Utility;
/// <summary>
/// Static class that defines all 25 career field clusters and provides methods to match careers to fields.
/// </summary>
public static class CareerFieldDefinitions
{
private static readonly Lazy<IReadOnlyList<CareerField>> _allFields = new(() => CreateAllCareerFields());
/// <summary>
/// Gets all 25 career field definitions
/// </summary>
public static IReadOnlyList<CareerField> GetAllCareerFields() => _allFields.Value;
/// <summary>
/// Gets the career fields that are related to the given careers.
/// Uses both direct name matching and pattern-based keyword matching.
/// </summary>
/// <param name="careers">The careers to match against</param>
/// <returns>Distinct list of related CareerFields</returns>
public static IReadOnlyList<CareerField> GetRelatedCareerFields(IEnumerable<Career> careers)
{
if (careers == null)
return Array.Empty<CareerField>();
var careerNames = careers
.Where(c => !string.IsNullOrWhiteSpace(c.Name))
.Select(c => c.Name.Trim())
.ToHashSet(StringComparer.OrdinalIgnoreCase);
if (!careerNames.Any())
return Array.Empty<CareerField>();
var matchingFields = new HashSet<CareerField>();
var allFields = GetAllCareerFields();
foreach (var field in allFields)
{
bool matches = false;
// Check direct matches
foreach (var directMatch in field.DirectCareerMatches)
{
if (careerNames.Contains(directMatch))
{
matches = true;
break;
}
}
// Check pattern matches if no direct match found
if (!matches)
{
foreach (var keyword in field.PatternKeywords)
{
if (careerNames.Any(name => name.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
{
matches = true;
break;
}
}
}
if (matches)
{
matchingFields.Add(field);
}
}
return matchingFields.OrderBy(f => f.Id).ToList();
}
private static IReadOnlyList<CareerField> CreateAllCareerFields()
{
return new List<CareerField>
{
// 1. Aerospace & Automotive Engineering
new CareerField(
1,
"Aerospace & Automotive Engineering",
"Careers focused on designing and engineering aircraft, spacecraft, and vehicles for transportation.",
new[] { "Aeronautical engineer", "Aircraft systems engineer", "Automobile designer", "Automotive designer", "Automotive modeler", "Race car engineer" },
new[] { "aeronautical", "aircraft", "automobile", "automotive", "race car" }
),
// 2. Mechanical & Robotics Engineering
new CareerField(
2,
"Mechanical & Robotics Engineering",
"Engineering disciplines involving mechanical systems, machinery design, and automated robotic systems.",
new[] { "Machine designer", "Mechanical drafter", "Mechanical engineer", "Robotics engineer" },
new[] { "mechanical", "robotics", "machine" }
),
// 3. Electrical & Electronics Engineering
new CareerField(
3,
"Electrical & Electronics Engineering",
"Careers involving electrical systems, circuits, and electronic device design and maintenance.",
new[] { "Electrical engineer", "Electrical technician", "Electrician", "Electromechanical engineer", "Electronic analyst", "Electronic designer" },
new[] { "electrical", "electronic", "electrician" }
),
// 4. Civil & Structural Engineering
new CareerField(
4,
"Civil & Structural Engineering",
"Engineering fields focused on infrastructure, buildings, bridges, and construction project management.",
new[] { "Civil engineer", "Construction analyst", "Construction manager", "General contractor", "Structural engineer", "Structural iron and steel work technician" },
new[] { "civil", "construction", "structural", "contractor" }
),
// 5. Environmental & Energy Engineering
new CareerField(
5,
"Environmental & Energy Engineering",
"Engineering careers focused on sustainable energy solutions, environmental protection, and chemical processes.",
new[] { "Chemical engineer", "Energy efficiency technician", "Environmental engineer", "Solar engineer", "Solar panel installer", "Solar sales consultant" },
new[] { "chemical", "energy", "environmental", "solar" }
),
// 6. General Engineering & Quality
new CareerField(
6,
"General Engineering & Quality",
"Broad engineering roles including management, quality assurance, and standards compliance across various industries.",
new[] { "Engineer", "Engineering manager", "Engineering technician", "Quality assurance engineer", "Quality engineer", "Standards engineer" },
new[] { "engineer", "quality", "standards" }
),
// 7. Architecture & Urban Planning
new CareerField(
7,
"Architecture & Urban Planning",
"Design and planning careers focused on buildings, spaces, and community development.",
new[] { "Architect", "Community planner", "Interior designer", "Urban and regional planner" },
new[] { "architect", "planner", "interior design", "urban" }
),
// 8. Software Development
new CareerField(
8,
"Software Development",
"Careers in creating, designing, and developing computer software applications and systems.",
new[] { "Computer programmer", "Computer software engineer", "Programming & software development", "Software designer", "Software engineer" },
new[] { "programming", "programmer", "software", "developer" }
),
// 9. IT & Networking
new CareerField(
9,
"IT & Networking",
"Information technology careers involving computer systems, networks, technical support, and telecommunications.",
new[] { "Computer engineer", "Computer network specialist", "Computer technician", "Information support & services", "Network systems", "Technical support specialist", "Telecommunications manager" },
new[] { "network", "computer", "technical support", "telecommunications", "IT" }
),
// 10. Cybersecurity & Digital Forensics
new CareerField(
10,
"Cybersecurity & Digital Forensics",
"Security-focused careers protecting digital systems, investigating cybercrimes, and ensuring information security.",
new[] { "Cryptographer", "Cyber Crime Investigator", "Cyber defense incident responder", "Cyber forensics expert", "Cyber legal advisor", "Cyber operator", "Cybersecurity engineer", "Vulnerability assessor" },
new[] { "cyber", "security", "forensics", "cryptography", "vulnerability" }
),
// 11. Data Science & Analytics
new CareerField(
11,
"Data Science & Analytics",
"Careers analyzing data, applying mathematical and statistical methods to solve problems and make decisions.",
new[] { "Actuary", "Data analyst", "Data scientist", "Economist", "Mathematician", "Operations research analyst" },
new[] { "data", "analyst", "actuary", "economist", "mathematician", "research" }
),
// 12. CAD, CNC & Manufacturing
new CareerField(
12,
"CAD, CNC & Manufacturing",
"Careers in computer-aided design, manufacturing processes, and production planning.",
new[] { "CAD professional", "CNC programmer", "Manufacturing", "Production planner" },
new[] { "CAD", "CNC", "manufacturing", "production" }
),
// 13. Industrial & Product Design
new CareerField(
13,
"Industrial & Product Design",
"Design careers creating products, commercial goods, and industrial solutions with focus on form and function.",
new[] { "Appraiser", "Commercial and industrial design", "Designer", "Industrial designer", "Product designer" },
new[] { "designer", "design", "industrial", "product", "appraiser" }
),
// 14. Visual Arts & Animation
new CareerField(
14,
"Visual Arts & Animation",
"Creative careers in visual design, illustration, animation, and digital art creation.",
new[] { "Animator", "Artist", "Computer animator", "Graphic artist", "Illustrator", "Multimedia designer" },
new[] { "animator", "artist", "graphic", "illustrator", "multimedia" }
),
// 15. Game Design & Interactive Media
new CareerField(
15,
"Game Design & Interactive Media",
"Careers in video game design, development, testing, and professional gaming.",
new[] { "Game designer", "Game Play Tester", "Professional Gamer" },
new[] { "game", "gamer", "gaming" }
),
// 16. Audio & Music Production
new CareerField(
16,
"Audio & Music Production",
"Careers in audio engineering, music composition, sound design, and broadcast technology.",
new[] { "Audio designer or engineer", "Audio Engineer", "Audio operator or technician", "Broadcast technician", "Music composer" },
new[] { "audio", "music", "broadcast", "sound" }
),
// 17. Video & Film Production
new CareerField(
17,
"Video & Film Production",
"Careers in video production, filmmaking, directing, and television broadcasting.",
new[] { "Audiovisual technician", "Director", "Entertainment/television broadcaster", "Videographer" },
new[] { "video", "film", "director", "television", "broadcast", "videographer" }
),
// 18. Web & Digital Communications
new CareerField(
18,
"Web & Digital Communications",
"Careers in web design, digital communication, and instructional technology.",
new[] { "Instructional technologist", "Web & digital communications", "Webmaster", "Website designer" },
new[] { "web", "website", "digital", "communications", "webmaster" }
),
// 19. Writing & Publishing
new CareerField(
19,
"Writing & Publishing",
"Careers in writing, editing, publishing, and content creation across various media formats.",
new[] { "Ad copy writer", "Editor", "Publisher", "Screenplay writer", "Speech writer", "Technical writer", "Writer" },
new[] { "writing", "writer", "editor", "publisher", "copy" }
),
// 20. Journalism & Public Relations
new CareerField(
20,
"Journalism & Public Relations",
"Careers in news reporting, photojournalism, public relations, and communications management.",
new[] { "Internal communications manager", "Motivational speaker", "Photojournalist", "Reporter" },
new[] { "journalism", "reporter", "photojournalist", "communications", "speaker" }
),
// 21. Forensics & Criminal Investigation
new CareerField(
21,
"Forensics & Criminal Investigation",
"Careers in criminal investigation, forensic science, and analyzing evidence for legal proceedings.",
new[] { "Crime scene investigator", "Detective", "Forensic accountant", "Forensic anthropologist", "Forensic engineering scientist", "Forensic pathologist" },
new[] { "forensic", "detective", "investigator", "crime" }
),
// 22. Healthcare & Medical Technology
new CareerField(
22,
"Healthcare & Medical Technology",
"Medical and healthcare careers providing patient care, medical technology, and health services.",
new[] { "Dietitian", "Doctor", "Epidemiologist", "Medical technologist", "Nurse", "Pharmacist", "Prosthetics practitioner" },
new[] { "medical", "health", "doctor", "nurse", "pharmacist", "dietitian", "epidemiology" }
),
// 23. Science & Research
new CareerField(
23,
"Science & Research",
"Scientific research careers across biology, physics, meteorology, and other scientific disciplines.",
new[] { "Botanist", "Food scientist", "Meteorologist", "Molecular biologist", "Physics instructor", "Plant geneticist", "Research and development scientist", "Research assistant", "Researcher" },
new[] { "scientist", "research", "biology", "physics", "botanist", "meteorologist", "geneticist" }
),
// 24. Education & Training
new CareerField(
24,
"Education & Training",
"Careers in teaching, training, and educational instruction across various subjects and technologies.",
new[] { "Educator", "Teacher/trainer", "Technology education instructor" },
new[] { "educator", "teacher", "trainer", "education", "instructor" }
),
// 25. Business, Legal & Government
new CareerField(
25,
"Business, Legal & Government",
"Careers in business management, legal services, government, politics, and public policy.",
new[] { "Creative consultant", "Entrepreneur", "Government Official", "Lawyer", "Legal Aide", "Lobbyist", "Management executive", "Market researcher", "Marketing strategist", "Parliamentarian", "Politician", "Project manager", "Public affairs specialist", "Public policy specialist", "Recording Clerk", "Small business owner", "Volunteer manager" },
new[] { "business", "legal", "lawyer", "government", "politician", "manager", "marketing", "consultant", "entrepreneur" }
)
};
}
}
+57
View File
@@ -0,0 +1,57 @@
using System.Text.RegularExpressions;
namespace Core.Utility;
public static class CareerNormalizer
{
/// <summary>
/// Normalizes career names from multiline text input.
/// Strips bullet points, trims whitespace, and returns distinct normalized names.
/// </summary>
/// <param name="input">Multiline text input containing career names</param>
/// <returns>Collection of normalized career names (trimmed, with bullets removed)</returns>
public static IEnumerable<string> NormalizeCareerNames(string? input)
{
if (string.IsNullOrWhiteSpace(input))
{
return Enumerable.Empty<string>();
}
return input
.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)
.Select(line => NormalizeSingleCareerName(line))
.Where(name => !string.IsNullOrWhiteSpace(name))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
/// <summary>
/// Normalizes a single career name by stripping bullet points and trimming whitespace.
/// </summary>
/// <param name="careerName">The career name to normalize</param>
/// <returns>Normalized career name</returns>
private static string NormalizeSingleCareerName(string careerName)
{
if (string.IsNullOrWhiteSpace(careerName))
{
return string.Empty;
}
// Remove common bullet point characters (•, -, *, etc.) and trim
var normalized = Regex.Replace(careerName.Trim(), @"^[\u2022\u2023\u25E6\u2043\u2219\-\*\•]\s*", string.Empty, RegexOptions.Compiled);
return normalized.Trim();
}
/// <summary>
/// Finds or creates a career name for case-insensitive duplicate detection.
/// Returns the normalized (lowercase) version for comparison.
/// </summary>
/// <param name="careerName">The career name to normalize for comparison</param>
/// <returns>Lowercase normalized name for duplicate detection</returns>
public static string GetNormalizedKey(string careerName)
{
return NormalizeSingleCareerName(careerName).ToLowerInvariant();
}
}
+18 -11
View File
@@ -11,17 +11,18 @@ This approach allows you to edit credentials without rebuilding the container.
**Steps:** **Steps:**
1. **Generate Password Hashes** (on your development machine): 1. **Generate Password Hashes** (on your development machine):
```bash ```bash
# Run the app locally and navigate to: # Run the app locally and navigate to:
https://localhost:<port>/dev/hash-password?password=YourPassword https://localhost:<port>/dev/hash-password?password=YourPassword
``` ```
2. **Create `auth-secrets.json`** on your Docker host: 2. **Create `auth-secrets.json`** on your Docker host:
```bash ```bash
cp auth-secrets.example.json auth-secrets.json cp auth-secrets.example.json auth-secrets.json
``` ```
3. **Edit `auth-secrets.json`** and replace the placeholder hashes: 3. **Edit `auth-secrets.json`** and replace the placeholder hashes:
```json ```json
{ {
"Authentication": { "Authentication": {
@@ -36,19 +37,20 @@ This approach allows you to edit credentials without rebuilding the container.
} }
} }
``` ```
4. **Mount the file in Docker Compose**: 4. **Mount the file in Docker Compose**:
```yaml ```yaml
volumes: volumes:
- ./auth-secrets.json:/app/secrets/auth-secrets.json:ro - ./auth-secrets.json:/app/secrets/auth-secrets.json:ro
``` ```
5. **Update credentials**: Simply edit `auth-secrets.json` on the host and restart the container: 5. **Update credentials**: Simply edit `auth-secrets.json` on the host and restart the container:
```bash ```bash
docker-compose restart webapp docker-compose restart webapp
``` ```
**Security Note**: Set proper file permissions on the host: **Security Note**: Set proper file permissions on the host:
```bash ```bash
chmod 600 auth-secrets.json chmod 600 auth-secrets.json
``` ```
@@ -60,6 +62,7 @@ chmod 600 auth-secrets.json
This approach is useful for container orchestration platforms (Kubernetes, Docker Swarm, etc.). This approach is useful for container orchestration platforms (Kubernetes, Docker Swarm, etc.).
**Docker Compose Example**: **Docker Compose Example**:
```yaml ```yaml
environment: environment:
- TSA_Authentication__Users__0__Email=admin@example.com - TSA_Authentication__Users__0__Email=admin@example.com
@@ -73,6 +76,7 @@ environment:
``` ```
**Docker Run Example**: **Docker Run Example**:
```bash ```bash
docker run -d \ docker run -d \
-p 8080:8080 \ -p 8080:8080 \
@@ -85,6 +89,7 @@ docker run -d \
``` ```
**Kubernetes Secret Example**: **Kubernetes Secret Example**:
```yaml ```yaml
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
@@ -138,11 +143,13 @@ docker-compose logs -f webapp
### Adding a New User ### Adding a New User
**With Volume-Mounted File:** **With Volume-Mounted File:**
1. Edit `auth-secrets.json` on the host 1. Edit `auth-secrets.json` on the host
2. Add new user entry to the `Users` array 2. Add new user entry to the `Users` array
3. Restart the container: `docker-compose restart webapp` 3. Restart the container: `docker-compose restart webapp`
**With Environment Variables:** **With Environment Variables:**
1. Add new environment variables (increment the index number) 1. Add new environment variables (increment the index number)
2. Recreate the container: `docker-compose up -d` 2. Recreate the container: `docker-compose up -d`
@@ -162,24 +169,22 @@ docker-compose logs -f webapp
## Security Considerations ## Security Considerations
1. **File Permissions**: 1. **File Permissions**:
```bash ```bash
chmod 600 auth-secrets.json chmod 600 auth-secrets.json
chown root:root auth-secrets.json chown root:root auth-secrets.json
``` ```
2. **Never Commit Secrets**: Add to `.gitignore`: 2. **Never Commit Secrets**: Add to `.gitignore`:
``` ```
auth-secrets.json auth-secrets.json
docker-compose.yml docker-compose.yml
``` ```
3. **Use HTTPS in Production**: Configure SSL/TLS certificates 3. **Use HTTPS in Production**: Configure SSL/TLS certificates
4. **Backup Credentials**: Store encrypted backups of `auth-secrets.json` 4. **Backup Credentials**: Store encrypted backups of `auth-secrets.json`
5. **Password Rotation**: Periodically regenerate password hashes 5. **Password Rotation**: Periodically regenerate password hashes
6. **Monitor Access**: Review application logs for failed login attempts: 6. **Monitor Access**: Review application logs for failed login attempts:
```bash ```bash
docker-compose logs webapp | grep "Failed login" docker-compose logs webapp | grep "Failed login"
``` ```
@@ -191,6 +196,7 @@ docker-compose logs -f webapp
### Container Won't Start ### Container Won't Start
Check logs: Check logs:
```bash ```bash
docker-compose logs webapp docker-compose logs webapp
``` ```
@@ -198,16 +204,17 @@ docker-compose logs webapp
### Can't Login ### Can't Login
1. Verify `auth-secrets.json` is properly mounted: 1. Verify `auth-secrets.json` is properly mounted:
```bash ```bash
docker exec tsa-app ls -la /app/secrets/ docker exec tsa-app ls -la /app/secrets/
``` ```
2. Check if the file is being loaded: 2. Check if the file is being loaded:
```bash ```bash
docker-compose logs webapp | grep "secrets" docker-compose logs webapp | grep "secrets"
``` ```
3. Verify JSON syntax: 3. Verify JSON syntax:
```bash ```bash
cat auth-secrets.json | jq . cat auth-secrets.json | jq .
``` ```
+2
View File
@@ -10,6 +10,8 @@ namespace Data
public DbSet<Student> Students { get; set; } public DbSet<Student> Students { get; set; }
public DbSet<Team> Teams { get; set; } public DbSet<Team> Teams { get; set; }
public DbSet<StudentEventRanking> StudentEventRanking { get; set; } public DbSet<StudentEventRanking> StudentEventRanking { get; set; }
public DbSet<EventOccurrence> EventOccurrences { get; set; }
public DbSet<Career> Careers { get; set; }
public AppDbContext() public AppDbContext()
{ {
@@ -0,0 +1,23 @@
using Core.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Data.Configurations
{
public class CareerConfiguration : IEntityTypeConfiguration<Career>
{
public void Configure(EntityTypeBuilder<Career> builder)
{
builder.HasKey(c => c.Id);
// Indexes
builder.HasIndex(c => c.Name).IsUnique();
// Constraints
builder.Property(c => c.Name)
.IsRequired()
.HasMaxLength(200);
}
}
}
@@ -42,6 +42,14 @@ namespace Data.Configurations
builder.Property(e => e.EventFormat) builder.Property(e => e.EventFormat)
.HasConversion<string>() .HasConversion<string>()
.HasMaxLength(50); .HasMaxLength(50);
// Ignore RelatedCareersText (not mapped to database)
builder.Ignore(e => e.RelatedCareersText);
// Many-to-many relationship with Career
builder.HasMany(e => e.RelatedCareers)
.WithMany()
.UsingEntity(j => j.ToTable("EventDefinitionCareers"));
} }
} }
} }
@@ -0,0 +1,48 @@
using Core.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Data.Configurations
{
public class EventOccurrenceConfiguration : IEntityTypeConfiguration<EventOccurrence>
{
public void Configure(EntityTypeBuilder<EventOccurrence> builder)
{
builder.HasKey(e => e.Id);
// Indexes
builder.HasIndex(e => e.StartTime);
builder.HasIndex(e => e.EventDefinitionId);
builder.HasIndex(e => e.SpecialEventType);
// Foreign key relationship (optional)
builder.HasOne(e => e.EventDefinition)
.WithMany()
.HasForeignKey(e => e.EventDefinitionId)
.OnDelete(DeleteBehavior.Restrict); // Don't cascade delete if EventDefinition is deleted
// Constraints
builder.Property(e => e.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(e => e.Time)
.IsRequired()
.HasMaxLength(100);
builder.Property(e => e.Date)
.IsRequired()
.HasMaxLength(100);
builder.Property(e => e.Location)
.HasMaxLength(500);
builder.Property(e => e.SpecialEventType)
.HasMaxLength(50);
// Validation: Either EventDefinitionId OR SpecialEventType must be set (enforced at application level)
// EF Core doesn't support mutually exclusive constraints directly, so we'll handle this in validation attributes or service layer
}
}
}
+339
View File
@@ -0,0 +1,339 @@
// <auto-generated />
using System;
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("20251227205816_EventOccurrence")]
partial class EventOccurrence
{
/// <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<int>("ChapterEligibilityCountRegionals")
.HasColumnType("INTEGER");
b.Property<int>("ChapterEligibilityCountState")
.HasColumnType("INTEGER");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("TEXT");
b.Property<string>("Documentation")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("Eligibility")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("EventFormat")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<int?>("LevelOfEffort")
.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")
.HasMaxLength(1024)
.HasColumnType("TEXT");
b.Property<bool>("OnSiteActivity")
.HasColumnType("INTEGER");
b.Property<bool>("Presubmission")
.HasColumnType("INTEGER");
b.Property<string>("SemifinalistActivity")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("ShortName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("Theme")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("EventFormat");
b.HasIndex("Name")
.IsUnique();
b.ToTable("Events");
});
modelBuilder.Entity("Core.Entities.EventOccurrence", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Date")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<DateTime?>("EndTime")
.HasColumnType("TEXT");
b.Property<int?>("EventDefinitionId")
.HasColumnType("INTEGER");
b.Property<string>("Location")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SpecialEventType")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<DateTime>("StartTime")
.HasColumnType("TEXT");
b.Property<string>("Time")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("EventDefinitionId");
b.HasIndex("SpecialEventType");
b.HasIndex("StartTime");
b.ToTable("EventOccurrences");
});
modelBuilder.Entity("Core.Entities.Student", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<int>("Grade")
.HasColumnType("INTEGER");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("NationalId")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("OfficerRole")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("RegionalId")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("StateId")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<int>("TsaYear")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("Email");
b.HasIndex("Grade");
b.HasIndex("FirstName", "LastName");
b.ToTable("Students");
});
modelBuilder.Entity("Core.Entities.StudentEventRanking", b =>
{
b.Property<int>("StudentId")
.HasColumnType("INTEGER");
b.Property<int>("EventDefinitionId")
.HasColumnType("INTEGER");
b.Property<int>("Rank")
.HasColumnType("INTEGER");
b.HasKey("StudentId", "EventDefinitionId");
b.HasIndex("EventDefinitionId");
b.HasIndex("Rank");
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<string>("Identifier")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CaptainId");
b.HasIndex("EventId");
b.HasIndex("EventId", "Identifier");
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("TeamStudents", (string)null);
});
modelBuilder.Entity("Core.Entities.EventOccurrence", b =>
{
b.HasOne("Core.Entities.EventDefinition", "EventDefinition")
.WithMany()
.HasForeignKey("EventDefinitionId")
.OnDelete(DeleteBehavior.Restrict);
b.Navigation("EventDefinition");
});
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")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Core.Entities.EventDefinition", "Event")
.WithMany()
.HasForeignKey("EventId")
.OnDelete(DeleteBehavior.Restrict)
.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,63 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations
{
/// <inheritdoc />
public partial class EventOccurrence : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EventOccurrences",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
EventDefinitionId = table.Column<int>(type: "INTEGER", nullable: true),
SpecialEventType = table.Column<string>(type: "TEXT", maxLength: 50, nullable: true),
Name = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
Time = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
Date = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
StartTime = table.Column<DateTime>(type: "TEXT", nullable: false),
EndTime = table.Column<DateTime>(type: "TEXT", nullable: true),
Location = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_EventOccurrences", x => x.Id);
table.ForeignKey(
name: "FK_EventOccurrences_Events_EventDefinitionId",
column: x => x.EventDefinitionId,
principalTable: "Events",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_EventOccurrences_EventDefinitionId",
table: "EventOccurrences",
column: "EventDefinitionId");
migrationBuilder.CreateIndex(
name: "IX_EventOccurrences_SpecialEventType",
table: "EventOccurrences",
column: "SpecialEventType");
migrationBuilder.CreateIndex(
name: "IX_EventOccurrences_StartTime",
table: "EventOccurrences",
column: "StartTime");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EventOccurrences");
}
}
}
+388
View File
@@ -0,0 +1,388 @@
// <auto-generated />
using System;
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("20251228200039_RelatedCareers")]
partial class RelatedCareers
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.8");
modelBuilder.Entity("CareerEventDefinition", b =>
{
b.Property<int>("EventDefinitionId")
.HasColumnType("INTEGER");
b.Property<int>("RelatedCareersId")
.HasColumnType("INTEGER");
b.HasKey("EventDefinitionId", "RelatedCareersId");
b.HasIndex("RelatedCareersId");
b.ToTable("EventDefinitionCareers", (string)null);
});
modelBuilder.Entity("Core.Entities.Career", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("Careers");
});
modelBuilder.Entity("Core.Entities.EventDefinition", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ChapterEligibilityCountRegionals")
.HasColumnType("INTEGER");
b.Property<int>("ChapterEligibilityCountState")
.HasColumnType("INTEGER");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("TEXT");
b.Property<string>("Documentation")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("Eligibility")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("EventFormat")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<int?>("LevelOfEffort")
.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")
.HasMaxLength(1024)
.HasColumnType("TEXT");
b.Property<bool>("OnSiteActivity")
.HasColumnType("INTEGER");
b.Property<bool>("Presubmission")
.HasColumnType("INTEGER");
b.Property<string>("SemifinalistActivity")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("ShortName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("Theme")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("EventFormat");
b.HasIndex("Name")
.IsUnique();
b.ToTable("Events");
});
modelBuilder.Entity("Core.Entities.EventOccurrence", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Date")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<DateTime?>("EndTime")
.HasColumnType("TEXT");
b.Property<int?>("EventDefinitionId")
.HasColumnType("INTEGER");
b.Property<string>("Location")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SpecialEventType")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<DateTime>("StartTime")
.HasColumnType("TEXT");
b.Property<string>("Time")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("EventDefinitionId");
b.HasIndex("SpecialEventType");
b.HasIndex("StartTime");
b.ToTable("EventOccurrences");
});
modelBuilder.Entity("Core.Entities.Student", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<int>("Grade")
.HasColumnType("INTEGER");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("NationalId")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("OfficerRole")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("RegionalId")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("StateId")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<int>("TsaYear")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("Email");
b.HasIndex("Grade");
b.HasIndex("FirstName", "LastName");
b.ToTable("Students");
});
modelBuilder.Entity("Core.Entities.StudentEventRanking", b =>
{
b.Property<int>("StudentId")
.HasColumnType("INTEGER");
b.Property<int>("EventDefinitionId")
.HasColumnType("INTEGER");
b.Property<int>("Rank")
.HasColumnType("INTEGER");
b.HasKey("StudentId", "EventDefinitionId");
b.HasIndex("EventDefinitionId");
b.HasIndex("Rank");
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<string>("Identifier")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CaptainId");
b.HasIndex("EventId");
b.HasIndex("EventId", "Identifier");
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("TeamStudents", (string)null);
});
modelBuilder.Entity("CareerEventDefinition", b =>
{
b.HasOne("Core.Entities.EventDefinition", null)
.WithMany()
.HasForeignKey("EventDefinitionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Core.Entities.Career", null)
.WithMany()
.HasForeignKey("RelatedCareersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Core.Entities.EventOccurrence", b =>
{
b.HasOne("Core.Entities.EventDefinition", "EventDefinition")
.WithMany()
.HasForeignKey("EventDefinitionId")
.OnDelete(DeleteBehavior.Restrict);
b.Navigation("EventDefinition");
});
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")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Core.Entities.EventDefinition", "Event")
.WithMany()
.HasForeignKey("EventId")
.OnDelete(DeleteBehavior.Restrict)
.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,72 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations
{
/// <inheritdoc />
public partial class RelatedCareers : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Careers",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Careers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "EventDefinitionCareers",
columns: table => new
{
EventDefinitionId = table.Column<int>(type: "INTEGER", nullable: false),
RelatedCareersId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EventDefinitionCareers", x => new { x.EventDefinitionId, x.RelatedCareersId });
table.ForeignKey(
name: "FK_EventDefinitionCareers_Careers_RelatedCareersId",
column: x => x.RelatedCareersId,
principalTable: "Careers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_EventDefinitionCareers_Events_EventDefinitionId",
column: x => x.EventDefinitionId,
principalTable: "Events",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Careers_Name",
table: "Careers",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_EventDefinitionCareers_RelatedCareersId",
table: "EventDefinitionCareers",
column: "RelatedCareersId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EventDefinitionCareers");
migrationBuilder.DropTable(
name: "Careers");
}
}
}
@@ -1,4 +1,5 @@
// <auto-generated /> // <auto-generated />
using System;
using Data; using Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
@@ -16,6 +17,40 @@ namespace Data.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.8"); modelBuilder.HasAnnotation("ProductVersion", "9.0.8");
modelBuilder.Entity("CareerEventDefinition", b =>
{
b.Property<int>("EventDefinitionId")
.HasColumnType("INTEGER");
b.Property<int>("RelatedCareersId")
.HasColumnType("INTEGER");
b.HasKey("EventDefinitionId", "RelatedCareersId");
b.HasIndex("RelatedCareersId");
b.ToTable("EventDefinitionCareers", (string)null);
});
modelBuilder.Entity("Core.Entities.Career", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("Careers");
});
modelBuilder.Entity("Core.Entities.EventDefinition", b => modelBuilder.Entity("Core.Entities.EventDefinition", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -93,6 +128,55 @@ namespace Data.Migrations
b.ToTable("Events"); b.ToTable("Events");
}); });
modelBuilder.Entity("Core.Entities.EventOccurrence", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Date")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<DateTime?>("EndTime")
.HasColumnType("TEXT");
b.Property<int?>("EventDefinitionId")
.HasColumnType("INTEGER");
b.Property<string>("Location")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SpecialEventType")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<DateTime>("StartTime")
.HasColumnType("TEXT");
b.Property<string>("Time")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("EventDefinitionId");
b.HasIndex("SpecialEventType");
b.HasIndex("StartTime");
b.ToTable("EventOccurrences");
});
modelBuilder.Entity("Core.Entities.Student", b => modelBuilder.Entity("Core.Entities.Student", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -214,6 +298,31 @@ namespace Data.Migrations
b.ToTable("TeamStudents", (string)null); b.ToTable("TeamStudents", (string)null);
}); });
modelBuilder.Entity("CareerEventDefinition", b =>
{
b.HasOne("Core.Entities.EventDefinition", null)
.WithMany()
.HasForeignKey("EventDefinitionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Core.Entities.Career", null)
.WithMany()
.HasForeignKey("RelatedCareersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Core.Entities.EventOccurrence", b =>
{
b.HasOne("Core.Entities.EventDefinition", "EventDefinition")
.WithMany()
.HasForeignKey("EventDefinitionId")
.OnDelete(DeleteBehavior.Restrict);
b.Navigation("EventDefinition");
});
modelBuilder.Entity("Core.Entities.StudentEventRanking", b => modelBuilder.Entity("Core.Entities.StudentEventRanking", b =>
{ {
b.HasOne("Core.Entities.EventDefinition", "EventDefinition") b.HasOne("Core.Entities.EventDefinition", "EventDefinition")
+92 -5
View File
@@ -7,10 +7,10 @@ public class EventOccurrenceParser_Tests
{ {
[Test] [Test]
public void ParseTest() public void ParseNationalsTest()
{ {
var events = TestEntityHandler.GetEvents(); var events = TestEntityHandler.GetEvents();
var parser = new EventOccurrenceParser(TestEntityHandler.GetEventOccurrenceFileInfo(), events); var parser = new EventOccurrenceParser(TestEntityHandler.GetEventOccurrenceNationalsFileInfo(), events);
var dictionary = parser.Parse(); var dictionary = parser.Parse();
Console.WriteLine($"Occurrence, Month, Date, Time, Location"); Console.WriteLine($"Occurrence, Month, Date, Time, Location");
foreach (var @event in events) foreach (var @event in events)
@@ -30,15 +30,102 @@ public class EventOccurrenceParser_Tests
} }
Console.WriteLine("General Schedule"); Console.WriteLine("General Schedule");
if (dictionary.ContainsKey(EventDefinition.GeneralSchedule))
{
foreach (var eo in dictionary[EventDefinition.GeneralSchedule].OrderBy(occurrence => occurrence.StartTime)) foreach (var eo in dictionary[EventDefinition.GeneralSchedule].OrderBy(occurrence => occurrence.StartTime))
{ {
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}"); Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
} }
}
if (dictionary.ContainsKey(EventDefinition.VotingDelegates)) Console.WriteLine("Meet the Candidates");
foreach (var eo in dictionary[EventDefinition.VotingDelegates]) if (dictionary.ContainsKey(EventDefinition.MeetTheCandidates))
{ {
Console.WriteLine($"{eo.Name} {eo.StartTime}, {eo.Location}"); foreach (var eo in dictionary[EventDefinition.MeetTheCandidates])
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Chapter Officer Meeting");
if (dictionary.ContainsKey(EventDefinition.ChapterOfficerMeeting))
{
foreach (var eo in dictionary[EventDefinition.ChapterOfficerMeeting])
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Voting Delegate Meeting");
if (dictionary.ContainsKey(EventDefinition.VotingDelegateMeeting))
{
foreach (var eo in dictionary[EventDefinition.VotingDelegateMeeting])
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Assert.Pass();
}
[Test]
public void ParseStatesTest()
{
var events = TestEntityHandler.GetEvents();
var parser = new EventOccurrenceParser(TestEntityHandler.GetEventOccurrenceStateFileInfo(), events);
var dictionary = parser.Parse();
Console.WriteLine($"Occurrence, Month, Date, Time, Location");
foreach (var @event in events)
{
Console.WriteLine($"{@event.Name}");
if (!dictionary.ContainsKey(@event))
{
Console.WriteLine("!!! eventDefinition not found " + @event.Name);
continue;
}
var eventOccurrences = dictionary[@event];
foreach (var eo in eventOccurrences)
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("General Schedule");
if (dictionary.ContainsKey(EventDefinition.GeneralSchedule))
{
foreach (var eo in dictionary[EventDefinition.GeneralSchedule].OrderBy(occurrence => occurrence.StartTime))
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Meet the Candidates");
if (dictionary.ContainsKey(EventDefinition.MeetTheCandidates))
{
foreach (var eo in dictionary[EventDefinition.MeetTheCandidates])
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Chapter Officer Meeting");
if (dictionary.ContainsKey(EventDefinition.ChapterOfficerMeeting))
{
foreach (var eo in dictionary[EventDefinition.ChapterOfficerMeeting])
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Voting Delegate Meeting");
if (dictionary.ContainsKey(EventDefinition.VotingDelegateMeeting))
{
foreach (var eo in dictionary[EventDefinition.VotingDelegateMeeting])
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
} }
Assert.Pass(); Assert.Pass();
+7 -1
View File
@@ -16,11 +16,17 @@ public static class TestEntityHandler
return eventRankingsParser.Parse(); return eventRankingsParser.Parse();
} }
public static FileInfo GetEventOccurrenceFileInfo() public static FileInfo GetEventOccurrenceNationalsFileInfo()
{ {
return FileUtility.GetContentFile(ContentDirectory, "2025 TSA Nationals Competition Event Times.txt"); return FileUtility.GetContentFile(ContentDirectory, "2025 TSA Nationals Competition Event Times.txt");
} }
public static FileInfo GetEventOccurrenceStateFileInfo()
{
return FileUtility.GetContentFile(ContentDirectory, "2025 TN TSA State Competition Event Times.txt");
}
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";
-366
View File
@@ -1,366 +0,0 @@
using System.Diagnostics;
using System.Text.Json;
using System.Text.RegularExpressions;
using Core.Calculation;
using Core.Entities;
using Core.Parsers;
using Core.Utility;
using Microsoft.AspNetCore.Mvc;
using Web.Models;
namespace Web.Controllers
{
public partial class HomeController : Controller
{
private const string ContentDirectory = @"C:\Users\james\source\TSA Chapter Organizer\Tests\Parsers\TestInput\";
private EventDefinition[] GetEvents()
{
var fileInfo = FileUtility.GetContentFile(ContentDirectory, "2024-25 RMS TSA student & eventDefinition - EventDefinition Definitions.csv");
var eventRankingsParser = new EventDefinitionParser(fileInfo);
return eventRankingsParser.Parse();
}
private Student[] GetStudents(IList<EventDefinition> events)
{
var fileInfo = FileUtility.GetContentFile(ContentDirectory, "2024-25 RMS TSA student & eventDefinition - Nationals Student EventDefinition Rankings.csv");
var eventRankingsParser = new StudentParser(fileInfo);
return eventRankingsParser.Parse(events);
}
public static StudentEventRanking[] GetStudentEventRankings(Student[] students, EventDefinition[] events)
{
var fileInfo = FileUtility.GetContentFile(ContentDirectory, "2024-25 RMS TSA - Student Event Rankings.csv");
var rankingParser = new StudentEventRankingParser(fileInfo);
return rankingParser.Parse(students, events);
}
public Team[] GetTeams(IList<EventDefinition> competitiveEvents, IList<Student> students)
{
var studentEventRankingsCsv = "2024-25 RMS TSA student & eventDefinition - Nationals Teams.csv";
var fileInfo = FileUtility.GetContentFile(ContentDirectory, studentEventRankingsCsv);
var eventRankingsParser = new TeamParser(fileInfo);
var teams = eventRankingsParser.Parse(competitiveEvents, students);
foreach (var student in students)
{
student.Teams = teams.Where(t => t.Students.Contains(student)).ToList();
}
return teams;
}
public AssignmentAssumption[] GetAssignmentAssumptions(IList<EventDefinition> competitiveEvents, IList<Student> students)
{
var assumptionsCsv = "2024-25 RMS TSA student & eventDefinition - assumptions.csv";
var fileInfo = FileUtility.GetContentFile(ContentDirectory, assumptionsCsv);
var assumptionParser = new AssignmentAssumptionParser(fileInfo);
var assumptions = assumptionParser.Parse(competitiveEvents, students);
return assumptions;
}
public IDictionary<EventDefinition, List<EventOccurrence>> GetStateEventOccurrences(IList<EventDefinition> competitiveEvents)
{
var eventTimesFilename = "2025 TN TSA State Competition EventDefinition Times.txt";
var fileInfo = FileUtility.GetContentFile(ContentDirectory, eventTimesFilename);
var parser = new EventOccurrenceParser(fileInfo, competitiveEvents);
return parser.Parse();
}
public IDictionary<EventDefinition, List<EventOccurrence>> GetNationalEventOccurrences(IList<EventDefinition> competitiveEvents)
{
var eventTimesFilename = "2025 TSA Nationals Competition EventDefinition Times.txt";
var fileInfo = FileUtility.GetContentFile(ContentDirectory, eventTimesFilename);
var parser = new EventOccurrenceParser(fileInfo, competitiveEvents);
var nationalEventOccurrences = parser.Parse();
var locationPrefixes = new[] { "Cheekwood", "Ryman", "Lincoln", "Canal", "Online", "Magnolia", "Tennessee", "Bayou", "Hermitage", "Belmont", "Davidson", "Washington", "Belle Meade"};
var oredLocations = string.Join("|", locationPrefixes);
var regex = new Regex($"^(.*)((?:{oredLocations}).*)");
foreach (var occurrence in nationalEventOccurrences)
{
foreach (var eventOccurrence in occurrence.Value)
{
if (!string.IsNullOrEmpty(eventOccurrence.Location))
continue;
var match = regex.Match(eventOccurrence.Name);
if (!match.Success)
continue;
eventOccurrence.Name = match.Groups[1].Value.Trim();
eventOccurrence.Location = match.Groups[2].Value.Trim();
}
}
return nationalEventOccurrences;
}
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Events()
{
var competitiveEvents = GetEvents().Where(e => e.Name != "Chapter Team").ToArray();
return View(competitiveEvents);
}
public IActionResult StudentEventHandout()
{
var competitiveEvents = GetEvents();
var students = GetStudents(competitiveEvents);
var teams = GetTeams(competitiveEvents, students);
return View(Tuple.Create(students));
}
public IActionResult StudentEvents()
{
var events = GetEvents();
var students = GetStudents(events);
var rankings = GetStudentEventRankings(students, events);
var eventStudentPicksArray = StudentEventRanking.GetEventStudentRankings(rankings);
var assignmentParameters = new AssignmentParameters
{
EffortUpperBound = 9,
RequireOnSite = true,
RequireRegional = true,
TeamSizeLimit = 4
};
var eventAssignment = new EventAssigner(events, students, assignmentParameters);
var assignmentAssumptions = GetAssignmentAssumptions(events, students);
foreach (var assumption in assignmentAssumptions)
{
switch (assumption.Assumption)
{
case Assumption.Exclude:
eventAssignment.ExcludeFromEvent(assumption.EventAssignment);
break;
case Assumption.Include:
eventAssignment.AssignToEvent(assumption.EventAssignment);
break;
}
}
eventAssignment.RemoveEvent(new List<EventDefinition>
{
events.First(e => e.Name == "Chapter Team")
});
eventAssignment.IncludedEvents(new List<EventDefinition>
{
//competitiveEvents.First(e => e.Name == "System Control Technology")
});
var eventAssignmentsList = eventAssignment.Solve();
return View(Tuple.Create(events, students, eventStudentPicksArray, eventAssignmentsList, assignmentParameters));
}
public IActionResult Teams()
{
var competitiveEvents = GetEvents();
var students = GetStudents(competitiveEvents);
var teams = GetTeams(competitiveEvents, students);
//teams = teams.Where(t => t.EventDefinition.RegionalEvent).ToArray();
return View(Tuple.Create(teams));
}
public IActionResult Regionals()
{
var competitiveEvents = GetEvents();
var students = GetStudents(competitiveEvents);
var teams = GetTeams(competitiveEvents, students);
teams = teams.Where(t => t.EventDefinition.RegionalEvent).ToArray();
var enumerable = students.Where(s => !teams.SelectMany(ts => ts.Students).Contains(s)).ToArray();
return View(Tuple.Create(teams, enumerable));
}
public IActionResult State()
{
var competitiveEvents = GetEvents();
var students = GetStudents(competitiveEvents);
var teams = GetTeams(competitiveEvents, students);
var eventOccurrences = GetStateEventOccurrences(competitiveEvents);
// Filter out pre-conference
eventOccurrences =
(from kv in eventOccurrences
let newV = kv.Value.Where(eo => !eo.Name.Contains("Pre-Conference"))
select Tuple.Create(kv.Key, newV))
.ToDictionary(s => s.Item1, s => s.Item2.ToList());
return View(Tuple.Create(teams, students, eventOccurrences));
}
public IActionResult Nationals()
{
var competitiveEvents = GetEvents();
var students = GetStudents(competitiveEvents);
var teams = GetTeams(competitiveEvents, students);
var eventOccurrences = GetNationalEventOccurrences(competitiveEvents);
// Filter out pre-conference
eventOccurrences =
(from kv in eventOccurrences
let newV = kv.Value.Where(eo => !eo.Name.Contains("Pre-Conference"))
select Tuple.Create(kv.Key, newV))
.ToDictionary(s => s.Item1, s => s.Item2.ToList());
return View(Tuple.Create(teams, students, eventOccurrences));
}
public IActionResult TeamGrid()
{
var competitiveEvents = GetEvents();
var students = GetStudents(competitiveEvents);
var teams = GetTeams(competitiveEvents, students);
return View(Tuple.Create(teams, students));
}
public IActionResult Students()
{
var competitiveEvents = GetEvents();
var students = GetStudents(competitiveEvents);
GetTeams(competitiveEvents, students);
return View(Tuple.Create(students));
}
public static Func<T, bool> And<T>(params Func<T, bool>[] predicates)
{
return t => predicates.All(predicate => predicate(t));
}
public IActionResult Schedule()
{
var scheduleOptions =
new ScheduleOptions(
timeSlots: 3,
mustIncludeEvents:new []
{
"Medical Technology", "Electrical Applications" //, "RegionalTeam",
,"Dragster", "Flight"
},
extended: new string[]
{
"Invention", "Construction Challenge", "Mechanical", "Mass", "Micro"
//"STEM"
//"Community", "Vlogging"// "Microcontroller"
},
omittedEvents: new string[]
{
"Vlogging", "Junior", "Community Service Video", "Digital Photography",
"STEM"
//"Leadership",// "Electrical", //"Construction"
// "Forensic",
//"CAD"
//"I&I Team 1", "I&I Team 2"//, "Website Design",
},
absentStudents: new string[]
{
//"Eliam"
},
reverse: true,
slotPush:0);
var events = GetEvents();
var students = GetStudents(events);
var allTeams = GetTeams(events, students);
var omittedEvents = allTeams.Where(t => scheduleOptions.OmittedEvents?.Any(s => t.Name.Contains(s)) == true).ToArray();
allTeams = allTeams.Where(t => !omittedEvents.Contains(t)).ToArray();
bool RegionalPredicate(Team t) => t.EventDefinition.RegionalEvent;
bool TeamPredicate(Team t) => t.EventDefinition.EventFormat is EventFormat.Team;
bool RegionalTeamPredicate(Team t) => RegionalPredicate(t) && TeamPredicate(t);
bool HighEffort(Team t) => t.EventDefinition.LevelOfEffort == 3;
bool LowEffort(Team t) => t.EventDefinition.LevelOfEffort == 1;
bool RegionalTeamSomeEffortPredicate(Team t) => RegionalTeamPredicate(t) && !LowEffort(t);
bool IndividualPredicate(Team t) => t.EventDefinition.EventFormat is EventFormat.Individual;
bool negativePredicate(Team t) => false;
var mustIncludeTeams =
from e in events
from t in allTeams
where t.EventDefinition == e &&
(scheduleOptions.MustIncludeEvents?.Any(s => s == "RegionalTeam") == true && RegionalTeamSomeEffortPredicate(t)
|| scheduleOptions.MustIncludeEvents?.Any( t.EventDefinition.Name.Contains) == true
|| HighEffort(t))
select t;
Debug.WriteLine("Must Include: " + string.Join(", ", mustIncludeTeams.Select(t => t.ToStringWithIndividualAndRegional())));
Debug.WriteLine("Omitted: " + string.Join(", ", omittedEvents.Select(t => t.ToStringWithIndividualAndRegional())));
var teamScheduler = new TeamScheduler(mustIncludeTeams.ToArray(), scheduleOptions.TimeSlots);
var schedule = teamScheduler.Solve();
//schedule = schedule.OrderByDescending(ts => new Random(schedule.GetHashCode() + 1).Next()).ToArray();
if (scheduleOptions.SlotPush != 0)
{
schedule = schedule.Skip(scheduleOptions.SlotPush).Concat(schedule.Take(scheduleOptions.SlotPush))
.ToArray();
}
if (scheduleOptions.Reverse)
schedule = schedule.Reverse().ToArray();
////// extend schedules
var extendedTeams =
from t in allTeams
where
scheduleOptions.MustIncludeEvents?.Any(s => scheduleOptions.ExtendedTeams?.Any(et => t.Name.Contains(et)) == true) == true
select t;
foreach (var extendedTeam in extendedTeams)
{
schedule = new UnassignedStudentScheduler(allTeams, schedule).AddAdditionalTimeSlot(extendedTeam);
}
schedule = new UnassignedStudentScheduler(allTeams, schedule).ScheduleStrategy(UnassignedScheduleStrategy.LevelOfEffort);
schedule = new UnassignedStudentScheduler(allTeams, schedule).ScheduleStrategy(UnassignedScheduleStrategy.BiggestGroup);
schedule = new UnassignedStudentScheduler(allTeams, schedule).ScheduleStrategy(UnassignedScheduleStrategy.IndividualEvents);
//schedule = new UnassignedStudentScheduler(allTeams, schedule).ScheduleStrategy(UnassignedScheduleStrategy.AnyNotMeetingAlready);
var unassignedStudents = UnassignedStudentScheduler.UnassignedStudents(students, schedule).ToArray();
return View(Tuple.Create(schedule, unassignedStudents, scheduleOptions));
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
@@ -1,25 +0,0 @@
namespace Web.Controllers;
public partial class HomeController
{
public class ScheduleOptions(
int timeSlots = 3,
string[]? absentStudents = null,
string[]? extended = null,
string[]? omittedEvents = null,
string[]? mustIncludeEvents = null,
bool reverse = false,
DateTime date = new(),
int slotPush = 0
)
{
public int TimeSlots = timeSlots;
public string[]? AbsentStudents = absentStudents;
public string[]? ExtendedTeams = extended;
public string[]? OmittedEvents = omittedEvents;
public string[]? MustIncludeEvents = mustIncludeEvents;
public bool Reverse { get; } = reverse;
public int SlotPush { get; } = slotPush;
public DateTime Date { get; } = date == new DateTime() ? DateTime.Today : date;
}
}
-26
View File
@@ -1,26 +0,0 @@
namespace Web
{
public class LabelHelper
{
public static string GetOrderClass(int pick)
{
switch (pick)
{
case 1:
return "first-pick";
case 2:
return "second-pick";
case 3:
return "third-pick";
case 4:
return "fourth-pick";
case 5:
return "fifth-pick";
case 6:
return "sixth-pick";
default:
return "non-pick";
}
}
}
}
-9
View File
@@ -1,9 +0,0 @@
namespace Web.Models
{
public class ErrorViewModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}
-23
View File
@@ -1,23 +0,0 @@
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
@@ -1,28 +0,0 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:28852",
"sslPort": 0
}
},
"profiles": {
"Web": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5016",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -1,15 +0,0 @@
@model int?
@{
switch (Model)
{
case 1:
<text>★☆☆</text>
break;
case 2:
<text>★★☆</text>
break;
case 3:
<text>★★★</text>
break;
}
}
@@ -1,15 +0,0 @@
@model int?
@{
switch (Model)
{
case 1:
<text>☆</text>
break;
case 2:
<text>✯</text>
break;
case 3:
<text>★</text>
break;
}
}
-66
View File
@@ -1,66 +0,0 @@
@using Core.Entities
@using Core.Utility
@model EventDefinition[]
@{
ViewData["Title"] = "Events Page";
}
<div>
@foreach (var evt in Model.OrderBy(e => e.Name))
{
<div class="container nobrk">
@if (evt.RegionalEvent)
{
<div class="row">
<div class="col">
<i>Regional EventDefinition</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">
@Html.Partial("EffortStarsPartial", evt.LevelOfEffort)
</div>
<div class="col-2">
@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 2024-25:</i></div>
<div class="col">@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>
-5
View File
@@ -1,5 +0,0 @@
@using Core.Entities
@using Core.Utility
@{
ViewData["Title"] = "Home Page";
}
-355
View File
@@ -1,355 +0,0 @@
@using System.Text.RegularExpressions
@using Core.Entities
@model Tuple<Team[], Student[], IDictionary<EventDefinition, List<EventOccurrence>>>
@{
ViewData["Title"] = "Teams";
var eventOccurrences = Model.Item3;
}
<div class="container nobrk pt-5">
<h2>Nationals EventDefinitions</h2>
<table class="table">
<thead>
<tr>
<td>Team</td>
<td>Team Members</td>
<td>Dates</td>
<td>Materials</td>
</tr>
</thead>
@foreach (var team in Model.Item1)
{
var students = team.Students;
@if (true @* team.EventDefinition.EventDefinitionFormat == EventDefinitionEventDefinitionFormat.Team *@)
{
<tr>
<td>
@* <strong class="@{ GetTeamClass(team);}">@team.Name</strong> *@
@team.Name @* #@team.TeamNumber
*@ </td>
<td>
@{
var first = true;
}
@foreach (var student in students.OrderByDescending(s => (s.Grade + s.TsaYear) * (team.Captain == s ? 2 : 1)).ThenBy(s => s.FirstNameLastName))
{
@if (!first)
{
<text>, </text>
}
first = false;
@student.FirstNameLastName @if (team.Captain == student)
{
<text> (Cpt. @team.Captain.NationalID)</text>
}
}
</td>
<td>
@* @if (team.EventDefinition.StatePresubmission)
{
<text>Pre-submission due Friday, March 14</text>
}
@if (team.EventDefinition.StatePretesting)
{
<text>Pre-testing Wednesday, April 2nd</text>
}
@if (team.EventDefinition.StatePreliminaryRound)
{
<text>Preliminary and Semifinalist Rounds</text>
} *@
</td>
<td>
@team.EventDefinition.Documentation
</td>
</tr>
}
@* else if (team.EventDefinition.EventDefinitionFormat == EventDefinitionEventDefinitionFormat.Individual)
{
foreach (var student in students)
{
<tr>
<td>
@team.EventDefinition.Name - @student.FirstNameLastName (@student.RegionalID)
</td>
<td>@team.RegionalTimeSlot</td>
<td></td>
</tr>
}
} *@
}
</table>
</div>
@{
var s = Model.Item2.OrderBy(s => s.LastNameFirstName);
@foreach (var student in s)
{
<div class="container nobrk pt-5" style="page-break-before: always;">
<h3>@student.FirstNameLastName - @student.NationalID</h3>
<h4>TSA 2025 Nationals Schedule</h4>
<div class="row">
<div class="col col-2">EventDefinitions</div>
<div class="col">
@foreach (var ev in student.Teams)
{
<div class="row">
<div class="col col-2">
@if (ev.EventDefinition.EventFormat is EventFormat.Team)
{
@ev.TeamNumber
}
else
{
@ev.Captain.NationalID
}
</div>
<div class="col-4">
<text> @ev.Name
@if (ev.Captain == student)
{
<strong>(Cpt.)</strong>
}
</text>
</div>
<div class="col-6">
@ev.EventDefinition.SemifinalistActivity
</div>
</div>
}
</div>
</div>
<table class="table">
<thead>
<tr>
<td>Time</td>
<td>EventDefinition</td>
<td></td>
<td>Location</td>
</tr>
</thead>
@foreach (var date in
eventOccurrences
.Where(eo =>
student.Teams.Select(t => t.EventDefinition).Any(a => a == eo.Key)
|| eo.Key == EventDefinition.GeneralSchedule
|| (eo.Key == EventDefinition.VotingDelegates && student.VotingDelegate))
.SelectMany(eo => eo.Value.Select(v => Tuple.Create(v, eo.Key)))
.GroupBy(de => de.Item1.StartTime.Date)
.OrderBy(d => d.Key)
)
{
var eventsForThisDay =
date
.Where(de => de.Item1.Name != "Judging")
.Where(de => de.Item1.StartTime > new DateTime(2024, 3, 1))
// filter out occurrences where non-captain
.Where(de =>
!de.Item1.SignupSubmitPickup
|| de.Item2.EventFormat is EventFormat.Individual
|| student.Teams.Any(t => t.Captain == student && t.EventDefinition == de.Item2)
)
.OrderBy(de => de.Item1.StartTime);
@if (!eventsForThisDay.Any())
continue;
<tr>
<td colspan="4" class="align-content-center text-center fw-bold">@date.Key.ToString("MMMM d")</td>
</tr>
@foreach (var eventOccurrence in eventsForThisDay.OrderBy(de => de.Item1.StartTime))
{
string hlClass = null;
@if (!eventOccurrence.Item2.Name.Contains("General"))
{
hlClass = "fw-bold";
}
<tr>
<td class="@hlClass" style="white-space:nowrap;">@eventOccurrence.Item1.Time</td>
<td class="@hlClass">@eventOccurrence.Item2.Name</td>
<td class="@hlClass">
@eventOccurrence.Item1.Name
@if (eventOccurrence.Item1.Name.Contains("Pick") && eventOccurrence.Item2.EventFormat is EventFormat.Team)
{
<br/>
<text>or coordinate with a teammate</text>
}
</td>
<td>@eventOccurrence.Item1.Location
@if (eventOccurrence.Item1.Location == "Online" && eventOccurrence.Item1.Name.Contains("Sign-up") ) { <text>by Advisor</text>}
</td>
</tr>
}
}
</table>
</div>
}
}
@{
<div class="container nobrk pt-5">
<h2>Combined Schedule</h2>
<table class="table">
<thead>
<tr>
<td>Time</td>
<td>Team</td>
<td></td>
<td>Location</td>
</tr>
</thead>
<tbody>
@foreach (var eventsForDate
in Model.Item3.SelectMany(eo => eo.Value.Select(e => Tuple.Create(e, eo.Key)))
.Where(de => de.Item1.Name != "Judging")
.GroupBy(eo => eo.Item1.StartTime.Date)
.OrderBy(eo => eo.Key)
)
{
<tr><td colspan="4"><strong>@eventsForDate.Key.ToString("MMMM d") </strong> </td></tr>
@foreach (var occurrence in eventsForDate.OrderBy(o => o.Item1.StartTime))
{
var teams = Model.Item1.Where(t => t.EventDefinition == occurrence.Item2);
if (occurrence.Item2 != EventDefinition.GeneralSchedule && occurrence.Item2 != EventDefinition.VotingDelegates && !teams.Any())
continue;
<tr>
<td style="white-space:nowrap;">@occurrence.Item1.Time</td>
<td>
@if (occurrence.Item2 == EventDefinition.GeneralSchedule)
{
<text>Everyone</text>
}
else if (occurrence.Item2 == EventDefinition.VotingDelegates)
{
<text>Voting Delegates - @string.Join(", ", Model.Item2.Where(stu => stu.VotingDelegate).Select(stu => stu.FirstName))</text>
}
@foreach (var team in teams)
{
<text>@team</text>
<text> - @string.Join(", ", team.Students.Select(stu => stu.FirstName))</text>
}
</td>
<td>
@occurrence.Item1.Name
@if (occurrence.Item1.SignupSubmitPickup)
{
<br/>
<text>1 Team Member</text>
}
@if (occurrence.Item1.Name.Contains("Semifinalist") && (occurrence.Item1.Name.Contains("Interview") || occurrence.Item1.Name.Contains("Presentation")))
{
<br/>
<text>@Regex.Match(@occurrence.Item2.SemifinalistActivity, @"(?<=\().*?(?=\))").Value</text>
}
</td>
<td>@occurrence.Item1.Location</td>
</tr>
}
}
</tbody>
</table>
</div>
}
<div class="container nobrk pt-5">
<h2>Students</h2>
<table class="table-primary">
<thead>
<tr>
<td>Student</td>
<td>ID</td>
</tr>
</thead>
@foreach (var student in Model.Item2.OrderByDescending(s => s.Officer.Contains("President")).ThenBy(s => s.LastNameFirstName))
{
var assignments
= student.Teams.Where(ea => ea.Students.Contains(student))
.Distinct()
.OrderBy(e => e.Name).ToList();
<tr>
<td>
<strong>@student.FirstNameLastName</strong> @if (!string.IsNullOrEmpty(student.Officer))
{
<text>(@student.Officer)</text>
}
</td>
<td>@student.NationalID</td>
@foreach (var t in assignments)
{
<td>
@if (t.EventDefinition.EventFormat != EventFormat.Individual)
{
@t.Name
@if (t.Captain == student)
{
<text> (Captain)</text>
}
}
else
{
@t.EventDefinition.Name
}
</td>
}
</tr>
}
</table>
</div>
<div class="container nobrk pt-5">
<h2>Students</h2>
@foreach (var student in Model.Item2.OrderByDescending(s => s.Officer.Contains("President")).ThenBy(s => s.LastNameFirstName))
{
var assignments
= student.Teams.Where(ea => ea.Students.Contains(student))
.Distinct()
.OrderBy(e => e.Name).ToList();
<p>
<strong>@student.FirstNameLastName</strong>
@if (!string.IsNullOrEmpty(student.Officer))
{
<text>(@student.Officer)</text>
}
<text>@student.NationalID</text>
</p>
@foreach (var t in assignments)
{
<p>
@if (t.EventDefinition.EventFormat != EventFormat.Individual)
{
@t.Name
@if (t.Captain == student)
{
<text> (Captain)</text>
}
}
else
{
@t.EventDefinition.Name
}
</p>
}
}
</div>
-6
View File
@@ -1,6 +0,0 @@
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
-123
View File
@@ -1,123 +0,0 @@
@using Core.Entities
@using Core.Utility
@model Tuple<Team[], Student[]>
@{
ViewData["Title"] = "Teams";
}
<table class="table">
<thead>
<tr>
<td>Team</td>
@*<td>Time Slot</td>*@
<td>Notes</td>
<td>Team Members</td>
</tr>
</thead>
@foreach (var team in Model.Item1/*.OrderBy(t => t.RegionalTimeSlotObj)*/)
{
var students = team.Students;
@if (team.EventDefinition.EventFormat == EventFormat.Team)
{
<tr>
<td>
@* <strong class="@{ GetTeamClass(team);}">@team.Name</strong> *@
@team.Name #@team.TeamNumber
</td>
@*<td>
@team.RegionalTimeSlot
</td>*@
<td>
@team.EventDefinition.Notes
</td>
<td>
@{
var first = true;
}
@foreach (var student in students.OrderByDescending(s => (s.Grade + s.TsaYear) * (team.Captain == s ? 2 : 1)).ThenBy(s => s.FirstNameLastName))
{
@if (!first)
{
<text>, </text>
}
first = false;
@student.FirstNameLastName @if (team.Captain == student)
{
<text> (Cpt.) (@student.RegionalID)</text>
}
}
</td>
</tr>
}
else if (team.EventDefinition.EventFormat == EventFormat.Individual)
{
foreach (var student in students)
{
<tr>
<td>
@team.EventDefinition.Name #@student.RegionalID
</td>
<td>@team.EventDefinition.Notes</td>
<td>@student.FirstNameLastName</td>
</tr>
}
}
}
</table>
@{
var s = Model.Item1.SelectMany(t => t.Students).Distinct().OrderBy(s => s.LastNameFirstName);
<table class="table">
<thead>
<tr>
<td>Student</td>
<td>Regional ID</td>
<td>Team</td>
<td>Time Slot</td>
</tr>
</thead>
@foreach (var student in s)
{
<tr>
<td>@student.FirstNameLastName</td>
<td>@student.RegionalID</td>
<td></td>
<td></td>
</tr>
var teams = Model.Item1.Where(t => t.Students.Contains(student));
foreach (var team in teams/*.OrderBy(t => t.RegionalTimeSlotObj)*/)
{
<tr>
<td></td>
<td></td>
<td>@team.Name
@if (team.Captain == student)
{
<text>(Cpt.)</text>
}
</td>
<td>@team.EventDefinition.Notes @*@team.RegionalTimeSlot*@</td>
</tr>
}
}
</table>
}
<p>No regional eventDefinitions: @string.Join(", ", @Model.Item2.Select(s=> s.FirstName))</p>
@functions
{
public void GetOrderClass(int pick)
{
@Html.Raw(LabelHelper.GetOrderClass(pick))
}
private void GetTeamClass(Team team)
{
if (team.EventDefinition.RegionalEvent)
{
@Html.Raw("regional")
;
}
}
}
-130
View File
@@ -1,130 +0,0 @@
@using Core.Entities
@model Tuple<IList<Team>[], IEnumerable<Student>[], Web.Controllers.HomeController.ScheduleOptions>
@{
ViewData["Title"] = "Schedule";
var slot = 0;
var schedule = Model.Item1;
var unassignedStudents = Model.Item2;
var allStudents = schedule.SelectMany(t => t).SelectMany(t => t.Students).Distinct().ToList();
var allMeetingTeams = schedule.SelectMany(t => t).Distinct();
}
<div class="bluewhite p-5">
@{
List<(Student student, IEnumerable<Team> teams)> overlaps;
}
@foreach (var timeslot in schedule)
{
overlaps = TeamSchedulerSolution.GetStudentTeamOverlaps(timeslot).ToList();
var partialTeams = timeslot.Where(t => t is PartialTeam && t.EventDefinition.EventFormat is not EventFormat.Individual);
var fullTeams = timeslot.Where(t => !partialTeams.Contains(t));
<h3>Time Slot @(slot + 1)</h3>
<p>@Model.Item3.Date.ToShortDateString()</p>
<h4>Teams</h4>
<table class="table schedule">
<thead>
<th class="col-2"></th>
<th class="col-3"></th>
<th class="col-5"></th>
</thead>
<tbody>
@foreach (var team in fullTeams.Where(t => t.EventDefinition.EventFormat is EventFormat.Team).OrderBy(t => t.Name))
{
@await Html.PartialAsync("ScheduleTeamPartial", Tuple.Create(team, overlaps, Model.Item3.AbsentStudents))
}
</tbody>
</table>
if (partialTeams.Any())
{
<table class="table schedule">
<thead>
<th class="col-2"></th>
<th class="col-3"></th>
<th class="col-5"></th>
</thead>
<tbody>
@foreach (var team in partialTeams.OrderBy(t => t.Name))
{
@await Html.PartialAsync("ScheduleTeamPartial", Tuple.Create(team, overlaps, Model.Item3.AbsentStudents))
}
</tbody>
</table>
}
<h4>Individual</h4>
<p><i>Use time for individual eventDefinition or to <strong>work on a team eventDefinition</strong> </i></p>
<table class="table schedule">
<tbody>
<tr>
@foreach (var team in fullTeams.Where(t => t.EventDefinition.EventFormat is EventFormat.Individual).OrderBy(t => t.Name))
{
<td>
<strong>@team.Captain?.FirstName</strong> (@team.EventDefinition.Name@if(team.EventDefinition.RegionalEvent){ @* <text> (<i>Regional</i>)</text> *@})
</td>
@* @await Html.PartialAsync("ScheduleTeamPartial", Tuple.Create(team, overlaps)) *@
}
@foreach (var student in unassignedStudents[slot])
{
<td>
<strong>@student.FirstName</strong>
</td>
}
</tr>
</tbody>
</table>
slot++;
}
<h3>Missed team eventDefinitions for today</h3>
<table class="table schedule">
@foreach (var student in allStudents.OrderBy(s => s.FirstName))
{
var studentMeetings = student.Teams.Where(t => allMeetingTeams.Any(mt => mt.Name == t.Name && mt.Students.Contains(student)));
var studentMissed = student.Teams.Where(t => studentMeetings.All(mt => mt.Name != t.Name)).ToList();
var studentMissedTeams = studentMissed.Where(t => t.EventDefinition.EventFormat is EventFormat.Team);
var studentMissedIndividual = studentMissed.Where(t => t.EventDefinition.EventFormat is EventFormat.Individual);
if (studentMissedTeams.Any())
{
<tr>
<td class="col-4">@student.FirstName</td>
<td>
@string.Join(", ", studentMissedTeams.Select(t => t.ToStringWithIndividualAndRegional()))
</td>
</tr>
}
if (studentMissedIndividual.Any())
{
<tr>
<td class="col-4">@student.FirstName</td>
<td>
@string.Join(", ", studentMissedIndividual.Select(t => t.ToStringWithIndividualAndRegional()))
</td>
</tr>
}
}
</table>
</div>
@functions
{
private void GetTeamClass(Team team)
{
if (team.EventDefinition.RegionalEvent)
{
@Html.Raw("regional")
;
}
}
}
@@ -1,75 +0,0 @@
@using Core.Entities
@model Tuple<Core.Entities.Team, List<(Student student, IEnumerable<Team> teams)>, string[]?>
@{
var team = Model.Item1;
var overlaps = Model.Item2;
}
<tr>
<td class="@{ GetTeamClass(team); } col-6" >
<strong>@team</strong>
@if (!string.IsNullOrEmpty(team.EventDefinition.EventAttributes()))
{
<i>(@team.EventDefinition.EventAttributes())</i>
}
<small><i>@team.EventDefinition.SemifinalistActivity</i></small>
@* @if (team.EventDefinition.StatePresubmission)
{
<small>(pre-submission)</small>
}
@if (team.EventDefinition.StatePretesting)
{
<small>(pre-testing)</small>
} *@
</td>
<td>
@{ var first = true; }
@foreach (var student in team.Students.OrderByDescending(s => (s.Grade + s.TsaYear) * (team.Captain == s ? 2 : 1)))
{
if (!first)
{
<text>, </text>
}
{
first = false;
}
@if (overlaps.Any(t => t.student == student))
{
<span style="color: #F66">@student.FirstName</span>
}
else if(Model.Item3?.Any(s => student.FirstNameLastName.Contains(s))== true)
{
<span style="color: lightgray">@student.FirstName</span>
}
else
{
@student.FirstName
}
@if (team.Captain == student)
{
<span class="text-warning small">•</span>
@* <i class="bi bi-chevron-double-up text-warning small"></i> *@
}
}
</td>
<td>
@team.EventDefinition.Documentation
</td>
</tr>
@functions
{
private void GetTeamClass(Team team)
{
// if (team.EventDefinition.RegionalEvent)
// {
// @Html.Raw("regional")
// ;
// }
}
}
-358
View File
@@ -1,358 +0,0 @@
@using System.Text.RegularExpressions
@using Core.Entities
@using Core.Utility
@model Tuple<Team[], Student[], IDictionary<EventDefinition, List<EventOccurrence>>>
@{
ViewData["Title"] = "Teams";
var eventOccurrences = Model.Item3;
}
<div class="container nobrk pt-5">
<h2>State EventDefinitions</h2>
<table class="table">
<thead>
<tr>
<td>Team</td>
<td>Team Members</td>
<td>Dates</td>
<td>Materials</td>
</tr>
</thead>
@foreach (var team in Model.Item1)
{
var students = team.Students;
@if (true @* team.EventDefinition.EventDefinitionFormat == EventDefinitionEventDefinitionFormat.Team *@)
{
<tr>
<td>
@* <strong class="@{ GetTeamClass(team);}">@team.Name</strong> *@
@team.Name @* #@team.TeamNumber
*@ </td>
<td>
@{
var first = true;
}
@foreach (var student in students.OrderByDescending(s => (s.Grade + s.TsaYear) * (team.Captain == s ? 2 : 1)).ThenBy(s => s.FirstNameLastName))
{
@if (!first)
{
<text>, </text>
}
first = false;
@student.FirstNameLastName @if (team.Captain == student)
{
<text> (Cpt. @team.Captain.StateID)</text>
}
}
</td>
<td>
@if (team.EventDefinition.StatePresubmission)
{
<text>Pre-submission due Friday, March 14</text>
}
@if (team.EventDefinition.StatePretesting)
{
<text>Pre-testing Wednesday, April 2nd</text>
}
@if (team.EventDefinition.StatePreliminaryRound)
{
<text>Preliminary and Semifinalist Rounds</text>
}
</td>
<td>
@team.EventDefinition.Documentation
</td>
</tr>
}
@* else if (team.EventDefinition.EventDefinitionFormat == EventDefinitionEventDefinitionFormat.Individual)
{
foreach (var student in students)
{
<tr>
<td>
@team.EventDefinition.Name - @student.FirstNameLastName (@student.RegionalID)
</td>
<td>@team.RegionalTimeSlot</td>
<td></td>
</tr>
}
} *@
}
</table>
</div>
@{
var s = Model.Item2.OrderBy(s => s.LastNameFirstName);
@foreach (var student in s)
{
<div class="container nobrk pt-5" style="page-break-before: always;">
<h3>@student.FirstNameLastName - @student.StateID</h3>
<h4>TSA 2025 TN State Schedule</h4>
<div class="row">
<div class="col col-2">EventDefinitions</div>
<div class="col">
@foreach (var ev in student.Teams)
{
<div class="row">
<div class="col col-2">
@if (ev.EventDefinition.EventFormat is EventFormat.Team)
{
@ev.TeamNumber
}
else
{
@ev.Captain.StateID
}
</div>
<div class="col-4">
<text> @ev.Name
@if (ev.Captain == student)
{
<strong>(Cpt.)</strong>
}
</text>
</div>
<div class="col-6">
@ev.EventDefinition.SemifinalistActivity
</div>
</div>
}
</div>
</div>
<table class="table">
<thead>
<tr>
<td>Time</td>
<td>EventDefinition</td>
<td></td>
<td>Location</td>
</tr>
</thead>
@foreach (var date in
eventOccurrences
.Where(eo =>
student.Teams.Select(t => t.EventDefinition).Any(a => a == eo.Key)
|| eo.Key == EventDefinition.GeneralSchedule
|| (eo.Key == EventDefinition.VotingDelegates && student.VotingDelegate))
.SelectMany(eo => eo.Value.Select(v => Tuple.Create(v, eo.Key)))
.GroupBy(de => de.Item1.Date + ", " + de.Item1.StartTime.DayOfWeek)
.OrderBy(d => d.Key)
)
{
var eventsForThisDay =
date
.Where(de => de.Item1.Name != "Judging")
.Where(de => de.Item1.StartTime > new DateTime(2024, 3, 1))
// filter out occurrences where non-captain
.Where(de =>
!de.Item1.SignupSubmitPickup
|| de.Item2.EventFormat is EventFormat.Individual
|| student.Teams.Any(t => t.Captain == student && t.EventDefinition == de.Item2)
)
.OrderBy(de => de.Item1.StartTime);
@if (!eventsForThisDay.Any())
continue;
<tr>
<td colspan="4"><strong>@date.Key</strong></td>
</tr>
@foreach (var eventOccurrence in eventsForThisDay.OrderBy(de => de.Item1.StartTime))
{
<tr>
<td>@eventOccurrence.Item1.Time</td>
<td>@eventOccurrence.Item2.Name</td>
<td>@eventOccurrence.Item1.Name
@if (eventOccurrence.Item1.Name.Contains("Pick") && eventOccurrence.Item2.EventFormat is EventFormat.Team)
{
<br/>
<text>or coordinate with a teammate</text>
}
</td>
<td>@eventOccurrence.Item1.Location
@if (eventOccurrence.Item1.Location == "Online" && eventOccurrence.Item1.Name.Contains("Sign-up") ) { <text>by Advisor</text>}
</td>
</tr>
}
}
</table>
</div>
}
}
@{
<div class="container nobrk pt-5">
<h2>Combined Schedule</h2>
<table class="table">
<thead>
<tr>
<td>Time</td>
<td>Team</td>
<td></td>
<td>Location</td>
</tr>
</thead>
<tbody>
@foreach (var eventsForDate
in Model.Item3.SelectMany(eo => eo.Value.Select(e => Tuple.Create(e, eo.Key)))
.Where(de => de.Item1.Name != "Judging")
.GroupBy(eo => eo.Item1.StartTime.Date)
.OrderBy(eo => eo.Key)
)
{
<tr><td colspan="4"><strong>@eventsForDate.Key.ToString("MMMM d") </strong> </td></tr>
@foreach (var occurrence in eventsForDate.OrderBy(o => o.Item1.StartTime))
{
var teams = Model.Item1.Where(t => t.EventDefinition == occurrence.Item2);
if (occurrence.Item2 != EventDefinition.GeneralSchedule && occurrence.Item2 != EventDefinition.VotingDelegates && !teams.Any())
continue;
<tr>
<td>@occurrence.Item1.Time</td>
<td>
@if (occurrence.Item2 == EventDefinition.GeneralSchedule)
{
<text>Everyone</text>
}
else if (occurrence.Item2 == EventDefinition.VotingDelegates)
{
<text>Voting Delegates - @string.Join(", ", Model.Item2.Where(stu => stu.VotingDelegate).Select(stu => stu.FirstName))</text>
}
@foreach (var team in teams)
{
<text>@team</text>
<text> - @string.Join(", ", team.Students.Select(stu => stu.FirstName))</text>
}
</td>
<td>
@occurrence.Item1.Name
@if (occurrence.Item1.SignupSubmitPickup)
{
<br/>
<text>1 Team Member</text>
}
@if (occurrence.Item1.Name.Contains("Semifinalist") && (occurrence.Item1.Name.Contains("Interview") || occurrence.Item1.Name.Contains("Presentation")))
{
<br/>
<text>@Regex.Match(@occurrence.Item2.SemifinalistActivity, @"(?<=\().*?(?=\))").Value</text>
}
</td>
<td>@occurrence.Item1.Location</td>
</tr>
}
}
</tbody>
</table>
</div>
}
<div class="container nobrk pt-5">
<h2>Students</h2>
<table class="table-primary">
<thead>
<tr>
<td>Student</td>
<td>ID</td>
</tr>
</thead>
@foreach (var student in Model.Item2.OrderByDescending(s => s.Officer.Contains("President")).ThenBy(s => s.LastNameFirstName))
{
var assignments
= student.Teams.Where(ea => ea.Students.Contains(student))
.Distinct()
.OrderBy(e => e.Name).ToList();
<tr>
<td>
<strong>@student.FirstNameLastName</strong> @if (!string.IsNullOrEmpty(student.Officer))
{
<text>(@student.Officer)</text>
}
</td>
<td>@student.StateID</td>
@foreach (var t in assignments)
{
<td>
@if (t.EventDefinition.EventFormat != EventFormat.Individual)
{
@t.Name
@if (t.Captain == student)
{
<text> (Captain)</text>
}
}
else
{
@t.EventDefinition.Name
}
@{
if (t.EventDefinition.EventFormat == EventFormat.Individual)
{
<sup class="activity">(ind)</sup>
}
if (t.EventDefinition.OnSiteActivity)
{
<sup class="activity">(act)</sup>
}
}
</td>
}
</tr>
}
</table>
</div>
<div class="container nobrk pt-5">
<h2>Students</h2>
@foreach (var student in Model.Item2.OrderByDescending(s => s.Officer.Contains("President")).ThenBy(s => s.LastNameFirstName))
{
var assignments
= student.Teams.Where(ea => ea.Students.Contains(student))
.Distinct()
.OrderBy(e => e.Name).ToList();
<p>
<strong>@student.FirstNameLastName</strong>
@if (!string.IsNullOrEmpty(student.Officer))
{
<text>(@student.Officer)</text>
}
<text>@student.StateID</text>
</p>
@foreach (var t in assignments)
{
<p>
@if (t.EventDefinition.EventFormat != EventFormat.Individual)
{
@t.Name
@if (t.Captain == student)
{
<text> (Captain)</text>
}
}
else
{
@t.EventDefinition.Name
}
</p>
}
}
</div>
@@ -1,79 +0,0 @@
@using Core.Entities
@model Tuple<Student[]>
@{
ViewData["Title"] = "Student Handout Page";
}
@foreach (var s in Model.Item1.OrderBy(n => n.FirstName))
{
<div class="container nobrk pt-5">
<p><i>@DateTime.Today.ToShortDateString()</i></p>
<h2><i>TSA teams and eventDefinitions:</i> @s.FirstNameLastName </h2>
@foreach (var team in
s.Teams.OrderByDescending(t => t.EventDefinition.EventFormat == EventFormat.Team)
.ThenByDescending(t => t.EventDefinition.LevelOfEffort)
.ThenByDescending(t => t.Name))
{
var evt = team.EventDefinition;
<div>
@if (evt.RegionalEvent)
{
<div class="row">
<div class="col">
<i>Regional EventDefinition</i>
</div>
</div>
}
<div div class="row">
<div class="col-6">
<h5>@evt.Name&nbsp;&nbsp;&nbsp;@Html.Partial("EffortStarsPartial", evt.LevelOfEffort)</h5>
</div>
<div class="col-4">
@if (evt.EventFormat is EventFormat.Team)
{
<html>
<strong>Teammates</strong><br/>
@string.Join(", ", team.Students.OrderByDescending(s => s.Grade + s.TsaYear).ThenBy(s => s.FirstName).Where(tm => tm != s).Select(tm => tm.FirstName))
</html>
}
else
{
<html>
<strong>@evt.EventFormat</strong>
</html>
}
</div>
<div class="col-2">
@evt.SemifinalistActivity
@if (evt.StatePresubmission)
{<text>, State Presubmission <strong>due March 14th</strong></text>}
@if (evt.StatePretesting)
{<text>, State Pre-testing <strong>April 2nd</strong></text>}
@if (evt.StatePreliminaryRound)
{<text>, State Preliminary and Semifinalist Rounds</text>}
</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 2024-25:</i></div>
<div class="col">@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>
}
@@ -1,210 +0,0 @@
@using Core.Entities
@using Core.Utility
@model Tuple<EventDefinition[], Student[], EventStudentRankings[], Team[], AssignmentParameters>
@{
ViewData["Title"] = "Home Page";
var maxStudentPicks = Model.Item3.MaxBy(picks => picks.StudentRankings.Count).StudentRankings.Count;
var parameters = Model.Item5;
var unassignedEvents = Model.Item1.Where(e => Model.Item4.All(t => t.EventDefinition != e));
}
<table class="table-primary">
<tr>
<td colspan="8">
Effort Limit:<strong>@parameters.EffortUpperBound</strong>
&nbsp;&nbsp;Require Regionals:<strong>@parameters.RequireRegional</strong>
&nbsp;&nbsp;Require On-Site Activity:<strong>@parameters.RequireOnSite</strong>
</td>
</tr>
</table>
<br/>
<table class="table-primary">
<thead>
<tr><td>Student</td><td>Level of Effort Total</td></tr>
</thead>
@foreach (var student in Model.Item2.OrderBy(s => s.FirstName))
{
var assignments
= Model.Item4.Where(ea => ea.Students.Contains(student))
.Select(ea => ea.EventDefinition)
.Distinct()
@* .OrderBy(e =>
{
var r = student.RankedEventPicks.IndexOf(e);
r = r >= 0 ? r : 10;
//r = r * (4 - e.LevelOfEffort.Value);
return r;
}) *@
;
<tr>
<td><strong>@student.FirstName</strong></td>
<td>@assignments.Sum(a => a.LevelOfEffort)</td>
@foreach (var evt in assignments)
{
@* var h = student.RankedEventPicks.IndexOf(evt) + 1; *@
@* @{ class="GetOrderClass(h);" } *@
<td class="">
@evt.ShortName @Html.Partial("EffortStarsPartial", evt.LevelOfEffort)
@{
if (evt.EventFormat == EventFormat.Individual)
{
<sup class="activity">(ind)</sup>
}
if (evt.RegionalEvent)
{
<sup class="activity">(reg)</sup>
}
if (evt.OnSiteActivity)
{
<sup class="activity">(act)</sup>
}
}
</td>
}
</tr>
}
</table>
<table><tr><td>&nbsp;</td></tr></table>
<table class="table-primary">
<thead>
<tr><td>Teams</td></tr>
</thead>
@foreach (var evt in Model.Item1.OrderByDescending(e => e.EventFormat is EventFormat.Team))
{
var assignments = Model.Item4.FirstOrDefault(i => i.EventDefinition == evt);
@if (assignments == null)
{
continue;
}
<tr class="table-primary">
<td class="table-primary"><strong>@evt.Name</strong></td>
<td colspan="1">
@Html.Partial("EffortStarsPartial", evt.LevelOfEffort)
@if (evt.EventFormat is EventFormat.Individual)
{
<text>(ind)</text>
}
@if (evt.RegionalEvent)
{
<text>(reg)</text>
}
@if (evt.OnSiteActivity)
{
<text>(act)</text>
}@evt.TeamSize</td>
@*<td style="nowrap">@evt.MaxTeamCountState</td>*@
@if (assignments != null)
{
foreach (var student in assignments.Students)
{
var h = student.RankedEventPicks.IndexOf(evt) + 1;
<td class="@{ GetOrderClass(h); }">@student.FirstName</td>
}
}
else
{
<td></td>
}
</tr>
}
<tr>
@{
var unassigned = string.Join(", ", unassignedEvents.Select(e => e.Name));
}
<td colspan="8">Unassigned EventDefinitions: @unassigned</td>
</tr>
</table>
<table><tr><td></td></tr></table>
<table>
<thead>
<th>Name</th>
<th>Grade</th>
<th>TSA Year</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
</thead>
@foreach (var s in Model.Item2.OrderBy(n => n.FirstName))
{
<tr>
<td>@s.FirstName</td>
<td>@s.Grade.Ordinal()</td>
<td>@s.TsaYear.Ordinal()</td>
@for (var i = 0; i < 7; i++)
{
var h = i + 1;
var evt = s.RankedEventPicks.Skip(i).FirstOrDefault();
if (evt == null)
continue;
<td class="@{ GetOrderClass(h); }">
@evt.ShortName @Html.Partial("EffortStarsPartial", evt.LevelOfEffort)
@if (evt.EventFormat == EventFormat.Individual)
{
<sup class="activity">(ind)</sup>
}
@if (evt.RegionalEvent)
{
<sup class="activity">(reg)</sup>
}
@if (evt.OnSiteActivity)
{
<sup class="activity">(act)</sup>
}
</td>
}
</tr>
}
</table>
<table>
<thead>
<tr><td>EventDefinition</td><td>Level of Effort</td><td>Individual</td><td>Regional</td><td>On-site Activity</td><td>Team Size</td><td>Max Team Count</td></tr>
</thead>
@foreach (var evt in Model.Item1)
{
var esp = Model.Item3.FirstOrDefault(i => i.EventDefinition == evt);
<tr>
<td>
@evt.Name
</td>
<td>@Html.Partial("EffortStarsPartial", evt.LevelOfEffort)</td>
<td>@if (evt.EventFormat is EventFormat.Individual) { <text>ind</text> }</td>
<td>@if (evt.RegionalEvent) { <text>reg</text> }</td>
<td>@if (evt.OnSiteActivity) { <text>act</text> }</td>
<td style="nowrap">@evt.TeamSize</td>
<td style="nowrap">@evt.MaxTeamCountState</td>
@for (var i = 0; i < maxStudentPicks; i++)
{
var d = esp?.StudentRankings.Skip(i).FirstOrDefault();
<td class="@{GetOrderClass(d?.Item2 ?? int.MaxValue);}">@d?.Item1.FirstName</td>
}
</tr>
}
</table>
@functions
{
public void GetOrderClass(int pick)
{
@Html.Raw(LabelHelper.GetOrderClass(pick))
}
}
-180
View File
@@ -1,180 +0,0 @@
@using Core.Entities
@model Tuple<Student[]>
@{
ViewData["Title"] = "Student Teams";
}
<table class="table-primary">
<thead>
<tr>
<td>Student</td><td>Level of Effort Total</td>
</tr>
</thead>
@foreach (var student in Model.Item1.OrderBy(s => s.FirstName))
{
var assignments
= student.Teams.Where(ea => ea.Students.Contains(student))
.Select(ea => ea.EventDefinition)
.Distinct()
@* .OrderBy(e =>
{
var r = student.RankedEventPicks.IndexOf(e);
r = r >= 0 ? r : 10;
//r = r * (4 - e.LevelOfEffort.Value);
return r;
}) *@
.ToList();
<tr>
<td>
<strong>@student.FirstNameLastName</strong> @if(!string.IsNullOrEmpty(student.Officer )) {<text>(@student.Officer)</text>}
</td>
<td>@assignments.Sum(a => a.LevelOfEffort)</td>
@foreach (var evt in assignments)
{
@* var h = student.RankedEventPicks.IndexOf(evt) + 1; *@
@* @{ GetOrderClass(h); } *@
<td class="">
@evt.ShortName @Html.Partial("EffortStarsPartial", evt.LevelOfEffort)
@{
if (evt.EventFormat == EventFormat.Individual)
{
<sup class="activity">(ind)</sup>
}
if (evt.RegionalEvent)
{
<sup class="activity">(reg)</sup>
}
if (evt.OnSiteActivity)
{
<sup class="activity">(act)</sup>
}
}
</td>
}
</tr>
}
</table>
@*
<table class="table">
<tr>
<th>Student</th>
<th>Team</th>
<th>Teammates</th>
</tr>
@foreach (var student in Model.Item1.RankBy(s => s.Name))
{
var teams = student.Teams;
<tr class="table-primary">
<td class="table-primary">
<strong>@student.FirstNameLastName</strong>
@if (!string.IsNullOrEmpty(student.Officer))
{
<text>(</text> @student.Officer <text>)</text>
}
</td>
</tr>
@foreach (var team in student.Teams)
{
<tr>
<td></td>
<td class="@{ GetTeamClass(team); }">@team.Name
@{
var ind = new List<string>();
if (team.Captain == student)
{
<span>(Cpt .)</span>
}
if (!team.EventDefinition.InterviewOrPresentation)
{
ind.Add("a");
}
if (team.EventDefinition.EventDefinitionFormat is EventDefinitionEventDefinitionFormat.Individual)
{
ind.Add("i");
}
if (ind.Count > 0)
{
<span class="activity">(@string.Join(",", ind))</span>
}
}
</td>
<td>
@string.Join(", ", team.Students.Where(s => s != student).RankByDescending(s => s.TsaYear + s.Grade).Select(s => s.FirstName))
</td>
</tr>
}
}
</table>
<p>
(a) denotes an eventDefinition that has activity other than interview or presentation at state
<br />
(i) denotes an individual eventDefinition
</p> *@
<table class="table-primary">
<thead>
<tr>
<td>Student</td>
</tr>
</thead>
@foreach (var student in Model.Item1.OrderBy(s => s.LastNameFirstName))
{
var assignments
= student.Teams.Where(ea => ea.Students.Contains(student))
.Select(ea => ea.EventDefinition)
.Distinct()
@* .OrderBy(e =>
{
var r = student.RankedEventPicks.IndexOf(e);
r = r >= 0 ? r : 10;
//r = r * (4 - e.LevelOfEffort.Value);
return r;
}) *@
.ToList();
<tr>
<td>
<strong>@student.FirstName</strong>
</td>
@foreach (var evt in assignments)
{
<td>
@evt.Name
@{
if (evt.EventFormat == EventFormat.Individual)
{
<sup class="activity">(individual)</sup>
}
if (evt.RegionalEvent)
{
<sup class="activity">(regional)</sup>
}
}
</td>
}
</tr>
}
</table>
@functions
{
public void GetOrderClass(int pick)
{
@Html.Raw(LabelHelper.GetOrderClass(pick))
}
private void GetTeamClass(Team team)
{
// if (team.EventDefinition.RegionalEvent)
// {
// @Html.Raw("regional");
// }
}
}
-46
View File
@@ -1,46 +0,0 @@
@model System.Tuple<Core.Entities.Team[],Core.Entities.Student[]>
@{
ViewData["Title"] = "Team Grid";
}
<table class="table">
<thead>
<tr> <th></th>
@foreach (var team in Model.Item1)
{
if (team.Name == team.EventDefinition.Name)
{
<td style="writing-mode: vertical-rl;text-orientation:sideways">
@team.EventDefinition.ShortName
</td>
}
else
{
<td style="writing-mode: vertical-rl;text-orientation:sideways">
@team.Name
</td>
}
}
</tr>
</thead>
<tbody>
@foreach (var student in Model.Item2.OrderBy(s => s.FirstName))
{
<tr>
<td>@student.FirstName</td>
@foreach (var team in Model.Item1)
{
@if (team.Students.Contains(student))
{
<td><strong> X</strong></td>
}
else
{
<td></td>
}
}
</tr>
}
</tbody>
</table>
-132
View File
@@ -1,132 +0,0 @@
@using Core.Entities
@model Tuple<Team[]>
@{
ViewData["Title"] = "Teams";
}
<table>
@foreach (var team in Model.Item1.Where(t => t.EventDefinition.Documentation.Contains("Port")))
{
<tr>
<td>@team.Name @if (team.EventDefinition.EventFormat is EventFormat.Individual)
{
<text>(ind)</text>
}
</td><td>@team.EventDefinition.Documentation</td>
@foreach(var student in @team.Students.OrderByDescending(s => s.TsaYear + s.Grade))
{
<td>@student.FirstName</td>
}
</tr>
}
@foreach (var student in Model.Item1.SelectMany(t => t.Students).Distinct().Where(s => s.Teams.Any(t => t.EventDefinition.Name.Contains("Port"))))
{
<tr>
<td>@student.FirstName</td>
</tr>
}
</table>
<table class="table-primary">
<thead>
<tr><td>Teams</td></tr>
</thead>
@{
var ind = false;
}
@foreach (var team in Model.Item1.OrderByDescending(t => t.EventDefinition.EventFormat is EventFormat.Team))
{
@if (!ind && team.EventDefinition.EventFormat is EventFormat.Individual)
{
<tr>
<td><hr/></td><td>Individual</td><td><hr/></td>
</tr>
ind = true;
}
<tr class="table-primary">
<td class="table-primary"><strong>@team.Name</strong></td>
<td colspan="1">
@* @Html.Partial("EffortStarsPartial", team.EventDefinition.LevelOfEffort) *@
@* @if (team.EventDefinition.RegionalEventDefinition)
{
<text>(reg)</text>
}
@if (team.EventDefinition.OnSiteActivity)
{
<text>(act)</text>
} *@
@team.EventDefinition.TeamSize
</td>
@*<td style="nowrap">@evt.MaxTeamCountState</td>*@
@foreach (var student in team.Students.OrderByDescending(s => (s.Grade + s.TsaYear) * (team.Captain == s ? 2 : 1)).ThenBy(s => s.FirstNameLastName))
{
<td>@student.FirstName @if (team.Captain == student) { <text>(Cpt.)</text>}</td>
}
</tr>
}
</table>
@*
<table class="table">
<thead>
<tr>
<td>EventDefinition</td>
<td>Pre-submission</td>
<td>Notes</td>
<td>Team Members</td>
</tr>
</thead>
@foreach (var team in Model.Item1)
{
var students = team.Students;
<tr>
<td>
@team.Name
</td>
</tr>
@foreach (var student in students.RankByDescending(s => (s.Grade + s.TsaYear) * (team.Captain == s ? 2 : 1)).ThenBy(s => s.FirstNameLastName))
{
<td>@student.FirstNameLastName @if (team.Captain == student) { <text>(Cpt.)</text>}</td>
}
<tr>
<td class="eventDefinition-desc" colspan="4">Team Size: @team.EventDefinition.TeamSize, Max Teams: @team.EventDefinition.MaxTeamCountState
@{
if (!team.EventDefinition.InterviewOrPresentation)
{
<span class="activity"> (a)</span>
}
if (team.EventDefinition.EventDefinitionFormat is EventDefinitionEventDefinitionFormat.Individual)
{
<span class="activity"> (i)</span>
}
}
</td>
</tr>
}
</table>
<p>
(a) denotes an eventDefinition that has activity other than interview or presentation at state
<br />
(i) denotes an individual eventDefinition
</p>
*@
@functions
{
public void GetOrderClass(int pick)
{
@Html.Raw(LabelHelper.GetOrderClass(pick))
}
private void GetTeamClass(Team team)
{
// if (team.EventDefinition.RegionalEvent)
// {
// @Html.Raw("regional");
// }
}
}
-25
View File
@@ -1,25 +0,0 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
-84
View File
@@ -1,84 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Web</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/Web.styles.css" asp-append-version="true" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Web</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Events">EventDefinitions</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="StudentEvents">Student EventDefinition Picks</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="StudentEventHandout">Student EventDefinition Handout</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Teams">Teams</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="TeamGrid">Team Grid</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Students">Students</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Schedule">Schedule</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Regionals">Regionals</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="State">State</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Nationals">Nationals</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
@@ -1,48 +0,0 @@
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
a {
color: #0077cc;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
}
@@ -1,2 +0,0 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
-3
View File
@@ -1,3 +0,0 @@
@using Web
@using Web.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
-3
View File
@@ -1,3 +0,0 @@
@{
Layout = "_Layout";
}
-23
View File
@@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="bootstrap" Version="5.3.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Remove="Views\Home\Teams.cshtml" />
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="Views\Home\TeamGrid.cshtml" />
<_ContentIncludedByDefault Remove="Views\Home\Teams.cshtml" />
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Remove="Views\Home\TeamGrid.cshtml" />
</ItemGroup>
</Project>
@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
-9
View File
@@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
-61
View File
@@ -1,61 +0,0 @@
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
@media print {
body .container {
max-width:1200px;
}
main {
font-size: 11px;
margin: 30pt;
color: #000;
background-color: #fff;
}
body header {
display: none;
}
.nobrk {
break-inside: avoid;
}
}
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px;
}
main {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif
}
table {
}
.bluewhite, table.schedule {
background-color: #4285F4;
color: white !IMPORTANT;
}
.first-pick { background-color: #dd7e6b; }
.second-pick { background-color: #ea9999; }
.third-pick { background-color: #f9cb9c; }
.fourth-pick { background-color: #ffe599; }
.fifth-pick { background-color: #fff2cc; }
.sixth-pick { background-color: #fffaea; }
.seventh-pick { background-color: #fffff0; }
.non-pick { background-color: #f0f2f4; }
.event-desc { padding-left: 40px; font-size:small; }
.activity { font:xx-small; color:grey;}
.regional { color: #f1c232; }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

-4
View File
@@ -1,4 +0,0 @@
// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.
@@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2011-2021 Twitter, Inc.
Copyright (c) 2011-2021 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,427 +0,0 @@
/*!
* Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: #0d6efd;
text-decoration: underline;
}
a:hover {
color: #0a58ca;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
direction: ltr /* rtl:ignore */;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */
File diff suppressed because one or more lines are too long
@@ -1,8 +0,0 @@
/*!
* Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
File diff suppressed because one or more lines are too long
@@ -1,424 +0,0 @@
/*!
* Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: #0d6efd;
text-decoration: underline;
}
a:hover {
color: #0a58ca;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
direction: ltr ;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
File diff suppressed because one or more lines are too long
@@ -1,8 +0,0 @@
/*!
* Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-right:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-right:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:right}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:right;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:right}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=email],[type=number],[type=tel],[type=url]{direction:ltr}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.rtl.min.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More