sozsoft-platform/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportGridStateHelper.cs

444 lines
15 KiB
C#
Raw Normal View History

2026-06-29 14:44:30 +00:00
using System;
using System.Collections.Generic;
using System.Data;
2026-06-29 14:44:30 +00:00
using System.Linq;
using System.Text.Json;
using Microsoft.Extensions.Localization;
using Sozsoft.Platform;
2026-06-29 14:44:30 +00:00
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.ListForms;
using Sozsoft.Platform.Localization;
2026-06-29 14:44:30 +00:00
namespace Sozsoft.Reports.PredefinedReports;
internal static class DynamicReportGridStateHelper
{
public interface IReportColumnWidth
{
ListFormField Field { get; }
bool IsImage { get; }
float ImageWidth { get; }
}
2026-06-29 14:44:30 +00:00
public static string GetFilterName(ListForm listForm, string prefix) =>
$"{prefix}-{GetStateStorageKey(listForm)}";
public static List<ListFormField> GetVisibleFields(
IReadOnlyList<ListFormField> availableFields,
string gridStateJson)
{
if (!TryGetColumnStates(gridStateJson, out var statesByField))
{
return availableFields
.Where(x => x.Visible == true)
.ToList();
}
return availableFields
.Where(field => statesByField.TryGetValue(field.FieldName, out var state)
? state.Visible
: field.Visible == true)
.OrderBy(field => statesByField.TryGetValue(field.FieldName, out var state)
? state.VisibleIndex
: int.MaxValue)
.ToList();
}
public static List<ListFormField> GetGroupedFields(
IReadOnlyList<ListFormField> availableFields,
string gridStateJson,
string groupParameter)
{
var groupedFields = new List<ListFormField>();
var groupedFieldNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var field in GetGroupFieldsFromParameter(availableFields, groupParameter))
{
AddGroupedField(field);
}
if (TryGetColumnStates(gridStateJson, out var statesByField))
{
foreach (var field in availableFields.Where(field =>
statesByField.TryGetValue(field.FieldName, out var state) &&
state.GroupIndex.HasValue)
.OrderBy(field => statesByField[field.FieldName].GroupIndex)
.ThenBy(field => statesByField[field.FieldName].VisibleIndex))
{
AddGroupedField(field);
}
}
foreach (var field in availableFields
.Select(field => new { Field = field, GroupIndex = GetConfiguredGroupIndex(field) })
.Where(x => x.GroupIndex.HasValue)
.OrderBy(x => x.GroupIndex)
.Select(x => x.Field))
{
AddGroupedField(field);
}
return groupedFields;
void AddGroupedField(ListFormField field)
{
if (!string.IsNullOrWhiteSpace(field?.FieldName) && groupedFieldNames.Add(field.FieldName))
{
groupedFields.Add(field);
}
}
}
public static Dictionary<string, float> GetColumnWidths(string gridStateJson, string columnWidthsJson = null)
{
var widths = TryGetColumnStates(gridStateJson, out var statesByField)
? statesByField
.Where(x => x.Value.Width.HasValue && x.Value.Width.Value > 0)
.ToDictionary(x => x.Key, x => x.Value.Width.Value, StringComparer.OrdinalIgnoreCase)
: new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
foreach (var item in ParseColumnWidths(columnWidthsJson))
{
widths[item.Key] = item.Value;
}
return widths;
}
public static float[] CalculateWidths<TColumn>(
IReadOnlyList<TColumn> columns,
float pageWidth,
IStringLocalizer<PlatformResource> localizer,
IReadOnlyDictionary<string, float> stateWidths = null)
where TColumn : IReportColumnWidth
{
if (columns.Count == 0)
{
return [];
}
var ideal = columns
.Select(column => column.IsImage
? column.ImageWidth
: GetPreferredColumnWidth(column.Field, localizer, stateWidths))
.ToArray();
var total = ideal.Sum();
if (total <= 0)
{
return columns.Select(_ => pageWidth / columns.Count).ToArray();
}
return ideal.Select(width => pageWidth * width / total).ToArray();
}
public static float GetPreferredColumnWidth(
ListFormField field,
IStringLocalizer<PlatformResource> localizer = null,
IReadOnlyDictionary<string, float> stateWidths = null)
{
var caption = localizer == null
? (string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName)
: DynamicReportImageHelper.GetLocalizedFieldCaption(field, localizer);
var name = field.FieldName ?? string.Empty;
var calculatedWidth = CalculateTextColumnWidth(field, caption, name);
if (!string.IsNullOrWhiteSpace(field.FieldName) &&
stateWidths?.TryGetValue(field.FieldName, out var stateWidth) == true &&
stateWidth > 0)
{
return Math.Max(stateWidth, 40F);
}
if (field.Width > 0)
{
return Math.Max(field.Width.Value, 40F);
}
return calculatedWidth;
}
2026-06-29 14:44:30 +00:00
private static string GetStateStorageKey(ListForm listForm)
{
if (!string.IsNullOrWhiteSpace(listForm.StateStoringJson))
{
try
{
using var document = JsonDocument.Parse(listForm.StateStoringJson);
if (TryGetProperty(document.RootElement, "storageKey", out var storageKey) &&
storageKey.ValueKind == JsonValueKind.String &&
!string.IsNullOrWhiteSpace(storageKey.GetString()))
{
return storageKey.GetString();
}
}
catch (JsonException)
{
// Fall back to the same default value used by GridOptionsDto.
}
}
return $"listFormGridStorage-{listForm.ListFormCode}";
}
private static bool TryGetColumnStates(
string gridStateJson,
out Dictionary<string, GridColumnState> statesByField)
{
statesByField = null;
if (string.IsNullOrWhiteSpace(gridStateJson))
{
return false;
}
try
{
using var document = JsonDocument.Parse(gridStateJson);
if (!TryGetProperty(document.RootElement, "columns", out var columns) ||
columns.ValueKind != JsonValueKind.Array)
{
return false;
}
var states = new List<GridColumnState>();
AddColumnStates(columns, true, states);
statesByField = states
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName))
.GroupBy(x => x.FieldName, StringComparer.OrdinalIgnoreCase)
.ToDictionary(x => x.Key, x => x.First(), StringComparer.OrdinalIgnoreCase);
return statesByField.Count > 0;
}
catch (JsonException)
{
return false;
}
}
private static void AddColumnStates(JsonElement columns, bool parentVisible, ICollection<GridColumnState> states)
{
foreach (var column in columns.EnumerateArray())
{
var visible = parentVisible &&
(!TryGetProperty(column, "visible", out var visibleProperty) ||
visibleProperty.ValueKind != JsonValueKind.False);
var fieldName = GetColumnFieldName(column);
var visibleIndex = TryGetProperty(column, "visibleIndex", out var visibleIndexProperty) &&
visibleIndexProperty.TryGetInt32(out var value)
? value
: states.Count;
var groupIndex = TryGetProperty(column, "groupIndex", out var groupIndexProperty) &&
groupIndexProperty.TryGetInt32(out var groupValue)
? groupValue
: (int?)null;
var width = TryGetProperty(column, "width", out var widthProperty) &&
TryGetFloat(widthProperty, out var widthValue)
? widthValue
: (float?)null;
2026-06-29 14:44:30 +00:00
if (!string.IsNullOrWhiteSpace(fieldName))
{
states.Add(new GridColumnState(fieldName, visible, visibleIndex, groupIndex, width));
2026-06-29 14:44:30 +00:00
}
if (TryGetProperty(column, "columns", out var childColumns) &&
childColumns.ValueKind == JsonValueKind.Array)
{
AddColumnStates(childColumns, visible, states);
}
}
}
private static string GetColumnFieldName(JsonElement column)
{
if (TryGetProperty(column, "dataField", out var dataField) &&
dataField.ValueKind == JsonValueKind.String)
{
return dataField.GetString();
}
if (TryGetProperty(column, "name", out var name) &&
name.ValueKind == JsonValueKind.String)
{
return name.GetString();
}
return null;
}
private static bool TryGetFloat(JsonElement element, out float value)
{
if (element.ValueKind == JsonValueKind.Number && element.TryGetSingle(out value))
{
return true;
}
if (element.ValueKind == JsonValueKind.String &&
float.TryParse(element.GetString(), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out value))
{
return true;
}
value = 0;
return false;
}
private static float CalculateTextColumnWidth(ListFormField field, string caption, string name)
{
if (field.SourceDbType == DbType.Boolean)
{
return Math.Max(75F, MeasureTextWidth(caption, 7.2F, 30F, 68F, 100F));
}
if (DynamicReportImageHelper.IsNumeric(field.SourceDbType))
{
return Math.Max(70F, MeasureTextWidth(caption, 7.2F, 30F, 68F, 120F));
}
if (field.SourceDbType is DbType.Date or DbType.DateTime or DbType.DateTime2 or DbType.DateTimeOffset)
{
return Math.Max(115F, MeasureTextWidth(caption, 7.2F, 30F, 90F, 145F));
}
return Math.Max(
MeasureTextWidth(caption, 7.2F, 30F, 85F, 220F),
MeasureTextWidth(name, 5.6F, 28F, 85F, 180F));
}
private static float MeasureTextWidth(
string value,
float characterWidth,
float padding,
float minimum,
float maximum)
{
if (string.IsNullOrWhiteSpace(value))
{
return 0;
}
return Math.Clamp(value.Trim().Length * characterWidth + padding, minimum, maximum);
}
private static Dictionary<string, float> ParseColumnWidths(string columnWidthsJson)
{
var widths = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(columnWidthsJson))
{
return widths;
}
try
{
using var document = JsonDocument.Parse(columnWidthsJson);
if (document.RootElement.ValueKind == JsonValueKind.Object)
{
foreach (var property in document.RootElement.EnumerateObject())
{
if (TryGetFloat(property.Value, out var width) && width > 0)
{
widths[property.Name] = width;
}
}
}
else if (document.RootElement.ValueKind == JsonValueKind.Array)
{
AddColumnWidths(document.RootElement, widths);
}
}
catch (JsonException)
{
}
return widths;
}
private static void AddColumnWidths(JsonElement columns, IDictionary<string, float> widths)
{
foreach (var column in columns.EnumerateArray())
{
var fieldName = GetColumnFieldName(column);
if (!string.IsNullOrWhiteSpace(fieldName) &&
TryGetProperty(column, "width", out var widthProperty) &&
TryGetFloat(widthProperty, out var width) &&
width > 0)
{
widths[fieldName] = width;
}
if (TryGetProperty(column, "columns", out var childColumns) &&
childColumns.ValueKind == JsonValueKind.Array)
{
AddColumnWidths(childColumns, widths);
}
}
}
private static List<ListFormField> GetGroupFieldsFromParameter(
IReadOnlyList<ListFormField> availableFields,
string groupParameter)
{
var result = new List<ListFormField>();
if (string.IsNullOrWhiteSpace(groupParameter))
{
return result;
}
var fieldsByName = availableFields
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName))
.ToDictionary(x => x.FieldName, StringComparer.OrdinalIgnoreCase);
foreach (var group in groupParameter.Split(
PlatformConsts.MultiValueDelimiter,
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
var parts = group.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (parts.Length > 0 && fieldsByName.TryGetValue(parts[0], out var field))
{
result.Add(field);
}
}
return result;
}
private static int? GetConfiguredGroupIndex(ListFormField field)
{
if (string.IsNullOrWhiteSpace(field.GroupingJson))
{
return null;
}
try
{
var grouping = JsonSerializer.Deserialize<ColumnGroupingDto>(field.GroupingJson, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
return grouping?.GroupIndex;
}
catch (JsonException)
{
return null;
}
}
2026-06-29 14:44:30 +00:00
private static bool TryGetProperty(JsonElement element, string name, out JsonElement value)
{
if (element.ValueKind == JsonValueKind.Object)
{
foreach (var property in element.EnumerateObject())
{
if (string.Equals(property.Name, name, StringComparison.OrdinalIgnoreCase))
{
value = property.Value;
return true;
}
}
}
value = default;
return false;
}
private sealed record GridColumnState(string FieldName, bool Visible, int VisibleIndex, int? GroupIndex, float? Width);
2026-06-29 14:44:30 +00:00
}