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:
@@ -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()
|
||||
};
|
||||
}
|
||||
|
||||
+3
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user