@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 @if (_isLoading) { Loading career mapping data... } else if (_networkData == null || !_networkData.Nodes.Any()) { No Career Field Mappings Found No events have related careers assigned that match any career fields. Edit events to add related careers. } else { Event-Career Field Relationships 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. Use mouse to zoom and pan the graph.
} @code { private NetworkData? _networkData; private bool _isLoading = true; 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(); if (eventsWithFields.Any()) { _networkData = GenerateNetworkData(eventsWithFields); } } finally { _isLoading = false; } } private NetworkData GenerateNetworkData(List events) { var nodes = new List(); var edges = new List(); // Dictionary to track node IDs (to avoid duplicates) var eventNodeIds = new Dictionary(); var fieldNodeIds = new Dictionary(); var eventCounter = 1; var fieldCounter = 1; // Dictionary to track which events connect to which career fields var eventToFields = new Dictionary>(); // 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 }); } // Get related career fields for this event's careers var relatedFields = CareerFieldDefinitions.GetRelatedCareerFields(evt.RelatedCareers); if (relatedFields.Any()) { eventToFields[evt.Id] = new HashSet(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 }); } } } } // 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" }; } }