2026-06-29 14:44:30 +00:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2026-07-04 18:13:41 +00:00
|
|
|
using System.Data;
|
2026-06-29 14:44:30 +00:00
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text.Json;
|
2026-07-04 18:13:41 +00:00
|
|
|
using Microsoft.Extensions.Localization;
|
2026-06-30 19:20:01 +00:00
|
|
|
using Sozsoft.Platform;
|
2026-06-29 14:44:30 +00:00
|
|
|
using Sozsoft.Platform.Entities;
|
2026-06-30 19:20:01 +00:00
|
|
|
using Sozsoft.Platform.ListForms;
|
2026-07-04 18:13:41 +00:00
|
|
|
using Sozsoft.Platform.Localization;
|
2026-06-29 14:44:30 +00:00
|
|
|
|
|
|
|
|
namespace Sozsoft.Reports.PredefinedReports;
|
|
|
|
|
|
|
|
|
|
internal static class DynamicReportGridStateHelper
|
|
|
|
|
{
|
2026-07-04 18:13:41 +00:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-30 19:20:01 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-04 18:13:41 +00:00
|
|
|
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;
|
2026-06-30 19:20:01 +00:00
|
|
|
var groupIndex = TryGetProperty(column, "groupIndex", out var groupIndexProperty) &&
|
|
|
|
|
groupIndexProperty.TryGetInt32(out var groupValue)
|
|
|
|
|
? groupValue
|
|
|
|
|
: (int?)null;
|
2026-07-04 18:13:41 +00:00
|
|
|
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))
|
|
|
|
|
{
|
2026-07-04 18:13:41 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-04 18:13:41 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-30 19:20:01 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-04 18:13:41 +00:00
|
|
|
private sealed record GridColumnState(string FieldName, bool Visible, int VisibleIndex, int? GroupIndex, float? Width);
|
2026-06-29 14:44:30 +00:00
|
|
|
}
|