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:
@@ -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("\"", """)
|
||||||
|
.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>
|
||||||
|
|
||||||
|
|||||||
@@ -26,3 +26,4 @@
|
|||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
@using Core.Entities
|
@using Core.Entities
|
||||||
@using Data
|
@using Data
|
||||||
|
@using Blazorade.Mermaid.Components
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
Reference in New Issue
Block a user