fix(admin): show template language status in editor

Center action controls and adjust template editor field heights.

Ref: IT-1601
This commit is contained in:
2026-10-17 23:14:53 +03:00
parent 7b640fe99e
commit 5a465d7257
6 changed files with 63 additions and 12 deletions
@@ -69,8 +69,12 @@ public sealed class AdminTemplatesController(IMediator mediator, IEmailTemplateD
var service = await services.GetAsync(serviceId, ct);
if (service is null || model.ServiceId != serviceId || model.ServiceName != service.Name)
return NotFound();
if (model.Id is Guid definitionId && await OwnedDefinitionAsync(service, definitionId, ct) is null)
var persisted = model.Id is Guid definitionId ? await OwnedDefinitionAsync(service, definitionId, ct) : null;
if (model.Id is not null && persisted is null)
return NotFound();
var presentCodes = persisted?.Languages.Select(l => l.LanguageCode).ToHashSet(StringComparer.Ordinal);
foreach (var language in model.Languages)
language.IsPresent = presentCodes?.Contains(language.LanguageCode) == true;
ValidateLanguageCatalog(model, service);
if (!ModelState.IsValid)
return EditorView(model, service);
@@ -226,7 +230,8 @@ public sealed class AdminTemplatesController(IMediator mediator, IEmailTemplateD
VariablesJson = JsonSerializer.Serialize(definition.Variables, JsonOptions),
Languages = definition.Languages.Select(l => new EmailTemplateLanguageEditViewModel
{
LanguageCode = l.LanguageCode, Subject = l.Subject, HtmlBody = l.HtmlBody, TextBody = l.TextBody
LanguageCode = l.LanguageCode, Subject = l.Subject, HtmlBody = l.HtmlBody, TextBody = l.TextBody,
IsPresent = true
}).ToList()
};
}
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace HrynCo.NotificationService.Web.Controllers.Admin.ViewModels;
@@ -12,6 +13,8 @@ public sealed class EmailTemplateLanguageEditViewModel : IValidatableObject
public string HtmlBody { get; set; } = "";
[DisplayFormat(ConvertEmptyStringToNull = false)]
public string TextBody { get; set; } = "";
[BindNever]
public bool IsPresent { get; set; }
public bool HasContent => !string.IsNullOrWhiteSpace(Subject) || !string.IsNullOrWhiteSpace(HtmlBody) || !string.IsNullOrWhiteSpace(TextBody);
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
@@ -34,18 +34,22 @@
</div>
<div class="mb-3">
<label asp-for="VariablesJson" class="form-label fw-semibold">Shared variables (JSON)</label>
<textarea asp-for="VariablesJson" class="form-control font-monospace" rows="4"></textarea>
<textarea asp-for="VariablesJson" class="form-control font-monospace" rows="14"></textarea>
<div class="form-text">One contract for every language: <code>[{"name":"UserName","required":true}]</code></div>
</div>
<p class="text-muted">Every configured service language has a tab. Leave all three content fields blank for an unused language; clear them to remove an existing version. Shared variables and the template remain.</p>
<p class="text-muted">Every configured service language has a tab. Present and Missing show saved versions. Leave all three content fields blank for an unused language; clear them to remove an existing version. Shared variables and the template remain.</p>
@if (Model.Languages.Count == 0) { <p>No service languages configured. You can save the key and shared variables now, then add language codes in Edit Service.</p> }
<div class="template-language-sheet mb-3" hidden="@(Model.Languages.Count == 0)">
<div class="nav nav-tabs" role="tablist" aria-label="Template languages" id="languageTabs">
@for (var i = 0; i < Model.Languages.Count; i++)
{
var language = Model.Languages[i];
<button type="button" role="tab" id="language-tab-@i" aria-controls="language-pane-@i"
aria-selected="@(i == active ? "true" : "false")" tabindex="@(i == active ? 0 : -1)"
class="nav-link @(i == active ? "active" : "")" data-pane="language-pane-@i">@Model.Languages[i].LanguageCode</button>
class="nav-link @(i == active ? "active" : "")" data-pane="language-pane-@i">
<span>@language.LanguageCode</span>
<span class="template-language-status @(language.IsPresent ? "template-language-present" : "template-language-missing")">@(language.IsPresent ? "Present" : "Missing")</span>
</button>
}
</div>
<div id="languagePanes">
@@ -59,11 +63,11 @@
</div>
<div class="mb-3">
<label asp-for="Languages[i].HtmlBody" class="form-label fw-semibold">HTML Body</label>
<textarea asp-for="Languages[i].HtmlBody" class="form-control font-monospace" rows="10" data-content="html"></textarea>
<textarea asp-for="Languages[i].HtmlBody" class="form-control font-monospace" rows="15" data-content="html"></textarea>
</div>
<div class="mb-3">
<label asp-for="Languages[i].TextBody" class="form-label fw-semibold">Text Body</label>
<textarea asp-for="Languages[i].TextBody" class="form-control font-monospace" rows="5" data-content="text"></textarea>
<textarea asp-for="Languages[i].TextBody" class="form-control font-monospace" rows="8" data-content="text"></textarea>
</div>
</section>
}
@@ -160,7 +160,13 @@ body {
.admin-data-table .channel-actions {
width: 1%;
white-space: nowrap;
text-align: right;
}
.admin-data-table .template-actions,
.admin-data-table .service-actions,
.admin-data-table .channel-actions,
.admin-data-table .service-language-actions {
text-align: center;
}
.page-header .service-template-service-name {
@@ -243,12 +249,11 @@ body {
/* ── Service language actions ─────────────────────────── */
.service-language-table .service-language-actions {
width: 1%;
text-align: right;
}
.service-language-table .service-language-action-buttons {
display: flex;
justify-content: flex-end;
justify-content: center;
gap: .5rem;
}
@@ -263,7 +268,7 @@ body {
@media (max-width: 575.98px) {
.service-language-table .service-language-action-buttons {
flex-direction: column;
align-items: flex-end;
align-items: center;
}
.service-language-table .service-language-action-buttons > .btn {
@@ -405,6 +410,9 @@ body {
.template-language-sheet > .nav-tabs .nav-link {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
gap: .375rem;
margin-bottom: 0;
white-space: nowrap;
}