Files
poprhythm 8af86e22d9 Refactor collection initializers to use C# 12 collection expressions
This commit updates various files across the Core and WebApp projects to replace traditional collection initializers with C# 12 collection expressions. Changes include modifications to EventAssignment.cs, TeamScheduler_DecisionTree.cs, CareerField.cs, EventDefinition.cs, and several components in the WebApp. These updates enhance code readability and maintainability by adhering to modern C# syntax standards.
2026-01-11 10:35:58 -05:00

311 lines
12 KiB
Plaintext

@page "/events/career-mapping"
@attribute [Authorize]
@using Microsoft.EntityFrameworkCore
@using Core.Utility
@using VisNetwork.Blazor.Models
@using Edge = VisNetwork.Blazor.Models.Edge
@inject AppDbContext Context
<PageHeader
Title="Career Mapping"
Subtitle="Event-Career Relationships"
ShowBackButton="true"
BackButtonUrl="/events"/>
@if (_isLoading)
{
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudProgressLinear Indeterminate="true" Color="Color.Primary"/>
<MudText Typo="Typo.body1" Class="mt-4">Loading career mapping data...</MudText>
</MudPaper>
}
else if (_networkData == null || !_networkData.Nodes.Any())
{
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h6" Class="mb-4">No Career Field Mappings Found</MudText>
<MudText Typo="Typo.body1" Class="mud-text-secondary">
No events have related careers assigned that match any career fields. Edit events to add related careers.
</MudText>
</MudPaper>
}
else
{
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h6" Class="mb-4">Event-Career Field Relationships</MudText>
<MudText Typo="Typo.body2" Class="mb-4 mud-text-secondary">
This diagram shows the connections between events and their related career fields.
Events are shown in blue, career fields in green. Career fields are clusters of related careers.
Click on a node to see details. Use mouse to zoom and pan the graph.
</MudText>
<div style="width: 100%; height: 600px; border: 1px solid #ddd; border-radius: 4px;">
<Network Id="careerMappingNetwork" Data="@_networkData" Options="@GetNetworkOptions" OnClick="HandleNetworkClick" />
</div>
@if (_selectedNodeInfo != null)
{
<MudPaper Elevation="1" Class="pa-4 mb-4" Style="background-color: #f5f5f5;">
<MudText Typo="Typo.h6" Class="mb-2">@_selectedNodeInfo.Title</MudText>
@if (!string.IsNullOrWhiteSpace(_selectedNodeInfo.Description))
{
<MudText Typo="Typo.body2" Class="mb-3 mud-text-secondary">@_selectedNodeInfo.Description</MudText>
}
@if (_selectedNodeInfo.Careers != null && _selectedNodeInfo.Careers.Any())
{
<MudText Typo="Typo.subtitle2" Class="mb-2">Related Careers:</MudText>
<div class="career-mapping">
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap">
@foreach (var career in _selectedNodeInfo.Careers.OrderBy(c => c))
{
<MudChip T="string" Size="Size.Small" Variant="Variant.Filled" Color="Color.Default">@career</MudChip>
}
</MudStack>
</div>
}
</MudPaper>
}
</MudPaper>
}
@code {
private NetworkData? _networkData;
private bool _isLoading = true;
private Dictionary<int, List<string>> _fieldIdToCareers = new();
private List<EventDefinition>? _allEvents;
private Dictionary<string, int> _nodeIdToFieldId = new();
private Dictionary<string, int> _nodeIdToEventId = new();
private SelectedNodeInfo? _selectedNodeInfo;
private class SelectedNodeInfo
{
public string Title { get; set; } = string.Empty;
public string? Description { get; set; }
public bool IsCareerField { get; set; }
public List<string>? Careers { get; set; }
}
protected override async Task OnInitializedAsync()
{
await LoadDataAsync();
}
private async Task LoadDataAsync()
{
_isLoading = true;
try
{
var events = await Context.Events
.Include(e => e.RelatedCareers)
.Where(e => e.RelatedCareers.Any())
.OrderBy(e => e.Name)
.ToListAsync();
// Filter to only events that have career fields (after matching)
var eventsWithFields = events
.Where(e => CareerFieldDefinitions.GetRelatedCareerFields(e.RelatedCareers).Any())
.ToList();
_allEvents = eventsWithFields;
// Build mapping of career field IDs to their related careers
_fieldIdToCareers.Clear();
foreach (var evt in eventsWithFields)
{
var relatedFields = CareerFieldDefinitions.GetRelatedCareerFields(evt.RelatedCareers);
foreach (var field in relatedFields)
{
if (!_fieldIdToCareers.ContainsKey(field.Id))
{
_fieldIdToCareers[field.Id] = [];
}
// Add unique career names for this field
foreach (var career in evt.RelatedCareers)
{
var normalizedCareerName = CareerNormalizer.GetNormalizedKey(career.Name);
// Check if this career matches the field
var matchesField = field.DirectCareerMatches.Any(dcm =>
string.Equals(CareerNormalizer.GetNormalizedKey(dcm), normalizedCareerName, StringComparison.OrdinalIgnoreCase)) ||
field.PatternKeywords.Any(pk => normalizedCareerName.Contains(pk, StringComparison.OrdinalIgnoreCase));
if (matchesField && !_fieldIdToCareers[field.Id].Contains(career.Name, StringComparer.OrdinalIgnoreCase))
{
_fieldIdToCareers[field.Id].Add(career.Name);
}
}
}
}
if (eventsWithFields.Any())
{
// Clear mappings before regenerating
_nodeIdToFieldId.Clear();
_nodeIdToEventId.Clear();
_networkData = GenerateNetworkData(eventsWithFields);
}
}
finally
{
_isLoading = false;
}
}
private NetworkData GenerateNetworkData(List<EventDefinition> events)
{
List<Node> nodes = [];
List<Edge> edges = [];
// Dictionary to track node IDs (to avoid duplicates)
var eventNodeIds = new Dictionary<int, string>();
var fieldNodeIds = new Dictionary<int, string>();
var eventCounter = 1;
var fieldCounter = 1;
// Dictionary to track which events connect to which career fields
var eventToFields = new Dictionary<int, HashSet<CareerField>>();
// First pass: collect all unique nodes and determine relationships
foreach (var evt in events.OrderBy(e => e.Name))
{
if (!eventNodeIds.ContainsKey(evt.Id))
{
var eventNodeId = $"E{eventCounter++}";
eventNodeIds[evt.Id] = eventNodeId;
// Add event node (blue)
nodes.Add(new Node
{
Id = eventNodeId,
Label = evt.Name,
Color = new NodeColorType { Background = "#90C3F5", Border = "#7DB3F0" },
Shape = "box",
Size = 25
});
// Store mapping for click handling
_nodeIdToEventId[eventNodeId] = evt.Id;
}
// Get related career fields for this event's careers
var relatedFields = CareerFieldDefinitions.GetRelatedCareerFields(evt.RelatedCareers);
if (relatedFields.Any())
{
eventToFields[evt.Id] = new HashSet<CareerField>(relatedFields);
// Track and add career field nodes
foreach (var field in relatedFields)
{
if (!fieldNodeIds.ContainsKey(field.Id))
{
var fieldNodeId = $"F{fieldCounter++}";
fieldNodeIds[field.Id] = fieldNodeId;
// Add career field node (green)
nodes.Add(new Node
{
Id = fieldNodeId,
Label = field.Name,
Color = new NodeColorType
{
Background = "#90F8B0",
Border = "#80E8A0",
Highlight = new NodeColorType.BorderBackgroundColor { Background = "#90F8B0", Border = "#80E8A0" }
},
Shape = "box",
Size = 20
});
// Store mapping for click handling
_nodeIdToFieldId[fieldNodeId] = field.Id;
}
}
}
}
// Second pass: create edges from events to career fields
foreach (var evt in events.OrderBy(e => e.Name))
{
if (eventToFields.TryGetValue(evt.Id, out var fields))
{
var eventNodeId = eventNodeIds[evt.Id];
foreach (var field in fields.OrderBy(f => f.Name))
{
var fieldNodeId = fieldNodeIds[field.Id];
edges.Add(new Edge
{
From = eventNodeId,
To = fieldNodeId
});
}
}
}
return new NetworkData
{
Nodes = nodes,
Edges = edges
};
}
private NetworkOptions GetNetworkOptions(Network network)
{
return new NetworkOptions
{
AutoResize = true,
Physics = new PhysicsOptions
{
Enabled = true
},
Height = "600px"
};
}
private void HandleNetworkClick(ClickEvent eventArg)
{
_selectedNodeInfo = null;
// Check if a node was clicked
if (eventArg.Nodes != null && eventArg.Nodes.Count > 0)
{
var nodeId = eventArg.Nodes[0];
// Check if it's a career field node
if (_nodeIdToFieldId.TryGetValue(nodeId, out var fieldId))
{
var field = CareerFieldDefinitions.GetAllCareerFields().FirstOrDefault(f => f.Id == fieldId);
if (field != null)
{
_fieldIdToCareers.TryGetValue(fieldId, out var careers);
_selectedNodeInfo = new SelectedNodeInfo
{
Title = field.Name,
Description = field.Description,
IsCareerField = true,
Careers = careers?.ToList() ?? []
};
}
}
// Check if it's an event node
else if (_nodeIdToEventId.TryGetValue(nodeId, out var eventId) && _allEvents != null)
{
var evt = _allEvents.FirstOrDefault(e => e.Id == eventId);
if (evt != null)
{
_selectedNodeInfo = new SelectedNodeInfo
{
Title = evt.Name,
Description = evt.Description,
IsCareerField = false,
Careers = evt.RelatedCareers.Select(c => c.Name).OrderBy(c => c).ToList()
};
}
}
}
StateHasChanged();
}
}