@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"
};
}
}