diff --git a/Core/Calculation/EventAssignment.cs b/Core/Calculation/EventAssignment.cs index 5f42fd8..3158ad9 100644 --- a/Core/Calculation/EventAssignment.cs +++ b/Core/Calculation/EventAssignment.cs @@ -254,7 +254,7 @@ namespace Core.Calculation private void AddStudentConstraint(CpModel model, BoolVar[,] x, Func eventFilter, Action> constraintAction) { - var buffer = new List(); + List buffer = []; foreach (var s in _allStudents) { buffer.Clear(); @@ -273,7 +273,7 @@ namespace Core.Calculation /// private void IndividualEventsMustBeRanked(CpModel model, BoolVar[,] x) { - var prohibitVar = new List(1); + List prohibitVar = []; foreach (var s in _allStudents) { @@ -328,7 +328,7 @@ namespace Core.Calculation /// private void LimitStudentAssignment(CpModel model, BoolVar[,] x) { - var studentCapacity = new List(); + List studentCapacity = []; foreach (var s in _allStudents) { @@ -418,7 +418,7 @@ namespace Core.Calculation /// private void AddEventConstraint(CpModel model, BoolVar[,] x, int eventIndex, int lb, int ub) { - var eventCapacity = new List(); + List eventCapacity = []; foreach (var s in _allStudents) { eventCapacity.Add(x[eventIndex, s]); @@ -455,7 +455,7 @@ namespace Core.Calculation model.AddAssumption(x[e, s]); } - var prohibitVar = new List(1); + List prohibitVar = []; foreach (var excludedAssignment in _assignmentRequirements.Where(a => a.Requirement == Requirement.Exclude)) { var e = _events.IndexOf(excludedAssignment.EventDefinition); diff --git a/Core/Calculation/TeamScheduler_DecisionTree.cs b/Core/Calculation/TeamScheduler_DecisionTree.cs index d3d0bf5..df9bcc4 100644 --- a/Core/Calculation/TeamScheduler_DecisionTree.cs +++ b/Core/Calculation/TeamScheduler_DecisionTree.cs @@ -18,7 +18,7 @@ public class TeamScheduler_DecisionTree { var timeSlots = new IList[_timeSlotCount]; for (var i = 0; i < _timeSlotCount; i++) - timeSlots[i] = new List(); + timeSlots[i] = []; foreach (var team in _teams.OrderByDescending(t => t.Students.Count)) { diff --git a/Core/Entities/CareerField.cs b/Core/Entities/CareerField.cs index cab0670..ceb118e 100644 --- a/Core/Entities/CareerField.cs +++ b/Core/Entities/CareerField.cs @@ -43,13 +43,10 @@ public class CareerField Id = id; Name = name; Description = description; - DirectCareerMatches = directCareerMatches ?? Array.Empty(); - PatternKeywords = patternKeywords ?? Array.Empty(); + DirectCareerMatches = directCareerMatches ?? []; + PatternKeywords = patternKeywords ?? []; } - public override string ToString() - { - return Name; - } + public override string ToString() => Name; } diff --git a/Core/Entities/EventDefinition.cs b/Core/Entities/EventDefinition.cs index 602f623..e3a2024 100644 --- a/Core/Entities/EventDefinition.cs +++ b/Core/Entities/EventDefinition.cs @@ -37,21 +37,6 @@ public class EventDefinition => SemifinalistActivity != null && (SemifinalistActivity.Contains("Interview") || SemifinalistActivity.Contains("Presentation")); public bool OnSiteActivity { get; set; } - //=> SemifinalistActivity != null - // && (SemifinalistActivity.Contains("Challenge") - // || SemifinalistActivity.Contains("Race") - // || SemifinalistActivity.Contains("Speech") - // || SemifinalistActivity.Contains("Test") - // || SemifinalistActivity.Contains("Flight") - // || Name.Contains("Leadership") - // || Name.Contains("Forensic") - // || Name.Contains("Flight") - // || Name.Contains("Coding") - // || SemifinalistActivity.Contains("Debate") - // || SemifinalistActivity.Contains("Photography") - // || SemifinalistActivity.Contains("Build") - // || Name.Contains("Chapter") - // || Name.Contains("Podcast")); [StringLength(1024)] public string? Notes { get; set; } @@ -79,15 +64,12 @@ public class EventDefinition public string? Description { get; set; } public int? LevelOfEffort { get; set; } - public ICollection RelatedCareers { get; set; } = new List(); + public ICollection RelatedCareers { get; set; } = []; [System.ComponentModel.DataAnnotations.Schema.NotMapped] public string? RelatedCareersText { get; set; } - public override string ToString() - { - return Name; - } + public override string ToString() => Name; public static readonly EventDefinition GeneralSchedule = new(){Name = "General Schedule"}; public static readonly EventDefinition MeetTheCandidates = new(){Name = "Meet the Candidates"}; diff --git a/Core/Parsers/EventOccurrenceGrammar.cs b/Core/Parsers/EventOccurrenceGrammar.cs index 81ef9c0..a4461a7 100644 --- a/Core/Parsers/EventOccurrenceGrammar.cs +++ b/Core/Parsers/EventOccurrenceGrammar.cs @@ -14,11 +14,11 @@ public static class EventOccurrenceGrammar /// Array of all month names in order (January through December). /// This is the single source of truth for month names used throughout the parser. /// - public static readonly string[] MonthNames = new[] - { + public static readonly string[] MonthNames = + [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" - }; + ]; // Build month parsers dynamically from MonthNames array private static readonly Parser[] MonthParsers = MonthNames diff --git a/Core/Parsers/StudentEventRankingParser.cs b/Core/Parsers/StudentEventRankingParser.cs index cdd6192..d4f4aae 100644 --- a/Core/Parsers/StudentEventRankingParser.cs +++ b/Core/Parsers/StudentEventRankingParser.cs @@ -27,7 +27,7 @@ public class StudentEventRankingParser : CsvParserBase continue; - var competitiveEvents = new List(6); + var competitiveEvents = new List(); for (var i = 1; i <= 6; i++) { diff --git a/Core/Parsers/StudentParser.cs b/Core/Parsers/StudentParser.cs index d890d39..e197dd6 100644 --- a/Core/Parsers/StudentParser.cs +++ b/Core/Parsers/StudentParser.cs @@ -13,7 +13,7 @@ public class StudentParser : CsvParserBase public Student[] Parse() { - var s = new List(); + var students = new List(); CsvReader.Read(); CsvReader.ReadHeader(); @@ -44,9 +44,9 @@ public class StudentParser : CsvParserBase RegionalId = regionalId, NationalId = nationalId }; - s.Add(student); + students.Add(student); } - return s.ToArray(); + return students.ToArray(); } } \ No newline at end of file diff --git a/Core/Utility/CareerFieldDefinitions.cs b/Core/Utility/CareerFieldDefinitions.cs index 190506c..ff3cbfa 100644 --- a/Core/Utility/CareerFieldDefinitions.cs +++ b/Core/Utility/CareerFieldDefinitions.cs @@ -23,7 +23,7 @@ public static class CareerFieldDefinitions public static IReadOnlyList GetRelatedCareerFields(IEnumerable careers) { if (careers == null) - return Array.Empty(); + return []; var careerNames = careers .Where(c => !string.IsNullOrWhiteSpace(c.Name)) @@ -31,7 +31,7 @@ public static class CareerFieldDefinitions .ToHashSet(StringComparer.OrdinalIgnoreCase); if (!careerNames.Any()) - return Array.Empty(); + return []; var matchingFields = new HashSet(); var allFields = GetAllCareerFields(); @@ -74,15 +74,15 @@ public static class CareerFieldDefinitions private static IReadOnlyList CreateAllCareerFields() { - return new List - { + return + [ // 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" } + [ "Aeronautical engineer", "Aircraft systems engineer", "Automobile designer", "Automotive designer", "Automotive modeler", "Race car engineer" ], + [ "aeronautical", "aircraft", "automobile", "automotive", "race car" ] ), // 2. Mechanical & Robotics Engineering @@ -90,8 +90,8 @@ public static class CareerFieldDefinitions 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" } + [ "Machine designer", "Mechanical drafter", "Mechanical engineer", "Robotics engineer" ], + [ "mechanical", "robotics", "machine" ] ), // 3. Electrical & Electronics Engineering @@ -99,8 +99,8 @@ public static class CareerFieldDefinitions 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" } + [ "Electrical engineer", "Electrical technician", "Electrician", "Electromechanical engineer", "Electronic analyst", "Electronic designer" ], + [ "electrical", "electronic", "electrician" ] ), // 4. Civil & Structural Engineering @@ -108,8 +108,8 @@ public static class CareerFieldDefinitions 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" } + [ "Civil engineer", "Construction analyst", "Construction manager", "General contractor", "Structural engineer", "Structural iron and steel work technician" ], + [ "civil", "construction", "structural", "contractor" ] ), // 5. Environmental & Energy Engineering @@ -117,8 +117,8 @@ public static class CareerFieldDefinitions 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" } + [ "Chemical engineer", "Energy efficiency technician", "Environmental engineer", "Solar engineer", "Solar panel installer", "Solar sales consultant" ], + [ "chemical", "energy", "environmental", "solar" ] ), // 6. General Engineering & Quality @@ -126,8 +126,8 @@ public static class CareerFieldDefinitions 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" } + [ "Engineer", "Engineering manager", "Engineering technician", "Quality assurance engineer", "Quality engineer", "Standards engineer" ], + [ "engineer", "quality", "standards" ] ), // 7. Architecture & Urban Planning @@ -135,8 +135,8 @@ public static class CareerFieldDefinitions 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" } + [ "Architect", "Community planner", "Interior designer", "Urban and regional planner" ], + [ "architect", "planner", "interior design", "urban" ] ), // 8. Software Development @@ -144,8 +144,8 @@ public static class CareerFieldDefinitions 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" } + [ "Computer programmer", "Computer software engineer", "Programming & software development", "Software designer", "Software engineer" ], + [ "programming", "programmer", "software", "developer" ] ), // 9. IT & Networking @@ -153,8 +153,8 @@ public static class CareerFieldDefinitions 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" } + [ "Computer engineer", "Computer network specialist", "Computer technician", "Information support & services", "Network systems", "Technical support specialist", "Telecommunications manager" ], + [ "network", "computer", "technical support", "telecommunications", "IT" ] ), // 10. Cybersecurity & Digital Forensics @@ -162,8 +162,8 @@ public static class CareerFieldDefinitions 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" } + [ "Cryptographer", "Cyber Crime Investigator", "Cyber defense incident responder", "Cyber forensics expert", "Cyber legal advisor", "Cyber operator", "Cybersecurity engineer", "Vulnerability assessor" ], + [ "cyber", "security", "forensics", "cryptography", "vulnerability" ] ), // 11. Data Science & Analytics @@ -171,8 +171,8 @@ public static class CareerFieldDefinitions 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" } + [ "Actuary", "Data analyst", "Data scientist", "Economist", "Mathematician", "Operations research analyst" ], + [ "data", "analyst", "actuary", "economist", "mathematician", "research" ] ), // 12. CAD, CNC & Manufacturing @@ -180,8 +180,8 @@ public static class CareerFieldDefinitions 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" } + [ "CAD professional", "CNC programmer", "Manufacturing", "Production planner" ], + [ "CAD", "CNC", "manufacturing", "production" ] ), // 13. Industrial & Product Design @@ -189,8 +189,8 @@ public static class CareerFieldDefinitions 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" } + [ "Appraiser", "Commercial and industrial design", "Designer", "Industrial designer", "Product designer" ], + [ "designer", "design", "industrial", "product", "appraiser" ] ), // 14. Visual Arts & Animation @@ -198,8 +198,8 @@ public static class CareerFieldDefinitions 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" } + [ "Animator", "Artist", "Computer animator", "Graphic artist", "Illustrator", "Multimedia designer" ], + [ "animator", "artist", "graphic", "illustrator", "multimedia" ] ), // 15. Game Design & Interactive Media @@ -207,8 +207,8 @@ public static class CareerFieldDefinitions 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" } + [ "Game designer", "Game Play Tester", "Professional Gamer" ], + [ "game", "gamer", "gaming" ] ), // 16. Audio & Music Production @@ -216,8 +216,8 @@ public static class CareerFieldDefinitions 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" } + [ "Audio designer or engineer", "Audio Engineer", "Audio operator or technician", "Broadcast technician", "Music composer" ], + [ "audio", "music", "broadcast", "sound" ] ), // 17. Video & Film Production @@ -225,8 +225,8 @@ public static class CareerFieldDefinitions 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" } + [ "Audiovisual technician", "Director", "Entertainment/television broadcaster", "Videographer" ], + [ "video", "film", "director", "television", "broadcast", "videographer" ] ), // 18. Web & Digital Communications @@ -234,8 +234,8 @@ public static class CareerFieldDefinitions 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" } + [ "Instructional technologist", "Web & digital communications", "Webmaster", "Website designer" ], + [ "web", "website", "digital", "communications", "webmaster" ] ), // 19. Writing & Publishing @@ -243,8 +243,8 @@ public static class CareerFieldDefinitions 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" } + [ "Ad copy writer", "Editor", "Publisher", "Screenplay writer", "Speech writer", "Technical writer", "Writer" ], + [ "writing", "writer", "editor", "publisher", "copy" ] ), // 20. Journalism & Public Relations @@ -252,8 +252,8 @@ public static class CareerFieldDefinitions 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" } + [ "Internal communications manager", "Motivational speaker", "Photojournalist", "Reporter" ], + [ "journalism", "reporter", "photojournalist", "communications", "speaker" ] ), // 21. Forensics & Criminal Investigation @@ -261,8 +261,8 @@ public static class CareerFieldDefinitions 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" } + [ "Crime scene investigator", "Detective", "Forensic accountant", "Forensic anthropologist", "Forensic engineering scientist", "Forensic pathologist" ], + [ "forensic", "detective", "investigator", "crime" ] ), // 22. Healthcare & Medical Technology @@ -270,8 +270,8 @@ public static class CareerFieldDefinitions 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" } + [ "Dietitian", "Doctor", "Epidemiologist", "Medical technologist", "Nurse", "Pharmacist", "Prosthetics practitioner" ], + [ "medical", "health", "doctor", "nurse", "pharmacist", "dietitian", "epidemiology" ] ), // 23. Science & Research @@ -279,8 +279,8 @@ public static class CareerFieldDefinitions 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" } + [ "Botanist", "Food scientist", "Meteorologist", "Molecular biologist", "Physics instructor", "Plant geneticist", "Research and development scientist", "Research assistant", "Researcher" ], + [ "scientist", "research", "biology", "physics", "botanist", "meteorologist", "geneticist" ] ), // 24. Education & Training @@ -288,8 +288,8 @@ public static class CareerFieldDefinitions 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" } + [ "Educator", "Teacher/trainer", "Technology education instructor" ], + [ "educator", "teacher", "trainer", "education", "instructor" ] ), // 25. Business, Legal & Government @@ -297,10 +297,10 @@ public static class CareerFieldDefinitions 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" } + [ "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" ], + [ "business", "legal", "lawyer", "government", "politician", "manager", "marketing", "consultant", "entrepreneur" ] ) - }; + ]; } } diff --git a/WebApp/Components/Features/Calendar/Index.razor b/WebApp/Components/Features/Calendar/Index.razor index 3d593ba..81d2297 100644 --- a/WebApp/Components/Features/Calendar/Index.razor +++ b/WebApp/Components/Features/Calendar/Index.razor @@ -99,7 +99,7 @@ // Load teams for all event definitions var teamsByEventId = await EventOccurrenceService.GetTeamsByEventDefinitionIdsAsync(eventDefinitionIds); - var items = new List(); + List items = []; foreach (var occ in eventOccurrences) { try @@ -112,7 +112,7 @@ // Get student first names for this event definition var studentFirstNames = occ.EventDefinition != null ? StudentFirstNames(occ.EventDefinition, teamsByEventId) - : new List(); + : []; var calendarItem = new CalendarEventItem(occ, studentFirstNames); items.Add(calendarItem); @@ -135,7 +135,7 @@ catch (Exception ex) { Logger.LogError(ex, "Error loading calendar events"); - _calendarItems = new List(); + _calendarItems = []; } finally { @@ -145,7 +145,7 @@ private static List StudentFirstNames(EventDefinition ed, Dictionary> teamsByEventId) { - var studentFirstNames = new List(); + List studentFirstNames = []; if (ed?.Id == null || !teamsByEventId.TryGetValue(ed.Id, out var teams)) return studentFirstNames; // Get all unique student first names from all teams for this event @@ -220,7 +220,7 @@ private string GetEventTooltip(CalendarEventItem item) { - var parts = new List(); + List parts = []; if (!string.IsNullOrEmpty(item.EventDefinition?.Name)) { diff --git a/WebApp/Components/Features/Events/CareerMapping.razor b/WebApp/Components/Features/Events/CareerMapping.razor index 4bd4e7b..ca47cf3 100644 --- a/WebApp/Components/Features/Events/CareerMapping.razor +++ b/WebApp/Components/Features/Events/CareerMapping.razor @@ -118,7 +118,7 @@ else { if (!_fieldIdToCareers.ContainsKey(field.Id)) { - _fieldIdToCareers[field.Id] = new List(); + _fieldIdToCareers[field.Id] = []; } // Add unique career names for this field foreach (var career in evt.RelatedCareers) @@ -153,8 +153,8 @@ else private NetworkData GenerateNetworkData(List events) { - var nodes = new List(); - var edges = new List(); + List nodes = []; + List edges = []; // Dictionary to track node IDs (to avoid duplicates) var eventNodeIds = new Dictionary(); @@ -284,7 +284,7 @@ else Title = field.Name, Description = field.Description, IsCareerField = true, - Careers = careers?.ToList() ?? new List() + Careers = careers?.ToList() ?? [] }; } } diff --git a/WebApp/Components/Features/Events/Index.razor b/WebApp/Components/Features/Events/Index.razor index d37b407..34fc0b5 100644 --- a/WebApp/Components/Features/Events/Index.razor +++ b/WebApp/Components/Features/Events/Index.razor @@ -1,5 +1,6 @@ @page "/events" @attribute [Authorize] +@implements IAsyncDisposable @using Microsoft.EntityFrameworkCore @using WebApp.Models @using WebApp.Components.Shared.Components @@ -74,16 +75,32 @@ @code { MudDataGrid _dataGrid = null!; private bool _isLoading = true; + private CancellationTokenSource? _cancellationTokenSource; + private bool _isDisposed = false; + + protected override void OnInitialized() + { + _cancellationTokenSource = new CancellationTokenSource(); + } private async Task> ServerReload(GridState state) { + if (_isDisposed) + { + return new GridData { TotalItems = 0, Items = [] }; + } + _isLoading = true; try { - var query = Context.Events.OrderBy(e => e.Name).Where(state.FilterDefinitions).OrderBy(state.SortDefinitions); + var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None; - var totalItems = await query.CountAsync(); - var pagedData = await query.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArrayAsync(); + var query = Context.Events + .AsNoTracking() + .OrderBy(e => e.Name).Where(state.FilterDefinitions).OrderBy(state.SortDefinitions); + + var totalItems = await query.CountAsync(cancellationToken); + var pagedData = await query.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArrayAsync(cancellationToken); return new GridData { @@ -91,31 +108,82 @@ Items = pagedData }; } + catch (TaskCanceledException) + { + return new GridData { TotalItems = 0, Items = [] }; + } + catch (JSDisconnectedException) + { + return new GridData { TotalItems = 0, Items = [] }; + } finally { - _isLoading = false; + if (!_isDisposed) + { + _isLoading = false; + } } } private async Task DeleteEventDefinition(EventDefinition evt) { - //_isRowBlocked = true; + if (_isDisposed) return; - var result = await DialogService - .ShowMessageBox("Delete Event", - (MarkupString)$"Are you sure want to delete {evt.Name}? This cannot be undone.", - yesText:"Yes", - noText:"Cancel"); - - if (result == true) + try { - Context.Events.Remove(evt!); - await Context.SaveChangesAsync(); - Snackbar.Add($"Delete event: Delete of Event {evt.Name}", Severity.Info); - } + var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None; - //_isRowBlocked = false; - StateHasChanged(); - await _dataGrid.ReloadServerData(); + var result = await DialogService + .ShowMessageBox("Delete Event", + (MarkupString)$"Are you sure want to delete {evt.Name}? This cannot be undone.", + yesText:"Yes", + noText:"Cancel"); + + if (_isDisposed) return; + + if (result == true) + { + Context.Events.Remove(evt!); + await Context.SaveChangesAsync(cancellationToken); + + if (!_isDisposed) + { + Snackbar.Add($"Event {evt.Name} deleted", Severity.Info); + } + } + + if (!_isDisposed) + { + StateHasChanged(); + await _dataGrid.ReloadServerData(); + } + } + catch (TaskCanceledException) + { + // Component was disposed, ignore + } + catch (JSDisconnectedException) + { + // JS connection lost, ignore + } + catch (Exception ex) + { + if (!_isDisposed) + { + Snackbar.Add($"Error deleting event: {ex.Message}", Severity.Error); + } + } + } + + public async ValueTask DisposeAsync() + { + if (!_isDisposed) + { + _isDisposed = true; + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + } + await ValueTask.CompletedTask; } } diff --git a/WebApp/Components/Features/MeetingSchedule/Index.razor b/WebApp/Components/Features/MeetingSchedule/Index.razor index 68c90f7..4e97f07 100644 --- a/WebApp/Components/Features/MeetingSchedule/Index.razor +++ b/WebApp/Components/Features/MeetingSchedule/Index.razor @@ -178,7 +178,7 @@ _scheduledTeams = _scheduledTeams.Where(t => t != unassignedTeam); else { - _scheduledTeams = _scheduledTeams.Concat(new[] { unassignedTeam }); + _scheduledTeams = _scheduledTeams.Concat([unassignedTeam]); } await SaveScheduledTeams(); } @@ -216,6 +216,7 @@ _teams = await Context.Teams + .AsNoTracking() .Include(e => e.Event) .Include(e => e.Students) .OrderBy(e => e.Event.Name) @@ -224,6 +225,7 @@ _students = await Context.Students + .AsNoTracking() .Include(e => e.Teams) .ThenInclude(e => e.Captain) .Include(e => e.EventRankings) @@ -311,13 +313,13 @@ // Try recommendation strategies in priority order var scheduler = new UnassignedStudentScheduler(_teams, _solution.TimeSlots); - var strategies = new[] - { + UnassignedScheduleStrategy[] strategies = + [ UnassignedScheduleStrategy.LevelOfEffort, UnassignedScheduleStrategy.BiggestGroup, UnassignedScheduleStrategy.AnyNotMeetingAlready, UnassignedScheduleStrategy.IndividualEvents - }; + ]; _possibleAdditions = strategies .Select(strategy => scheduler.ScheduleStrategy(strategy)) diff --git a/WebApp/Components/Features/Students/EventRanking.razor b/WebApp/Components/Features/Students/EventRanking.razor index 22f7b31..c4c0db4 100644 --- a/WebApp/Components/Features/Students/EventRanking.razor +++ b/WebApp/Components/Features/Students/EventRanking.razor @@ -140,11 +140,13 @@ else .OrderBy(e => e.Event.Name) .ToArray(); - var events = await Context.Events.ToArrayAsync(); + var events = await Context.Events + .AsNoTracking() + .ToArrayAsync(); var remainingEvents = events .Where(e => _eventStudentRankings.All(est => est.Event.Id != e.Id)) - .Select(e => new EventStudentRankings { Event = e, StudentRanking = Array.Empty>() }) + .Select(e => new EventStudentRankings { Event = e, StudentRanking = [] }) .OrderBy(e => e.Event.Name) .ToArray(); diff --git a/WebApp/Components/Features/Students/Index.razor b/WebApp/Components/Features/Students/Index.razor index b840e37..1186155 100644 --- a/WebApp/Components/Features/Students/Index.razor +++ b/WebApp/Components/Features/Students/Index.razor @@ -1,5 +1,6 @@ @page "/students" @attribute [Authorize] +@implements IAsyncDisposable @using Microsoft.EntityFrameworkCore @using WebApp.Models @using WebApp.Components.Shared.Components @@ -68,18 +69,34 @@ @code { MudDataGrid _dataGrid = null!; private bool _isLoading = true; + private CancellationTokenSource? _cancellationTokenSource; + private bool _isDisposed = false; + + protected override void OnInitialized() + { + _cancellationTokenSource = new CancellationTokenSource(); + } private async Task> ServerReload(GridState state) { + if (_isDisposed) + { + return new GridData { TotalItems = 0, Items = [] }; + } + _isLoading = true; try { + var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None; + var query = - Context.Students.OrderBy(e => e.LastName) + Context.Students + .AsNoTracking() + .OrderBy(e => e.LastName) .Where(state.FilterDefinitions).OrderBy(state.SortDefinitions); - var totalItems = await query.CountAsync(); - var pagedData = await query.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArrayAsync(); + var totalItems = await query.CountAsync(cancellationToken); + var pagedData = await query.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArrayAsync(cancellationToken); return new GridData { @@ -87,31 +104,82 @@ Items = pagedData }; } + catch (TaskCanceledException) + { + return new GridData { TotalItems = 0, Items = [] }; + } + catch (JSDisconnectedException) + { + return new GridData { TotalItems = 0, Items = [] }; + } finally { - _isLoading = false; + if (!_isDisposed) + { + _isLoading = false; + } } } private async Task DeleteStudent(Student student) { - //_isRowBlocked = true; + if (_isDisposed) return; - var result = await DialogService - .ShowMessageBox("Delete student", - (MarkupString)$"Are you sure want to delete {student.Name}? This cannot be undone.", - yesText:"Yes", - noText:"Cancel"); - - if (result == true) + try { - Context.Students.Remove(student!); - await Context.SaveChangesAsync(); - Snackbar.Add($"Delete event: Delete of Student {student.Name}", Severity.Info); - } + var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None; - //_isRowBlocked = false; - StateHasChanged(); - await _dataGrid.ReloadServerData(); + var result = await DialogService + .ShowMessageBox("Delete student", + (MarkupString)$"Are you sure want to delete {student.Name}? This cannot be undone.", + yesText:"Yes", + noText:"Cancel"); + + if (_isDisposed) return; + + if (result == true) + { + Context.Students.Remove(student!); + await Context.SaveChangesAsync(cancellationToken); + + if (!_isDisposed) + { + Snackbar.Add($"Student {student.Name} deleted", Severity.Info); + } + } + + if (!_isDisposed) + { + StateHasChanged(); + await _dataGrid.ReloadServerData(); + } + } + catch (TaskCanceledException) + { + // Component was disposed, ignore + } + catch (JSDisconnectedException) + { + // JS connection lost, ignore + } + catch (Exception ex) + { + if (!_isDisposed) + { + Snackbar.Add($"Error deleting student: {ex.Message}", Severity.Error); + } + } + } + + public async ValueTask DisposeAsync() + { + if (!_isDisposed) + { + _isDisposed = true; + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + } + await ValueTask.CompletedTask; } } diff --git a/WebApp/Components/Features/Students/Registration.razor b/WebApp/Components/Features/Students/Registration.razor index d01485b..b1fc63c 100644 --- a/WebApp/Components/Features/Students/Registration.razor +++ b/WebApp/Components/Features/Students/Registration.razor @@ -174,6 +174,7 @@ { // Load all students with their teams var students = await Context.Students + .AsNoTracking() .Include(s => s.Teams) .ThenInclude(t => t.Event) .Include(s => s.Teams) @@ -270,7 +271,7 @@ Student = student, Events = student.Teams?.Where(t => t?.Event != null) .Select(t => t.Event) - .ToList() ?? new List() + .ToList() ?? [] }; return ValidationService.ValidateStudentStatistics(stats, ValidationContext.StudentRegistration); diff --git a/WebApp/Components/Features/Teams/Edit.razor b/WebApp/Components/Features/Teams/Edit.razor index 06f083d..6a093ca 100644 --- a/WebApp/Components/Features/Teams/Edit.razor +++ b/WebApp/Components/Features/Teams/Edit.razor @@ -71,7 +71,9 @@ .Include(e => e.Event) .Include(e => e.Students) .FirstOrDefaultAsync(m => m.Id == Id); - _students = await Context.Students.ToListAsync(); + _students = await Context.Students + .AsNoTracking() + .ToListAsync(); _selectedStudents = Team?.Students.ToList(); if (Team is null) diff --git a/WebApp/Components/Features/Teams/Printout.razor b/WebApp/Components/Features/Teams/Printout.razor index f9a2cd9..f93cdc8 100644 --- a/WebApp/Components/Features/Teams/Printout.razor +++ b/WebApp/Components/Features/Teams/Printout.razor @@ -218,6 +218,7 @@ else { _teams = await Context.Teams + .AsNoTracking() .Include(e => e.Event) .Include(e => e.Students) .OrderByEventFormatFirst() @@ -228,6 +229,7 @@ else _maxTeamSize = _teams.Max(t => t.Students.Count); _students = await Context.Students + .AsNoTracking() .Include(e => e.Teams) .ThenInclude(e => e.Captain) .Include(e => e.EventRankings) diff --git a/WebApp/LocalStorageService.cs b/WebApp/LocalStorageService.cs index 2c3452f..cc0d4fe 100644 --- a/WebApp/LocalStorageService.cs +++ b/WebApp/LocalStorageService.cs @@ -9,10 +9,12 @@ namespace WebApp; public sealed class LocalStorageService { private readonly IJSRuntime _jsRuntime; + private readonly ILogger _logger; - public LocalStorageService(IJSRuntime jsRuntime) + public LocalStorageService(IJSRuntime jsRuntime, ILogger logger) { _jsRuntime = jsRuntime; + _logger = logger; } /// @@ -47,7 +49,7 @@ public sealed class LocalStorageService } catch (Exception ex) { - Console.WriteLine($"Failed to save boolean to localStorage [{key}]: {ex.Message}"); + _logger.LogWarning(ex, "Failed to save boolean to localStorage [{Key}]", key); } } @@ -83,7 +85,7 @@ public sealed class LocalStorageService } catch (Exception ex) { - Console.WriteLine($"Failed to save integer to localStorage [{key}]: {ex.Message}"); + _logger.LogWarning(ex, "Failed to save integer to localStorage [{Key}]", key); } } @@ -100,14 +102,14 @@ public sealed class LocalStorageService if (!string.IsNullOrEmpty(json)) { var array = JsonSerializer.Deserialize(json); - return array ?? Array.Empty(); + return array ?? []; } - return Array.Empty(); + return []; } catch (Exception ex) { - Console.WriteLine($"Failed to load integer array from localStorage [{key}]: {ex.Message}"); - return Array.Empty(); + _logger.LogWarning(ex, "Failed to load integer array from localStorage [{Key}]", key); + return []; } } @@ -125,7 +127,7 @@ public sealed class LocalStorageService } catch (Exception ex) { - Console.WriteLine($"Failed to save integer array to localStorage [{key}]: {ex.Message}"); + _logger.LogWarning(ex, "Failed to save integer array to localStorage [{Key}]", key); } } @@ -141,7 +143,7 @@ public sealed class LocalStorageService } catch (Exception ex) { - Console.WriteLine($"Failed to remove from localStorage [{key}]: {ex.Message}"); + _logger.LogWarning(ex, "Failed to remove from localStorage [{Key}]", key); } } @@ -156,7 +158,7 @@ public sealed class LocalStorageService } catch (Exception ex) { - Console.WriteLine($"Failed to clear localStorage: {ex.Message}"); + _logger.LogWarning(ex, "Failed to clear localStorage"); } } } diff --git a/WebApp/Logging/AntiforgeryLogEventSink.cs b/WebApp/Logging/AntiforgeryLogEventSink.cs index ba2fab2..3f4d125 100644 --- a/WebApp/Logging/AntiforgeryLogEventSink.cs +++ b/WebApp/Logging/AntiforgeryLogEventSink.cs @@ -33,15 +33,14 @@ namespace WebApp.Logging LogEventLevel.Information, null, // No exception at Info level messageTemplate, - new[] - { + [ new LogEventProperty("OriginalMessage", new ScalarValue(logEvent.MessageTemplate.Render(logEvent.Properties))), new LogEventProperty("SourceContext", logEvent.Properties.GetValueOrDefault("SourceContext") ?? new ScalarValue("Unknown")), new LogEventProperty("RequestPath", logEvent.Properties.GetValueOrDefault("RequestPath") ?? new ScalarValue("Unknown")) - }); + ]); _wrappedSink.Emit(rewrittenEvent); } diff --git a/WebApp/Services/EventDefinitionService.cs b/WebApp/Services/EventDefinitionService.cs index cad1649..da63e6a 100644 --- a/WebApp/Services/EventDefinitionService.cs +++ b/WebApp/Services/EventDefinitionService.cs @@ -40,6 +40,7 @@ public class EventDefinitionService // Get all existing careers from database (case-insensitive lookup) var existingCareers = await _context.Careers + .AsNoTracking() .Where(c => !string.IsNullOrWhiteSpace(c.Name)) .ToListAsync();