Add Career Mapping feature to Events section

Introduced a new CareerMapping component that visualizes relationships between events and related careers using a Mermaid diagram. Updated the Events Index page to include a navigation button for accessing the Career Mapping feature. Added Blazorade.Mermaid package for diagram rendering and updated _Imports.razor to include necessary namespaces.
This commit is contained in:
2025-12-28 21:55:35 -05:00
parent 065a83442c
commit 2c9aa1c223
4 changed files with 149 additions and 0 deletions
@@ -0,0 +1,146 @@
@page "/events/career-mapping"
@attribute [Authorize]
@using Microsoft.EntityFrameworkCore
@using WebApp.Components.Shared.Components
@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 Mappings Found</MudText>
<MudText Typo="Typo.body1" Class="mud-text-secondary">
No events have related careers assigned. Edit events to add related careers.
</MudText>
</MudPaper>
}
else
{
<MudPaper Elevation="2" Class="pa-6">
<MudText Typo="Typo.h6" Class="mb-4">Event-Career Relationships</MudText>
<MudText Typo="Typo.body2" Class="mb-4 mud-text-secondary">
This diagram shows the connections between events and their related careers.
Events are shown on the left, careers on the right.
</MudText>
<div class="mermaid-diagram-container" style="overflow-x: auto; min-height: 400px;">
<MermaidDiagram Definition="@_mermaidDefinition" />
</div>
</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();
if (events.Any())
{
_mermaidDefinition = GenerateMermaidDiagram(events);
}
}
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 careerNodeIds = new Dictionary<int, (string Id, string Label)>();
var careerCounter = 1;
var eventCounter = 1;
// First pass: collect all unique nodes
foreach (var evt in events)
{
if (!eventNodeIds.ContainsKey(evt.Id))
{
var eventNodeId = $"E{eventCounter++}";
var eventLabel = EscapeMermaidLabel(evt.Name);
eventNodeIds[evt.Id] = (eventNodeId, eventLabel);
}
foreach (var career in evt.RelatedCareers)
{
if (!careerNodeIds.ContainsKey(career.Id))
{
var careerNodeId = $"C{careerCounter++}";
var careerLabel = EscapeMermaidLabel(career.Name);
careerNodeIds[career.Id] = (careerNodeId, careerLabel);
}
}
}
// Second pass: define all nodes
foreach (var (id, (nodeId, label)) in eventNodeIds.OrderBy(e => e.Value.Label))
{
builder.AppendLine($" {nodeId}[\"{label}\"]");
}
foreach (var (id, (nodeId, label)) in careerNodeIds.OrderBy(c => c.Value.Label))
{
builder.AppendLine($" {nodeId}[\"{label}\"]");
}
// Third pass: define all edges
foreach (var evt in events.OrderBy(e => e.Name))
{
var currentEventNodeId = eventNodeIds[evt.Id].Id;
foreach (var career in evt.RelatedCareers.OrderBy(c => c.Name))
{
var currentCareerNodeId = careerNodeIds[career.Id].Id;
builder.AppendLine($" {currentEventNodeId} --> {currentCareerNodeId}");
}
}
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("\"", "&quot;")
.Replace("\n", " ")
.Replace("\r", " ")
.Trim();
}
}
@@ -11,6 +11,7 @@
<ActionButtons> <ActionButtons>
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="events/create" Variant="Variant.Filled" Color="Color.Primary">Create New</MudButton> <MudButton StartIcon="@Icons.Material.Filled.Create" Href="events/create" Variant="Variant.Filled" Color="Color.Primary">Create New</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.Print" Href="events/printout" Variant="Variant.Outlined">Printable Descriptions</MudButton> <MudButton StartIcon="@Icons.Material.Filled.Print" Href="events/printout" Variant="Variant.Outlined">Printable Descriptions</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.AccountTree" Href="events/career-mapping" Variant="Variant.Outlined">Career Mapping</MudButton>
</ActionButtons> </ActionButtons>
</PageHeader> </PageHeader>
+1
View File
@@ -26,3 +26,4 @@
@using MudBlazor @using MudBlazor
@using Core.Entities @using Core.Entities
@using Data @using Data
@using Blazorade.Mermaid.Components
+1
View File
@@ -14,6 +14,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="BlazorSortableList" Version="2.1.0" /> <PackageReference Include="BlazorSortableList" Version="2.1.0" />
<PackageReference Include="Blazorade.Mermaid" Version="1.3.0" />
<PackageReference Include="Heron.MudCalendar" Version="3.4.0" /> <PackageReference Include="Heron.MudCalendar" Version="3.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="9.0.11" /> <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="9.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter" Version="9.0.8" /> <PackageReference Include="Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter" Version="9.0.8" />