feat(web): add Mappings page with inline CRUD table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 22:37:29 -05:00
parent 91f2eec922
commit a6adaea2da
5 changed files with 258 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
@using TaskTracker.Api.Pages
@model TaskTracker.Core.Entities.AppMapping
@{
var isNew = Model.Id == 0;
var formId = isNew ? "mapping-form-new" : $"mapping-form-{Model.Id}";
var rowId = isNew ? "mapping-row-new" : $"mapping-row-{Model.Id}";
}
<tr id="@rowId" style="background: rgba(255,255,255,0.04);">
<td>
<input type="text" name="pattern" value="@Model.Pattern" placeholder="Pattern..."
class="input" autofocus form="@formId" />
</td>
<td>
<select name="matchType" class="select" form="@formId">
@{
var matchTypes = new[] { "ProcessName", "TitleContains", "UrlContains" };
var currentMatch = string.IsNullOrEmpty(Model.MatchType) ? "ProcessName" : Model.MatchType;
}
@foreach (var mt in matchTypes)
{
if (mt == currentMatch)
{
<option value="@mt" selected="selected">@mt</option>
}
else
{
<option value="@mt">@mt</option>
}
}
</select>
</td>
<td>
<input type="text" name="category" value="@Model.Category" placeholder="Category..."
class="input" form="@formId" />
</td>
<td>
<input type="text" name="friendlyName" value="@Model.FriendlyName" placeholder="Friendly name (optional)"
class="input" form="@formId" />
</td>
<td>
<form id="@formId"
hx-post="/mappings?handler=Save@(isNew ? "" : $"&id={Model.Id}")"
hx-target="#@rowId"
hx-swap="outerHTML"
style="display: flex; gap: 4px;">
<button type="submit" class="btn btn--ghost btn--sm" style="color: #22c55e;" title="Save">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>
</button>
@if (isNew)
{
<button type="button" class="btn btn--ghost btn--sm" title="Cancel"
onclick="this.closest('tr').remove()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
}
else
{
<button type="button" class="btn btn--ghost btn--sm" title="Cancel"
hx-get="/mappings?handler=Row&id=@Model.Id"
hx-target="#mapping-row-@Model.Id"
hx-swap="outerHTML">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
}
</form>
</td>
</tr>

View File

@@ -0,0 +1,41 @@
@using TaskTracker.Api.Pages
@model TaskTracker.Core.Entities.AppMapping
@{
var matchColor = MappingsModel.MatchTypeColors.GetValueOrDefault(Model.MatchType, "#64748b");
var catColor = MappingsModel.CategoryColors.GetValueOrDefault(Model.Category, "#64748b");
}
<tr id="mapping-row-@Model.Id">
<td><span class="mapping-pattern">@Model.Pattern</span></td>
<td>
<span class="match-type-badge" style="background: @(matchColor)20; color: @matchColor;">
@Model.MatchType
</span>
</td>
<td>
<span style="display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--color-text-primary);">
<span style="width: 8px; height: 8px; border-radius: 50%; background: @catColor; flex-shrink: 0;"></span>
@Model.Category
</span>
</td>
<td style="color: var(--color-text-secondary);">@(Model.FriendlyName ?? "\u2014")</td>
<td>
<div style="display: flex; gap: 4px;">
<button class="btn btn--ghost btn--sm"
hx-get="/mappings?handler=EditRow&id=@Model.Id"
hx-target="#mapping-row-@Model.Id"
hx-swap="outerHTML"
title="Edit">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
</button>
<button class="btn btn--ghost btn--sm"
hx-delete="/mappings?id=@Model.Id"
hx-target="#mapping-row-@Model.Id"
hx-swap="outerHTML"
hx-confirm="Delete this mapping rule?"
title="Delete"
style="color: var(--color-text-secondary);">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
</button>
</div>
</td>
</tr>