7266ab609b
Updated the CareerMapping component to improve clarity by changing terminology from "careers" to "career fields" throughout the UI. Enhanced the diagram generation logic to filter events based on related career fields, ensuring accurate representation of relationships. Added styling for event and field nodes in the Mermaid diagram for better visual distinction.
175 lines
6.1 KiB
Plaintext
175 lines
6.1 KiB
Plaintext
@page "/events/career-mapping"
|
|
@attribute [Authorize]
|
|
@using Microsoft.EntityFrameworkCore
|
|
@using WebApp.Components.Shared.Components
|
|
@using Core.Utility
|
|
@using Core.Entities
|
|
@inject AppDbContext Context
|
|
|
|
<PageHeader
|
|
Title="Career Mapping"
|
|
Subtitle="Event-Career Relationships"
|
|
ShowBackButton="true"
|
|
BackButtonUrl="/events" />
|
|
|
|
@if (_isLoading)
|
|
{
|
|
<MudPaper Elevation="2" Class="pa-6">
|
|
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
|
|
<MudText Typo="Typo.body1" Class="mt-4">Loading career mapping data...</MudText>
|
|
</MudPaper>
|
|
}
|
|
else if (string.IsNullOrWhiteSpace(_mermaidDefinition))
|
|
{
|
|
<MudPaper Elevation="2" Class="pa-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-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 on the left, career fields on the right. Career fields are clusters of related careers.
|
|
</MudText>
|
|
<div class="mermaid-diagram-container" style="overflow-x: auto; min-height: 400px;">
|
|
<MermaidDiagram Definition="@_mermaidDefinition" />
|
|
</div>
|
|
<MudText Style="white-space:pre-wrap;">@_mermaidDefinition</MudText>
|
|
</MudPaper>
|
|
}
|
|
|
|
@code {
|
|
private string _mermaidDefinition = string.Empty;
|
|
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())
|
|
{
|
|
_mermaidDefinition = GenerateMermaidDiagram(eventsWithFields);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_isLoading = false;
|
|
}
|
|
}
|
|
|
|
private string GenerateMermaidDiagram(List<EventDefinition> events)
|
|
{
|
|
var builder = new System.Text.StringBuilder();
|
|
builder.AppendLine("graph LR");
|
|
|
|
// Dictionary to track node IDs and labels (to avoid duplicates)
|
|
var eventNodeIds = new Dictionary<int, (string Id, string Label)>();
|
|
var fieldNodeIds = new Dictionary<int, (string Id, string Label)>();
|
|
var fieldCounter = 1;
|
|
var eventCounter = 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++}";
|
|
var eventLabel = EscapeMermaidLabel(evt.Name);
|
|
eventNodeIds[evt.Id] = (eventNodeId, eventLabel);
|
|
}
|
|
|
|
// 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 career field nodes
|
|
foreach (var field in relatedFields)
|
|
{
|
|
if (!fieldNodeIds.ContainsKey(field.Id))
|
|
{
|
|
var fieldNodeId = $"F{fieldCounter++}";
|
|
var fieldLabel = EscapeMermaidLabel(field.Name);
|
|
fieldNodeIds[field.Id] = (fieldNodeId, fieldLabel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Second pass: define all event nodes (blue)
|
|
foreach (var (id, (nodeId, label)) in eventNodeIds.OrderBy(e => e.Value.Label))
|
|
{
|
|
builder.AppendLine($" {nodeId}[\"{label}\"]:::eventNode");
|
|
}
|
|
|
|
// Third pass: define all career field nodes (green)
|
|
foreach (var (id, (nodeId, label)) in fieldNodeIds.OrderBy(f => f.Value.Label))
|
|
{
|
|
builder.AppendLine($" {nodeId}[\"{label}\"]:::fieldNode");
|
|
}
|
|
|
|
// Fourth pass: define all edges (events on left, fields on right)
|
|
foreach (var evt in events.OrderBy(e => e.Name))
|
|
{
|
|
if (eventToFields.TryGetValue(evt.Id, out var fields))
|
|
{
|
|
var currentEventNodeId = eventNodeIds[evt.Id].Id;
|
|
|
|
foreach (var field in fields.OrderBy(f => f.Name))
|
|
{
|
|
var currentFieldNodeId = fieldNodeIds[field.Id].Id;
|
|
builder.AppendLine($" {currentEventNodeId} --> {currentFieldNodeId}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add styling for different colors
|
|
builder.AppendLine("");
|
|
builder.AppendLine("classDef eventNode fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,color:#fff");
|
|
builder.AppendLine("classDef fieldNode fill:#50C878,stroke:#2D8659,stroke-width:2px,color:#fff");
|
|
|
|
return builder.ToString();
|
|
}
|
|
|
|
private string EscapeMermaidLabel(string label)
|
|
{
|
|
if (string.IsNullOrEmpty(label))
|
|
return string.Empty;
|
|
|
|
// Escape quotes and other special characters for Mermaid labels
|
|
return label
|
|
.Replace("\"", """)
|
|
.Replace("\n", " ")
|
|
.Replace("\r", " ")
|
|
.Trim();
|
|
}
|
|
}
|
|
|