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.
This commit is contained in:
2025-12-29 21:19:08 -05:00
parent 1d3167710d
commit 3bd076afb3
@@ -35,10 +35,28 @@ else
<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.
Use mouse to zoom and pan the graph.
Click on a node to see details. Use mouse to zoom and pan the graph.
</MudText>
@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 (_selectedNodeInfo.Careers != null && _selectedNodeInfo.Careers.Any())
{
<MudText Typo="Typo.subtitle2" Class="mb-2">Related Careers:</MudText>
<MudStack Row="true" Spacing="1" WrapItems="true">
@foreach (var career in _selectedNodeInfo.Careers.OrderBy(c => c))
{
<MudChip T="string" Size="Size.Small" Variant="Variant.Filled" Color="Color.Default">@career</MudChip>
}
</MudStack>
}
</MudPaper>
}
<div style="width: 100%; height: 600px; border: 1px solid #ddd; border-radius: 4px;">
<Network Id="careerMappingNetwork" Data="@_networkData" Options="@GetNetworkOptions"/>
<Network Id="careerMappingNetwork" Data="@_networkData" Options="@GetNetworkOptions" OnClick="HandleNetworkClick"/>
</div>
</MudPaper>
}
@@ -46,6 +64,18 @@ else
@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 bool IsCareerField { get; set; }
public List<string>? Careers { get; set; }
}
protected override async Task OnInitializedAsync()
{
@@ -69,8 +99,41 @@ else
.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] = new List<string>();
}
// 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);
}
}
@@ -111,6 +174,9 @@ else
Shape = "box",
Size = 25
});
// Store mapping for click handling
_nodeIdToEventId[eventNodeId] = evt.Id;
}
// Get related career fields for this event's careers
@@ -141,6 +207,9 @@ else
Shape = "box",
Size = 20
});
// Store mapping for click handling
_nodeIdToFieldId[fieldNodeId] = field.Id;
}
}
}
@@ -185,4 +254,48 @@ else
};
}
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,
IsCareerField = true,
Careers = careers?.ToList() ?? new List<string>()
};
}
}
// 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,
IsCareerField = false,
Careers = evt.RelatedCareers.Select(c => c.Name).OrderBy(c => c).ToList()
};
}
}
}
StateHasChanged();
}
}