Files
Ai/TrafagSalesExporter/Components/Pages/Transformations.razor

313 lines
14 KiB
Plaintext

@page "/transformations"
@attribute [Authorize(Policy = TrafagSalesExporter.Security.SecurityPolicies.AdminOnly)]
@using System.Reflection
@using TrafagSalesExporter.Models
@using TrafagSalesExporter.Services
@inject ITransformationsPageService TransformationsPageActions
@inject ITransformationCatalog TransformationCatalog
@inject ISnackbar Snackbar
@inject IUiTextService UiText
<PageTitle>@T("Transformationen", "Transformations")</PageTitle>
<MudText Typo="Typo.h4" Class="mb-4">@T("Transformer Ansicht", "Transformation view")</MudText>
<MudText Typo="Typo.body1" Class="mb-4">@T("Definiere pro Quellsystem einfache Feldregeln und komplexe record-basierte Strategien.", "Define simple field rules and complex record-based strategies per source system.")</MudText>
<MudPaper Class="pa-4" Elevation="1">
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined" Class="mb-3">
`Value`-Regeln arbeiten feldweise. `Record`-Regeln rufen eine registrierte C#-Strategie auf und koennen mehrere Felder eines Datensatzes verwenden.
</MudAlert>
<MudStack Row="true" Spacing="2" Class="mb-3">
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add" OnClick="AddRule">
@T("Regel hinzufuegen", "Add rule")
</MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Secondary" StartIcon="@Icons.Material.Filled.Save" OnClick="SaveAllAsync">
@T("Alle speichern", "Save all")
</MudButton>
</MudStack>
<MudTable Items="_rules" Dense Hover Striped>
<HeaderContent>
<MudTh>Aktiv</MudTh>
<MudTh>System</MudTh>
<MudTh>Scope</MudTh>
<MudTh>Source</MudTh>
<MudTh>Target</MudTh>
<MudTh>Typ / Klasse</MudTh>
<MudTh>Argument</MudTh>
<MudTh>Sort</MudTh>
<MudTh>Info</MudTh>
<MudTh>Aktionen</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd><MudCheckBox @bind-Value="context.IsActive" /></MudTd>
<MudTd>
<MudSelect T="string" Value="@context.SourceSystem" ValueChanged="@(v => context.SourceSystem = v)" Dense>
@foreach (var system in _sourceSystems.Where(x => x.IsActive))
{
<MudSelectItem Value="@system.Code">@system.DisplayName (@system.Code)</MudSelectItem>
}
</MudSelect>
</MudTd>
<MudTd>
<MudSelect T="string" Value="@context.RuleScope" ValueChanged="@(v => ChangeRuleScope(context, v))" Dense>
@foreach (var scope in _ruleScopes)
{
<MudSelectItem Value="@scope">@scope</MudSelectItem>
}
</MudSelect>
</MudTd>
<MudTd>
@if (IsRecordScope(context))
{
<MudChip T="string" Color="Color.Default" Variant="Variant.Outlined" Size="Size.Small" Text="Record-Regel" />
}
else
{
<MudSelect T="string" Value="@context.SourceField" ValueChanged="@(v => context.SourceField = v)" Dense>
@foreach (var field in _recordFields)
{
<MudSelectItem Value="@field">@field</MudSelectItem>
}
</MudSelect>
}
</MudTd>
<MudTd>
<MudSelect T="string" Value="@context.TargetField" ValueChanged="@(v => context.TargetField = v)" Dense>
@foreach (var field in _recordFields)
{
<MudSelectItem Value="@field">@field</MudSelectItem>
}
</MudSelect>
</MudTd>
<MudTd>
@{
var availableTypes = GetTypesForScope(context.RuleScope);
}
<MudSelect T="string"
@key="@GetTypeSelectKey(context)"
Value="@context.TransformationType"
ValueChanged="@(v => context.TransformationType = v)"
Dense
HelperText="@GetTypeHelperText(context)">
@foreach (var type in availableTypes)
{
<MudSelectItem Value="@type.Key">@(IsRecordScope(context) ? $"Klasse: {type.Key}" : type.Key)</MudSelectItem>
}
</MudSelect>
@if (IsRecordScope(context))
{
<MudText Typo="Typo.caption" Class="mt-1">
Hier waehlt man die registrierte C#-Strategie.
</MudText>
}
</MudTd>
<MudTd>
<MudTextField T="string" Value="@context.Argument" ValueChanged="@(v => context.Argument = v)"
HelperText="@GetArgumentHelperText(context)" />
</MudTd>
<MudTd>
<MudNumericField T="int" Value="@context.SortOrder" ValueChanged="@(v => context.SortOrder = v)" Dense />
</MudTd>
<MudTd>
@{
var catalogItem = GetCatalogItem(context);
}
<MudStack Spacing="1">
<MudText Typo="Typo.caption">@((catalogItem?.Description ?? T("Keine Beschreibung.", "No description.")) )</MudText>
<MudButton Variant="Variant.Text" Color="Color.Info" Size="Size.Small"
StartIcon="@Icons.Material.Filled.Code"
Disabled="@(catalogItem is null)"
OnClick="() => ShowCode(context)">
@T("Code anzeigen", "Show code")
</MudButton>
</MudStack>
</MudTd>
<MudTd>
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Size="Size.Small"
OnClick="() => RemoveRule(context)" />
</MudTd>
</RowTemplate>
</MudTable>
</MudPaper>
<MudDialog @bind-Visible="_codeDialogVisible" Options="_codeDialogOptions">
<TitleContent>
<MudText Typo="Typo.h6">@T("Transformationscode", "Transformation code")</MudText>
</TitleContent>
<DialogContent>
@if (_selectedCatalogItem is not null)
{
<MudStack Spacing="2">
<MudText Typo="Typo.subtitle2">@_selectedCatalogItem.Key (@_selectedCatalogItem.RuleScope)</MudText>
<MudText Typo="Typo.body2">@_selectedCatalogItem.Description</MudText>
<MudText Typo="Typo.caption">Klasse: @_selectedCatalogItem.TypeName</MudText>
<MudText Typo="Typo.caption">
Datei:
<MudLink Href="@GetSourceViewerUrl(_selectedCatalogItem.SourceFile, _selectedCatalogItem.TypeName)" Target="_blank">
@_selectedCatalogItem.SourceFile
</MudLink>
</MudText>
<MudPaper Class="pa-3">
<MudText Typo="Typo.caption">Snippet</MudText>
<pre style="margin:0; white-space:pre-wrap;">@_selectedCatalogItem.CodeSnippet</pre>
</MudPaper>
@if (_selectedRule is not null)
{
<MudPaper Class="pa-3">
<MudText Typo="Typo.caption">Aktuelle Regel</MudText>
<MudText Typo="Typo.body2">System: @_selectedRule.SourceSystem</MudText>
<MudText Typo="Typo.body2">Target: @_selectedRule.TargetField</MudText>
@if (!string.IsNullOrWhiteSpace(_selectedRule.SourceField))
{
<MudText Typo="Typo.body2">Source: @_selectedRule.SourceField</MudText>
}
<MudText Typo="Typo.body2">Argument: @(string.IsNullOrWhiteSpace(_selectedRule.Argument) ? "-" : _selectedRule.Argument)</MudText>
</MudPaper>
}
</MudStack>
}
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Text" OnClick="CloseCodeDialog">@T("Schliessen", "Close")</MudButton>
</DialogActions>
</MudDialog>
@code {
private readonly string[] _ruleScopes = ["Value", "Record"];
private readonly string[] _recordFields = typeof(SalesRecord)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Select(p => p.Name)
.OrderBy(n => n)
.ToArray();
private List<FieldTransformationRule> _rules = new();
private List<SourceSystemDefinition> _sourceSystems = [];
private IReadOnlyList<TransformationCatalogItem> _catalogItems = [];
private bool _codeDialogVisible;
private FieldTransformationRule? _selectedRule;
private TransformationCatalogItem? _selectedCatalogItem;
private readonly DialogOptions _codeDialogOptions = new() { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
protected override async Task OnInitializedAsync()
{
_catalogItems = TransformationCatalog.GetAll();
await LoadAsync();
}
private async Task LoadAsync()
{
var state = await TransformationsPageActions.LoadAsync();
_sourceSystems = state.SourceSystems;
_rules = state.Rules;
foreach (var rule in _rules)
{
rule.RuleScope = string.IsNullOrWhiteSpace(rule.RuleScope) ? "Value" : rule.RuleScope;
if (!GetTypesForScope(rule.RuleScope).Any(x => string.Equals(x.Key, rule.TransformationType, StringComparison.OrdinalIgnoreCase)))
{
rule.TransformationType = GetTypesForScope(rule.RuleScope).FirstOrDefault()?.Key ?? "Copy";
}
}
}
private void AddRule()
{
var nextSort = _rules.Count == 0 ? 10 : _rules.Max(r => r.SortOrder) + 10;
_rules.Add(new FieldTransformationRule
{
SourceSystem = _sourceSystems.FirstOrDefault(x => x.IsActive)?.Code ?? "SAP",
RuleScope = "Value",
SourceField = nameof(SalesRecord.Material),
TargetField = nameof(SalesRecord.Material),
TransformationType = "Copy",
SortOrder = nextSort,
IsActive = true
});
}
private void RemoveRule(FieldTransformationRule rule)
{
_rules.Remove(rule);
}
private async Task SaveAllAsync()
{
_rules = await TransformationsPageActions.SaveAllAsync(_rules);
Snackbar.Add(T("Transformationsregeln gespeichert.", "Transformation rules saved."), Severity.Success);
await LoadAsync();
}
private IReadOnlyList<TransformationCatalogItem> GetTypesForScope(string? ruleScope)
{
var scope = string.IsNullOrWhiteSpace(ruleScope) ? "Value" : ruleScope;
return TransformationCatalog.GetByScope(scope);
}
private static bool IsRecordScope(FieldTransformationRule rule)
=> string.Equals(rule.RuleScope, "Record", StringComparison.OrdinalIgnoreCase);
private void ChangeRuleScope(FieldTransformationRule rule, string scope)
{
rule.RuleScope = scope;
var firstType = GetTypesForScope(scope).FirstOrDefault()?.Key;
if (!string.IsNullOrWhiteSpace(firstType))
rule.TransformationType = firstType;
if (IsRecordScope(rule))
rule.SourceField = string.Empty;
else if (string.IsNullOrWhiteSpace(rule.SourceField))
rule.SourceField = nameof(SalesRecord.Material);
}
private string GetArgumentHelperText(FieldTransformationRule rule)
{
var item = _catalogItems.FirstOrDefault(x =>
string.Equals(x.RuleScope, rule.RuleScope, StringComparison.OrdinalIgnoreCase) &&
string.Equals(x.Key, rule.TransformationType, StringComparison.OrdinalIgnoreCase));
return item?.Description ?? T("Optionales Argument.", "Optional argument.");
}
private TransformationCatalogItem? GetCatalogItem(FieldTransformationRule rule)
=> _catalogItems.FirstOrDefault(x =>
string.Equals(x.RuleScope, rule.RuleScope, StringComparison.OrdinalIgnoreCase) &&
string.Equals(x.Key, rule.TransformationType, StringComparison.OrdinalIgnoreCase));
private void ShowCode(FieldTransformationRule rule)
{
_selectedRule = rule;
_selectedCatalogItem = GetCatalogItem(rule);
_codeDialogVisible = _selectedCatalogItem is not null;
}
private void CloseCodeDialog()
{
_codeDialogVisible = false;
_selectedRule = null;
_selectedCatalogItem = null;
}
private static string GetSourceViewerUrl(string sourceFile, string typeName)
=> $"/source-viewer?path={Uri.EscapeDataString(sourceFile)}&type={Uri.EscapeDataString(typeName)}";
private static string GetTypeSelectKey(FieldTransformationRule rule)
=> $"{rule.Id}:{rule.RuleScope}:{rule.TransformationType}";
private string GetTypeHelperText(FieldTransformationRule rule)
{
var types = GetTypesForScope(rule.RuleScope);
return types.Count == 0
? T("Keine Typen fuer diesen Scope registriert.", "No types registered for this scope.")
: IsRecordScope(rule)
? string.Format(T("Verfuegbare Klassen: {0}", "Available classes: {0}"), string.Join(", ", types.Select(x => x.Key)))
: string.Format(T("Verfuegbare Typen: {0}", "Available types: {0}"), string.Join(", ", types.Select(x => x.Key)));
}
}
@code {
private string T(string german, string english) => UiText.Text(german, english);
}