Add collapsible band editor to grid components
Introduce a toggleable "Band-Layout konfigurieren" section in CatalogsGrid.razor and MassDataGrid.razor, allowing users to expand or collapse the band editor UI. Added bandEditorExpanded state to control visibility. Updated CSS to style the new toggle button and its expanded/collapsed states, improving usability and reducing UI clutter.
This commit is contained in:
@@ -25,33 +25,42 @@ else if (items.Count == 0)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="band-editor">
|
<div class="band-editor">
|
||||||
<div class="band-controls">
|
<button class="band-editor-toggle" @onclick="() => bandEditorExpanded = !bandEditorExpanded">
|
||||||
<DxButton Text="Band hinzufügen" Click="AddBand" />
|
<span class="band-editor-toggle-icon @(bandEditorExpanded ? "expanded" : "")">►</span>
|
||||||
<DxButton Text="Layout speichern" Click="SaveLayoutAsync" Enabled="@CanSaveBandLayout" />
|
<span>Band-Layout konfigurieren</span>
|
||||||
<DxButton Text="Layout zurücksetzen" Click="ResetLayoutAsync" />
|
</button>
|
||||||
</div>
|
@if (bandEditorExpanded)
|
||||||
@foreach (var band in bandLayout.Bands)
|
|
||||||
{
|
{
|
||||||
<div class="band-row">
|
<div class="band-editor-body">
|
||||||
<DxTextBox Text="@band.Caption" TextChanged="@(value => UpdateBandCaption(band, value))" />
|
<div class="band-controls">
|
||||||
<DxButton Text="Entfernen" Click="@(() => RemoveBand(band))" />
|
<DxButton Text="Band hinzufügen" Click="AddBand" />
|
||||||
|
<DxButton Text="Layout speichern" Click="SaveLayoutAsync" Enabled="@CanSaveBandLayout" />
|
||||||
|
<DxButton Text="Layout zurücksetzen" Click="ResetLayoutAsync" />
|
||||||
|
</div>
|
||||||
|
@foreach (var band in bandLayout.Bands)
|
||||||
|
{
|
||||||
|
<div class="band-row">
|
||||||
|
<DxTextBox Text="@band.Caption" TextChanged="@(value => UpdateBandCaption(band, value))" />
|
||||||
|
<DxButton Text="Entfernen" Click="@(() => RemoveBand(band))" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<DxFormLayout CssClass="band-columns" ColCount="2">
|
||||||
|
@foreach (var column in columnDefinitions)
|
||||||
|
{
|
||||||
|
<DxFormLayoutItem Caption="@column.Caption">
|
||||||
|
<DxComboBox Data="@bandOptions"
|
||||||
|
TData="BandOption"
|
||||||
|
TValue="string"
|
||||||
|
TextFieldName="Caption"
|
||||||
|
ValueFieldName="Id"
|
||||||
|
Value="@GetColumnBand(column.FieldName)"
|
||||||
|
ValueChanged="@(value => UpdateColumnBand(column.FieldName, value))"
|
||||||
|
Width="100%" />
|
||||||
|
</DxFormLayoutItem>
|
||||||
|
}
|
||||||
|
</DxFormLayout>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<DxFormLayout CssClass="band-columns" ColCount="2">
|
|
||||||
@foreach (var column in columnDefinitions)
|
|
||||||
{
|
|
||||||
<DxFormLayoutItem Caption="@column.Caption">
|
|
||||||
<DxComboBox Data="@bandOptions"
|
|
||||||
TData="BandOption"
|
|
||||||
TValue="string"
|
|
||||||
TextFieldName="Caption"
|
|
||||||
ValueFieldName="Id"
|
|
||||||
Value="@GetColumnBand(column.FieldName)"
|
|
||||||
ValueChanged="@(value => UpdateColumnBand(column.FieldName, value))"
|
|
||||||
Width="100%" />
|
|
||||||
</DxFormLayoutItem>
|
|
||||||
}
|
|
||||||
</DxFormLayout>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid-section">
|
<div class="grid-section">
|
||||||
@@ -150,6 +159,7 @@ else
|
|||||||
private List<BandOption> bandOptions = new();
|
private List<BandOption> bandOptions = new();
|
||||||
private Dictionary<string, ColumnDefinition> columnLookup = new();
|
private Dictionary<string, ColumnDefinition> columnLookup = new();
|
||||||
private bool gridLayoutApplied;
|
private bool gridLayoutApplied;
|
||||||
|
private bool bandEditorExpanded;
|
||||||
|
|
||||||
private List<ColumnDefinition> columnDefinitions = new()
|
private List<ColumnDefinition> columnDefinitions = new()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,33 +37,42 @@ else
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="band-editor">
|
<div class="band-editor">
|
||||||
<div class="band-controls">
|
<button class="band-editor-toggle" @onclick="() => bandEditorExpanded = !bandEditorExpanded">
|
||||||
<DxButton Text="Band hinzufügen" Click="AddBand" />
|
<span class="band-editor-toggle-icon @(bandEditorExpanded ? "expanded" : "")">►</span>
|
||||||
<DxButton Text="Layout speichern" Click="SaveLayoutAsync" Enabled="@CanSaveBandLayout" />
|
<span>Band-Layout konfigurieren</span>
|
||||||
<DxButton Text="Layout zurücksetzen" Click="ResetLayoutAsync" />
|
</button>
|
||||||
</div>
|
@if (bandEditorExpanded)
|
||||||
@foreach (var band in bandLayout.Bands)
|
|
||||||
{
|
{
|
||||||
<div class="band-row">
|
<div class="band-editor-body">
|
||||||
<DxTextBox Text="@band.Caption" TextChanged="@(value => UpdateBandCaption(band, value))" />
|
<div class="band-controls">
|
||||||
<DxButton Text="Entfernen" Click="@(() => RemoveBand(band))" />
|
<DxButton Text="Band hinzufügen" Click="AddBand" />
|
||||||
|
<DxButton Text="Layout speichern" Click="SaveLayoutAsync" Enabled="@CanSaveBandLayout" />
|
||||||
|
<DxButton Text="Layout zurücksetzen" Click="ResetLayoutAsync" />
|
||||||
|
</div>
|
||||||
|
@foreach (var band in bandLayout.Bands)
|
||||||
|
{
|
||||||
|
<div class="band-row">
|
||||||
|
<DxTextBox Text="@band.Caption" TextChanged="@(value => UpdateBandCaption(band, value))" />
|
||||||
|
<DxButton Text="Entfernen" Click="@(() => RemoveBand(band))" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<DxFormLayout CssClass="band-columns" ColCount="2">
|
||||||
|
@foreach (var column in columnDefinitions)
|
||||||
|
{
|
||||||
|
<DxFormLayoutItem Caption="@column.Caption">
|
||||||
|
<DxComboBox Data="@bandOptions"
|
||||||
|
TData="BandOption"
|
||||||
|
TValue="string"
|
||||||
|
TextFieldName="Caption"
|
||||||
|
ValueFieldName="Id"
|
||||||
|
Value="@GetColumnBand(column.FieldName)"
|
||||||
|
ValueChanged="@(value => UpdateColumnBand(column.FieldName, value))"
|
||||||
|
Width="100%" />
|
||||||
|
</DxFormLayoutItem>
|
||||||
|
}
|
||||||
|
</DxFormLayout>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<DxFormLayout CssClass="band-columns" ColCount="2">
|
|
||||||
@foreach (var column in columnDefinitions)
|
|
||||||
{
|
|
||||||
<DxFormLayoutItem Caption="@column.Caption">
|
|
||||||
<DxComboBox Data="@bandOptions"
|
|
||||||
TData="BandOption"
|
|
||||||
TValue="string"
|
|
||||||
TextFieldName="Caption"
|
|
||||||
ValueFieldName="Id"
|
|
||||||
Value="@GetColumnBand(column.FieldName)"
|
|
||||||
ValueChanged="@(value => UpdateColumnBand(column.FieldName, value))"
|
|
||||||
Width="100%" />
|
|
||||||
</DxFormLayoutItem>
|
|
||||||
}
|
|
||||||
</DxFormLayout>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid-section">
|
<div class="grid-section">
|
||||||
@@ -178,6 +187,7 @@ else
|
|||||||
private List<BandOption> bandOptions = new();
|
private List<BandOption> bandOptions = new();
|
||||||
private Dictionary<string, ColumnDefinition> columnLookup = new();
|
private Dictionary<string, ColumnDefinition> columnLookup = new();
|
||||||
private bool gridLayoutApplied;
|
private bool gridLayoutApplied;
|
||||||
|
private bool bandEditorExpanded;
|
||||||
|
|
||||||
private List<ColumnDefinition> columnDefinitions = new()
|
private List<ColumnDefinition> columnDefinitions = new()
|
||||||
{
|
{
|
||||||
@@ -281,7 +291,6 @@ else
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
if (string.IsNullOrWhiteSpace(layoutUser))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CaptureColumnLayoutFromGrid();
|
CaptureColumnLayoutFromGrid();
|
||||||
@@ -299,41 +308,31 @@ else
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
if (string.IsNullOrWhiteSpace(layoutUser))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser);
|
await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser);
|
||||||
|
|
||||||
bandLayout = new BandLayout();
|
bandLayout = new BandLayout();
|
||||||
columnBandAssignments.Clear();
|
columnBandAssignments.Clear();
|
||||||
UpdateBandOptions();
|
UpdateBandOptions();
|
||||||
|
|
||||||
foreach (var column in columnDefinitions)
|
foreach (var column in columnDefinitions)
|
||||||
column.Width = null;
|
column.Width = null;
|
||||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
_sizeMode = SizeMode.Medium;
|
_sizeMode = SizeMode.Medium;
|
||||||
|
|
||||||
if (gridRef != null)
|
if (gridRef != null)
|
||||||
gridRef.LoadLayout(new GridPersistentLayout());
|
gridRef.LoadLayout(new GridPersistentLayout());
|
||||||
gridLayoutApplied = false;
|
gridLayoutApplied = false;
|
||||||
|
|
||||||
infoMessage = "Layout zurückgesetzt.";
|
infoMessage = "Layout zurückgesetzt.";
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CaptureColumnLayoutFromGrid()
|
private void CaptureColumnLayoutFromGrid()
|
||||||
{
|
{
|
||||||
if (gridRef == null)
|
if (gridRef == null) return;
|
||||||
return;
|
|
||||||
|
|
||||||
var layout = gridRef.SaveLayout();
|
var layout = gridRef.SaveLayout();
|
||||||
bandLayout.GridLayout = layout;
|
bandLayout.GridLayout = layout;
|
||||||
bandLayout.SizeMode = _sizeMode;
|
bandLayout.SizeMode = _sizeMode;
|
||||||
|
|
||||||
var orderedColumns = layout.Columns
|
var orderedColumns = layout.Columns
|
||||||
.Where(c => !string.IsNullOrWhiteSpace(c.FieldName))
|
.Where(c => !string.IsNullOrWhiteSpace(c.FieldName))
|
||||||
.OrderBy(c => c.VisibleIndex)
|
.OrderBy(c => c.VisibleIndex)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
bandLayout.ColumnOrder = orderedColumns.Select(c => c.FieldName).ToList();
|
bandLayout.ColumnOrder = orderedColumns.Select(c => c.FieldName).ToList();
|
||||||
bandLayout.ColumnWidths = orderedColumns
|
bandLayout.ColumnWidths = orderedColumns
|
||||||
.Where(c => !string.IsNullOrWhiteSpace(c.Width))
|
.Where(c => !string.IsNullOrWhiteSpace(c.Width))
|
||||||
@@ -407,15 +406,12 @@ else
|
|||||||
builder.OpenComponent<DxGridCommandColumn>(seq++);
|
builder.OpenComponent<DxGridCommandColumn>(seq++);
|
||||||
builder.AddAttribute(seq++, "Width", "120px");
|
builder.AddAttribute(seq++, "Width", "120px");
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
|
|
||||||
var grouped = bandLayout.Bands.SelectMany(b => b.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
var grouped = bandLayout.Bands.SelectMany(b => b.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (var column in columnDefinitions.Where(c => !grouped.Contains(c.FieldName)))
|
foreach (var column in columnDefinitions.Where(c => !grouped.Contains(c.FieldName)))
|
||||||
BuildDataColumn(builder, ref seq, column);
|
BuildDataColumn(builder, ref seq, column);
|
||||||
|
|
||||||
foreach (var band in bandLayout.Bands)
|
foreach (var band in bandLayout.Bands)
|
||||||
{
|
{
|
||||||
if (band.Columns.Count == 0) continue;
|
if (band.Columns.Count == 0) continue;
|
||||||
|
|
||||||
builder.OpenComponent<DxGridBandColumn>(seq++);
|
builder.OpenComponent<DxGridBandColumn>(seq++);
|
||||||
builder.AddAttribute(seq++, "Caption", band.Caption);
|
builder.AddAttribute(seq++, "Caption", band.Caption);
|
||||||
builder.AddAttribute(seq++, "Columns", (RenderFragment)(bandBuilder =>
|
builder.AddAttribute(seq++, "Columns", (RenderFragment)(bandBuilder =>
|
||||||
@@ -458,14 +454,12 @@ else
|
|||||||
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
|
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (validationMessageStore == null || editContext == null) return;
|
if (validationMessageStore == null || editContext == null) return;
|
||||||
|
|
||||||
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.UpdateProcedure))
|
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.UpdateProcedure))
|
||||||
{
|
{
|
||||||
validationMessageStore.Clear();
|
validationMessageStore.Clear();
|
||||||
editContext.NotifyValidationStateChanged();
|
editContext.NotifyValidationStateChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.CustomerName))
|
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.CustomerName))
|
||||||
{
|
{
|
||||||
validationMessageStore.Clear(new FieldIdentifier(editContext.Model, nameof(MassDataEditModel.CustomerName)));
|
validationMessageStore.Clear(new FieldIdentifier(editContext.Model, nameof(MassDataEditModel.CustomerName)));
|
||||||
@@ -483,7 +477,6 @@ else
|
|||||||
SetPopupHeaderText(true);
|
SetPopupHeaderText(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var item = (MassDataReadDto)e.DataItem;
|
var item = (MassDataReadDto)e.DataItem;
|
||||||
e.EditModel = new MassDataEditModel
|
e.EditModel = new MassDataEditModel
|
||||||
{
|
{
|
||||||
@@ -505,7 +498,6 @@ else
|
|||||||
infoMessage = null;
|
infoMessage = null;
|
||||||
validationMessageStore?.Clear();
|
validationMessageStore?.Clear();
|
||||||
editContext?.NotifyValidationStateChanged();
|
editContext?.NotifyValidationStateChanged();
|
||||||
|
|
||||||
var editModel = (MassDataEditModel)e.EditModel;
|
var editModel = (MassDataEditModel)e.EditModel;
|
||||||
if (!decimal.TryParse(editModel.AmountText, out var amount))
|
if (!decimal.TryParse(editModel.AmountText, out var amount))
|
||||||
{
|
{
|
||||||
@@ -513,7 +505,6 @@ else
|
|||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editModel.IsNew)
|
if (editModel.IsNew)
|
||||||
{
|
{
|
||||||
var existing = await Api.GetByCustomerNameAsync(editModel.CustomerName);
|
var existing = await Api.GetByCustomerNameAsync(editModel.CustomerName);
|
||||||
@@ -524,7 +515,6 @@ else
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto = new MassDataWriteDto
|
var dto = new MassDataWriteDto
|
||||||
{
|
{
|
||||||
CustomerName = editModel.CustomerName,
|
CustomerName = editModel.CustomerName,
|
||||||
@@ -532,7 +522,6 @@ else
|
|||||||
Category = editModel.Category,
|
Category = editModel.Category,
|
||||||
StatusFlag = editModel.StatusFlag
|
StatusFlag = editModel.StatusFlag
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var saved = await Api.UpsertAsync(dto);
|
var saved = await Api.UpsertAsync(dto);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ html, body {
|
|||||||
.app-light {
|
.app-light {
|
||||||
--band-editor-bg: #f8f9fa;
|
--band-editor-bg: #f8f9fa;
|
||||||
--band-editor-border: #dee2e6;
|
--band-editor-border: #dee2e6;
|
||||||
|
--band-toggle-hover-bg: #e9ecef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-dark {
|
.app-dark {
|
||||||
@@ -18,6 +19,7 @@ html, body {
|
|||||||
color: #f1f1f1;
|
color: #f1f1f1;
|
||||||
--band-editor-bg: #2d2d2d;
|
--band-editor-bg: #2d2d2d;
|
||||||
--band-editor-border: #444444;
|
--band-editor-border: #444444;
|
||||||
|
--band-toggle-hover-bg: #3a3a3a;
|
||||||
}
|
}
|
||||||
|
|
||||||
a, .btn-link {
|
a, .btn-link {
|
||||||
@@ -74,15 +76,47 @@ h1:focus {
|
|||||||
|
|
||||||
/* Grid Band-Editor */
|
/* Grid Band-Editor */
|
||||||
.band-editor {
|
.band-editor {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 12px;
|
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
border: 1px solid var(--band-editor-border);
|
border: 1px solid var(--band-editor-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--band-editor-bg);
|
background-color: var(--band-editor-bg);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.band-editor-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.band-editor-toggle:hover {
|
||||||
|
background-color: var(--band-toggle-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.band-editor-toggle-icon {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.band-editor-toggle-icon.expanded {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.band-editor-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 0 12px 12px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.band-controls {
|
.band-controls {
|
||||||
|
|||||||
Reference in New Issue
Block a user