using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text.Json; using Microsoft.Extensions.Localization; using Sozsoft.Platform; using Sozsoft.Platform.Entities; using Sozsoft.Platform.ListForms; using Sozsoft.Platform.Localization; namespace Sozsoft.Reports.PredefinedReports; internal static class DynamicReportGridStateHelper { public interface IReportColumnWidth { ListFormField Field { get; } bool IsImage { get; } float ImageWidth { get; } } public static string GetFilterName(ListForm listForm, string prefix) => $"{prefix}-{GetStateStorageKey(listForm)}"; public static List GetVisibleFields( IReadOnlyList 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 GetGroupedFields( IReadOnlyList availableFields, string gridStateJson, string groupParameter) { var groupedFields = new List(); var groupedFieldNames = new HashSet(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 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(StringComparer.OrdinalIgnoreCase); foreach (var item in ParseColumnWidths(columnWidthsJson)) { widths[item.Key] = item.Value; } return widths; } public static float[] CalculateWidths( IReadOnlyList columns, float pageWidth, IStringLocalizer localizer, IReadOnlyDictionary 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 localizer = null, IReadOnlyDictionary 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; } 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 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(); 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 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; if (!string.IsNullOrWhiteSpace(fieldName)) { states.Add(new GridColumnState(fieldName, visible, visibleIndex, groupIndex, width)); } 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 ParseColumnWidths(string columnWidthsJson) { var widths = new Dictionary(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 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 GetGroupFieldsFromParameter( IReadOnlyList availableFields, string groupParameter) { var result = new List(); 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(field.GroupingJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); return grouping?.GroupIndex; } catch (JsonException) { return null; } } 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); }