diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Scoresheets/QuestionDto.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Scoresheets/QuestionDto.cs index 25efc3334..a3d8178b5 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Scoresheets/QuestionDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Scoresheets/QuestionDto.cs @@ -54,5 +54,10 @@ public class QuestionDto : ExtensibleEntityDto { return JsonSerializer.Deserialize(Definition ?? "{}")?.Required.ToString(); } + + public uint? GetRowsValue() + { + return JsonSerializer.Deserialize(Definition ?? "{}")?.Rows; + } } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/Services/WorksheetsManager.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/Services/WorksheetsManager.cs index 5a663dee6..f61ef2115 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/Services/WorksheetsManager.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/Services/WorksheetsManager.cs @@ -150,9 +150,9 @@ private async Task CreateNewWorksheetInstanceAsync(IWorksheet var newWorksheetInstances = new List<(Worksheet, WorksheetInstance)>(); // naming convention custom_worksheetname_fieldname - foreach (var field in eventData.CustomFields) + foreach (var (fieldName, chefsPropertyName, value) in eventData.CustomFields) { - var split = field.Key.Split('_', StringSplitOptions.RemoveEmptyEntries); + var split = fieldName.Split('_', StringSplitOptions.RemoveEmptyEntries); if (!worksheetNames.Contains(split[1])) { @@ -182,9 +182,9 @@ private async Task CreateNewWorksheetInstanceAsync(IWorksheet foreach (var field in allFields) { - var match = eventData.CustomFields.Find(s => s.Key == field.Name); + var match = eventData.CustomFields.Find(s => s.fieldName == field.Name); newInstance.AddValue(field.Id, - ValueConverter.Convert(match.Value?.ToString() ?? string.Empty, field.Type)); + ValueConverter.Convert(match.value?.ToString() ?? string.Empty, field.Type, match.chefsPropertyName, eventData.VersionData)); } var newWorksheetInstance = await worksheetInstanceRepository.InsertAsync(newInstance); diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Unity.Flex.Application.csproj b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Unity.Flex.Application.csproj index 00cfcd81d..52a9ec57c 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Unity.Flex.Application.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Unity.Flex.Application.csproj @@ -18,11 +18,6 @@ - - - - - diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ChefsToUnityTypes.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ChefsToUnityTypes.cs new file mode 100644 index 000000000..fbac4eafc --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ChefsToUnityTypes.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using Unity.Flex.Worksheets; + +namespace Unity.Flex +{ + internal static class ChefsToUnityTypes + { + internal static string Convert(string input, string defaultValue) + { + var found = TypePairs.TryGetValue(input, out string? value); + if (found) + return value ?? CustomFieldType.Text.ToString(); + else + return defaultValue ?? CustomFieldType.Text.ToString(); + } + + // Define the pairs collection + internal static readonly Dictionary TypePairs = + new() + { + { "textarea", CustomFieldType.TextArea.ToString() }, + { "orgbook", CustomFieldType.Text.ToString() }, + { "textfield", CustomFieldType.Text.ToString() }, + { "currency", CustomFieldType.Currency.ToString() }, + { "datetime", CustomFieldType.DateTime.ToString() }, + { "checkbox", CustomFieldType.Checkbox.ToString() }, + { "select", CustomFieldType.SelectList.ToString() }, + { "selectboxes", CustomFieldType.CheckboxGroup.ToString() }, + { "radio", CustomFieldType.Radio.ToString() }, + { "phoneNumber", CustomFieldType.Phone.ToString() }, + { "email", CustomFieldType.Email.ToString() }, + { "number", CustomFieldType.Numeric.ToString() }, + { "time", CustomFieldType.Text.ToString() }, + { "day", CustomFieldType.Text.ToString() }, + { "hidden", CustomFieldType.Text.ToString() }, + { "simpletextfield", CustomFieldType.Text.ToString() }, + { "simpletextfieldadvanced", CustomFieldType.Text.ToString() }, + { "simpletime", CustomFieldType.Text.ToString() }, + { "simpletimeadvanced", CustomFieldType.Text.ToString() }, + { "simplenumber", CustomFieldType.Numeric.ToString() }, + { "simplenumberadvanced", CustomFieldType.Numeric.ToString() }, + { "simplephonenumber", CustomFieldType.Phone.ToString() }, + { "simplephonenumberadvanced", CustomFieldType.Phone.ToString() }, + { "simpleselect", CustomFieldType.SelectList.ToString() }, + { "simpleselectadvanced", CustomFieldType.SelectList.ToString() }, + { "simpleday", CustomFieldType.Text.ToString() }, + { "simpledayadvanced", CustomFieldType.Text.ToString() }, + { "simpleemail", CustomFieldType.Email.ToString() }, + { "simpleemailadvanced", CustomFieldType.Email.ToString() }, + { "simpledatetime", CustomFieldType.DateTime.ToString() }, + { "simpledatetimeadvanced", CustomFieldType.DateTime.ToString() }, + { "simpleurladvanced", CustomFieldType.Text.ToString() }, + { "simplecheckbox", CustomFieldType.Checkbox.ToString() }, + { "simpleradios", CustomFieldType.Radio.ToString() }, + { "simpleradioadvanced", CustomFieldType.Radio.ToString() }, + { "simplecheckboxes", CustomFieldType.CheckboxGroup.ToString() }, + { "simplecheckboxadvanced", CustomFieldType.CheckboxGroup.ToString() }, + { "simplecurrencyadvanced", CustomFieldType.Currency.ToString() }, + { "simpletextarea", CustomFieldType.TextArea.ToString() }, + { "simpletextareaadvanced", CustomFieldType.TextArea.ToString() }, + { "bcaddress", CustomFieldType.BCAddress.ToString() }, + { "datagrid", CustomFieldType.DataGrid.ToString() } + }; + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/DynamicDataBuilder.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/DynamicDataBuilder.cs new file mode 100644 index 000000000..b7de761db --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/DynamicDataBuilder.cs @@ -0,0 +1,114 @@ +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Unity.Flex.Worksheets; +using Unity.Flex.Worksheets.Values; + +namespace Unity.Flex +{ + internal static class DynamicDataBuilder + { + internal static DataGridValue BuildDataGrid(string currentValue, string? chefsFieldName, string? versionData) + { + if (versionData == null) return new DataGridValue(); + if (chefsFieldName == null) return new DataGridValue(); + + var jObject = JObject.Parse(versionData); + if (jObject["schema"] is not JObject schemaObject) return new DataGridValue(); + + var result = FindComponents(schemaObject, chefsFieldName, "datagrid"); + + var dataGridColumns = new List(); + var dataGridRows = new List(); + + foreach (var component in result) + { + var key = component["key"]?.ToString() ?? string.Empty; + var name = component["label"]?.ToString() ?? string.Empty; + var type = ChefsToUnityTypes.Convert((component["type"]?.ToString() ?? string.Empty), CustomFieldType.Text.ToString()); + var format = ResolveFormatter(component); + + dataGridColumns.Add(new DataGridColumn(key, name, type, format)); + } + + var jsonArray = JArray.Parse(currentValue); + + foreach (var item in jsonArray) + { + var dataGridRow = new DataGridRow(); + + var props = ((JObject)item).Properties(); + + foreach (var prop in props) + { + var key = prop.Name; + var column = dataGridColumns.Find(s => s.Key == key); + if (column != null) + { + dataGridRow.Cells.Add(new DataGridRowCell(key, prop.Value.ToString())); + } + } + + dataGridRows.Add(dataGridRow); + } + + return new DataGridValue(new DataGridRowsValue(dataGridRows)) { Columns = dataGridColumns }; + } + + private static string? ResolveFormatter(JToken component) + { + // look for format, then currency as a possible formatter + var format = component["format"]?.ToString() ?? string.Empty; + if (format == string.Empty) + { + format = component["currency"]?.ToString() ?? string.Empty; + } + return format; + } + + private static JArray FindComponents(JObject jObject, string key, string type) + { + JArray foundComponents = []; + TraverseComponents(jObject, key, type, foundComponents); + return foundComponents; + } + + private static void TraverseComponents(JToken jToken, string key, string type, JArray foundComponents) + { + if (jToken is JObject obj) + { + foreach (var property in obj.Properties()) + { + if (property.Name == "components" && property.Value != null) + { + TraverseComponents(property.Value, key, type, foundComponents); + } + else if (property.Name == "key" && (property.Value?.ToString() ?? string.Empty) == key) + { + AddFoundComponents(type, foundComponents, obj); + } + } + } + else if (jToken is JArray array) + { + foreach (var token in array) + { + TraverseComponents(token, key, type, foundComponents); + } + } + } + + private static void AddFoundComponents(string type, JArray foundComponents, JObject obj) + { + if (obj != null + && obj["type"] != null + && (obj["type"]?.ToString() ?? string.Empty) == type + && obj["components"] is JArray childComponents) + { + foreach (var child in childComponents) + { + foundComponents.Add(child); + } + } + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/FlexTypeExtensions.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/FlexTypeExtensions.cs index b13393449..bcb45a30f 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/FlexTypeExtensions.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/FlexTypeExtensions.cs @@ -21,7 +21,8 @@ public static string ConvertInputType(this CustomFieldType type) CustomFieldType.Radio => "radio", CustomFieldType.Checkbox => "checkbox", CustomFieldType.CheckboxGroup => "checkbox", - CustomFieldType.Email => "email", + CustomFieldType.Email => "email", + CustomFieldType.DataGrid => "datagrid", _ => "text", }; } @@ -49,6 +50,7 @@ public static string ConvertInputType(this CustomFieldType type) CustomFieldType.SelectList => JsonSerializer.Deserialize(definition), CustomFieldType.BCAddress => JsonSerializer.Deserialize(definition), CustomFieldType.TextArea => JsonSerializer.Deserialize(definition), + CustomFieldType.DataGrid => JsonSerializer.Deserialize(definition), _ => null, }; } @@ -61,6 +63,7 @@ public static string ConvertInputType(this CustomFieldType type) QuestionType.Number => JsonSerializer.Deserialize(definition), QuestionType.YesNo => JsonSerializer.Deserialize(definition), QuestionType.SelectList => JsonSerializer.Deserialize(definition), + QuestionType.TextArea => JsonSerializer.Deserialize(definition), _ => null, }; } @@ -99,7 +102,7 @@ public static string ApplyCssClass(this CustomFieldType type) public static string? GetMinValueOrNull(this CustomFieldDefinition field) { - return DefinitionResolver.ResolveMin(field); + return DefinitionResolver.ResolveMin(field); } public static string? GetMaxValueOrNull(this CustomFieldDefinition field) @@ -131,5 +134,15 @@ public static bool GetIsRequired(this CustomFieldDefinition field) { return DefinitionResolver.ResolveIsRequired(field); } + + public static bool GetIsDynamic(this CustomFieldDefinition field) + { + return DefinitionResolver.ResolveIsDynamic(field); + } + + public static uint? GetRowsOrZero(this CustomFieldDefinition field) + { + return DefinitionResolver.ResolveRows(field) ?? 0; + } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Localization/Flex/en.json b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Localization/Flex/en.json index 3684544e9..591ee1737 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Localization/Flex/en.json +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Localization/Flex/en.json @@ -30,6 +30,7 @@ "Worksheet:Configuration:DeleteWorksheetButtonText": "Delete Worksheet", "Worksheet:Configuration:AddCheckboxOptionText": "Add Option", "Worksheet:Configuration:ExportWorksheetButtonText": "Export Worksheet", - "Worksheet:Configuration:AddSelectListOptionText": "Add Option" + "Worksheet:Configuration:AddSelectListOptionText": "Add Option", + "Worksheet:Configuration:AddColumnOptionText": "Add Column" } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Scoresheet/Enums/QuestionType.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Scoresheet/Enums/QuestionType.cs index 6671fe8ec..46a0b9185 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Scoresheet/Enums/QuestionType.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Scoresheet/Enums/QuestionType.cs @@ -6,5 +6,6 @@ public enum QuestionType Text = 2, YesNo = 6, SelectList = 12, + TextArea = 14 } } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueConverter.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueConverter.cs index 3d72ba95a..c32b59605 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueConverter.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueConverter.cs @@ -8,7 +8,7 @@ namespace Unity.Flex { public static class ValueConverter { - public static string Convert(string currentValue, CustomFieldType type) + public static string Convert(string currentValue, CustomFieldType type, string? chefsFieldName = null, string? versionData = null) { return type switch { @@ -26,6 +26,7 @@ public static string Convert(string currentValue, CustomFieldType type) CustomFieldType.SelectList => JsonSerializer.Serialize(new SelectListValue(currentValue)), CustomFieldType.BCAddress => JsonSerializer.Serialize(new BCAddressValue(currentValue)), CustomFieldType.TextArea => JsonSerializer.Serialize(new TextAreaValue(currentValue)), + CustomFieldType.DataGrid => JsonSerializer.Serialize(DynamicDataBuilder.BuildDataGrid(currentValue, chefsFieldName, versionData)), _ => throw new NotImplementedException() }; } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueConverterExtensions.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueConverterExtensions.cs index 729f5e60f..347e8a9b6 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueConverterExtensions.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueConverterExtensions.cs @@ -7,7 +7,8 @@ public static bool IsTruthy(this string? value) if (value == null || value.Equals("true", System.StringComparison.CurrentCultureIgnoreCase) || value.Equals("1", System.StringComparison.CurrentCultureIgnoreCase) - || value.Equals("on", System.StringComparison.CurrentCultureIgnoreCase)) + || value.Equals("on", System.StringComparison.CurrentCultureIgnoreCase) + || value.StartsWith("true", System.StringComparison.CurrentCultureIgnoreCase)) { return true; } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueResolver.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueResolver.cs index 16a38a43a..636a11f59 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueResolver.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/ValueResolver.cs @@ -38,6 +38,7 @@ public static class ValueResolver QuestionType.Number => ResolveNumber(currentValue), QuestionType.YesNo => JsonSerializer.Deserialize(currentValue)?.Value, QuestionType.SelectList => JsonSerializer.Deserialize(currentValue)?.Value, + QuestionType.TextArea => JsonSerializer.Deserialize(currentValue)?.Value, _ => throw new NotImplementedException() }; diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/CurrencyDefinition.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/CurrencyDefinition.cs index a566fa6bf..2c292a646 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/CurrencyDefinition.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/CurrencyDefinition.cs @@ -1,13 +1,16 @@ using System.Text.Json.Serialization; +using Unity.Flex.Worksheets.Definitions.Interfaces; namespace Unity.Flex.Worksheets.Definitions { - public class CurrencyDefinition : CustomFieldDefinition + public class CurrencyDefinition : CustomFieldDefinition, ICustomFieldFormat { [JsonPropertyName("min")] public decimal Min { get; set; } = decimal.MinValue; [JsonPropertyName("max")] public decimal Max { get; set; } = decimal.MaxValue; + + public string Format { get; set; } = string.Empty; } } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DataGridDefinition.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DataGridDefinition.cs new file mode 100644 index 000000000..368a06274 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DataGridDefinition.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Unity.Flex.Worksheets.Definitions +{ + public class DataGridDefinition : CustomFieldDefinition + { + public DataGridDefinition() : base() + { + } + + [JsonPropertyName("dynamic")] + public bool Dynamic { get; set; } + + + [JsonPropertyName("columns")] + public List Columns { get; set; } = []; + + [JsonPropertyName("summaryOption")] + public string SummaryOption { get; set; } = DataGridDefinitionSummaryOption.None.ToString(); + } + + public class DataGridDefinitionColumn + { + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; + } + + public enum DataGridDefinitionSummaryOption + { + None, + Above, + Below + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DateDefinition.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DateDefinition.cs index 79f7430c7..3d005936c 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DateDefinition.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DateDefinition.cs @@ -1,9 +1,10 @@ using System; using System.Text.Json.Serialization; +using Unity.Flex.Worksheets.Definitions.Interfaces; namespace Unity.Flex.Worksheets.Definitions { - public class DateDefinition : CustomFieldDefinition + public class DateDefinition : CustomFieldDefinition, ICustomFieldFormat { [JsonPropertyName("min")] public DateTime Min { get; set; } = DateTime.MinValue; @@ -11,8 +12,10 @@ public class DateDefinition : CustomFieldDefinition [JsonPropertyName("max")] public DateTime Max { get; set; } = DateTime.MaxValue; + public string Format { get; set; } = string.Empty; + public DateDefinition() : base() - { + { } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DateTimeDefinition.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DateTimeDefinition.cs index 98ce46d11..553de75e9 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DateTimeDefinition.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DateTimeDefinition.cs @@ -1,16 +1,7 @@ -using System; -using System.Text.Json.Serialization; - -namespace Unity.Flex.Worksheets.Definitions +namespace Unity.Flex.Worksheets.Definitions { - public class DateTimeDefinition : CustomFieldDefinition + public class DateTimeDefinition : DateDefinition { - [JsonPropertyName("min")] - public DateTime Min { get; set; } = DateTime.MinValue; - - [JsonPropertyName("max")] - public DateTime Max { get; set; } = DateTime.MaxValue; - public DateTimeDefinition() : base() { } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DefinitionResolver.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DefinitionResolver.cs index c234df21f..1c797c4bf 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DefinitionResolver.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DefinitionResolver.cs @@ -27,6 +27,7 @@ public static string Resolve(CustomFieldType type, object? definition) CustomFieldType.CheckboxGroup => JsonSerializer.Serialize(new CheckboxGroupDefinition()), CustomFieldType.SelectList => JsonSerializer.Serialize(new SelectListDefinition()), CustomFieldType.TextArea => JsonSerializer.Serialize(new TextAreaDefinition()), + CustomFieldType.DataGrid => JsonSerializer.Serialize(new DataGridDefinition()), _ => throw new NotImplementedException(), }; } @@ -49,6 +50,7 @@ public static string Resolve(CustomFieldType type, object? definition) CustomFieldType.CheckboxGroup => JsonSerializer.Serialize((CheckboxGroupDefinition)definition), CustomFieldType.SelectList => JsonSerializer.Serialize((SelectListDefinition)definition), CustomFieldType.TextArea => JsonSerializer.Serialize((TextAreaDefinition)definition), + CustomFieldType.DataGrid => JsonSerializer.Serialize((DataGridDefinition)definition), _ => throw new NotImplementedException(), }; } @@ -71,6 +73,7 @@ public static string Resolve(CustomFieldType type, object? definition) CustomFieldType.CheckboxGroup => JsonSerializer.Serialize(element.ToString()), CustomFieldType.SelectList => JsonSerializer.Serialize(element.ToString()), CustomFieldType.TextArea => JsonSerializer.Serialize(element.ToString()), + CustomFieldType.DataGrid => JsonSerializer.Serialize(element.ToString()), _ => throw new NotImplementedException(), }; } @@ -88,6 +91,7 @@ public static string Resolve(QuestionType type, object? definition) QuestionType.Text => JsonSerializer.Serialize(new TextDefinition()), QuestionType.YesNo => JsonSerializer.Serialize(new QuestionYesNoDefinition()), QuestionType.SelectList => JsonSerializer.Serialize(new QuestionSelectListDefinition()), + QuestionType.TextArea => JsonSerializer.Serialize(new TextAreaDefinition()), _ => throw new NotImplementedException(), }; } @@ -99,6 +103,7 @@ public static string Resolve(QuestionType type, object? definition) QuestionType.Text => JsonSerializer.Serialize((TextDefinition)definition), QuestionType.YesNo => JsonSerializer.Serialize((QuestionYesNoDefinition)definition), QuestionType.SelectList => JsonSerializer.Serialize((QuestionSelectListDefinition)definition), + QuestionType.TextArea => JsonSerializer.Serialize((TextAreaDefinition)definition), _ => throw new NotImplementedException(), }; } @@ -110,6 +115,7 @@ public static string Resolve(QuestionType type, object? definition) QuestionType.Text => JsonSerializer.Serialize(element.ToString()), QuestionType.YesNo => JsonSerializer.Serialize(element.ToString()), QuestionType.SelectList => JsonSerializer.Serialize(element.ToString()), + QuestionType.TextArea => JsonSerializer.Serialize(element.ToString()), _ => throw new NotImplementedException(), }; } @@ -121,6 +127,7 @@ public static string Resolve(QuestionType type, object? definition) QuestionType.Text => JsonSerializer.Serialize(definition), QuestionType.YesNo => JsonSerializer.Serialize(definition), QuestionType.SelectList => JsonSerializer.Serialize(definition), + QuestionType.TextArea => JsonSerializer.Serialize(definition), _ => throw new NotImplementedException(), }; } @@ -190,5 +197,23 @@ public static bool ResolveIsRequired(CustomFieldDefinition field) { return field.Required; } + + public static bool ResolveIsDynamic(CustomFieldDefinition field) + { + return field switch + { + DataGridDefinition dataGrid => dataGrid.Dynamic, + _ => false + }; + } + + public static uint? ResolveRows(CustomFieldDefinition field) + { + return field switch + { + TextAreaDefinition textArea => textArea.Rows, + _ => null, + }; + } } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/Interfaces/ICustomFieldFormat.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/Interfaces/ICustomFieldFormat.cs new file mode 100644 index 000000000..b7b8943df --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/Interfaces/ICustomFieldFormat.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Unity.Flex.Worksheets.Definitions.Interfaces +{ + public interface ICustomFieldFormat + { + [JsonPropertyName("format")] + public string Format { get; set; } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/TextAreaDefinition.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/TextAreaDefinition.cs index 5f38bedca..00698a2d7 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/TextAreaDefinition.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/TextAreaDefinition.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Unity.Flex.Worksheets.Definitions { @@ -11,7 +10,7 @@ public class TextAreaDefinition : CustomFieldDefinition [JsonPropertyName("maxLength")] public uint MaxLength { get; set; } = uint.MaxValue; - [JsonProperty("rows")] + [JsonPropertyName("rows")] public uint Rows { get; set; } = uint.MinValue; public TextAreaDefinition() : base() diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Enums/CustomFieldType.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Enums/CustomFieldType.cs index 79b3b28e6..cd213106d 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Enums/CustomFieldType.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Enums/CustomFieldType.cs @@ -16,6 +16,7 @@ public enum CustomFieldType CheckboxGroup = 11, SelectList = 12, BCAddress = 13, - TextArea = 14 + TextArea = 14, + DataGrid = 15 } } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Events/CreateWorksheetInstanceByFieldValuesEto.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Events/CreateWorksheetInstanceByFieldValuesEto.cs index a76278869..707f9e14b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Events/CreateWorksheetInstanceByFieldValuesEto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Events/CreateWorksheetInstanceByFieldValuesEto.cs @@ -9,8 +9,9 @@ public class CreateWorksheetInstanceByFieldValuesEto public Guid SheetCorrelationId { get; set; } public string SheetCorrelationProvider { get; set; } = string.Empty; public Guid InstanceCorrelationId { get; set; } - public string InstanceCorrelationProvider { get; set; } = string.Empty; - public List> CustomFields { get; set; } = []; + public string InstanceCorrelationProvider { get; set; } = string.Empty; + public List<(string fieldName, string chefsPropertyName, object? value)> CustomFields { get; set; } = []; public Guid? VersionId { get; set; } + public string? VersionData { get; set; } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Values/DataGridValue.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Values/DataGridValue.cs new file mode 100644 index 000000000..f7d150ec9 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Values/DataGridValue.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Unity.Flex.Worksheets.Values +{ + public class DataGridValue : CustomValueBase + { + public DataGridValue() : base() { } + public DataGridValue(object value) : base(value) { } + + [JsonPropertyName("columns")] + public List Columns { get; set; } = []; + } + + public class DataGridRowsValue + { + public DataGridRowsValue() + { + } + public DataGridRowsValue(List rows) + { + Rows = rows; + } + + [JsonPropertyName("rows")] + public List Rows { get; set; } = []; + } + + public class DataGridRow + { + [JsonPropertyName("cells")] + public List Cells { get; set; } = []; + } + + public class DataGridRowCell + { + public DataGridRowCell() + { + } + + public DataGridRowCell(string key, string value) + { + Key = key; + Value = value; + } + + [JsonPropertyName("key")] + public string Key { get; set; } = "Key"; + + [JsonPropertyName("value")] + public string Value { get; set; } = "Value"; + } + + public class DataGridColumn + { + [JsonPropertyName("key")] + public string Key { get; set; } = "Key"; + + [JsonPropertyName("name")] + public string Name { get; set; } = "Name"; + + [JsonPropertyName("type")] + public string Type { get; set; } = "Type"; + + [JsonPropertyName("format")] + public string? Format { get; set; } + + public DataGridColumn() + { + } + + public DataGridColumn(string key, string name, string type, string? format) + { + Key = key; + Name = name; + Type = type; + Format = format; + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/WorksheetConfiguration/UpsertCustomFieldModal.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/WorksheetConfiguration/UpsertCustomFieldModal.cshtml index dd6618723..e89d9c4d4 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/WorksheetConfiguration/UpsertCustomFieldModal.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/WorksheetConfiguration/UpsertCustomFieldModal.cshtml @@ -40,7 +40,7 @@ onchange="typeSelectionChanged(this)"> - +
@await Component.InvokeAsync(typeof(CustomFieldDefinitionWidget), new { type = Model.FieldType, definition = Model.Definition })
diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/WorksheetConfiguration/UpsertCustomFieldModal.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/WorksheetConfiguration/UpsertCustomFieldModal.cshtml.cs index 546b8a715..40801a17e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/WorksheetConfiguration/UpsertCustomFieldModal.cshtml.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/WorksheetConfiguration/UpsertCustomFieldModal.cshtml.cs @@ -164,7 +164,8 @@ private static List GetAvailableFieldTypes() new SelectListItem("Select List", "SelectList"), new SelectListItem("Radio", "Radio"), new SelectListItem("Yes/No Select", "YesNo"), - new SelectListItem("BC Address", "BCAddress")]; + new SelectListItem("Data Grid (Experimental)", "DataGrid"), + new SelectListItem("BC Address (Experimental)", "BCAddress")]; } public class ModalResponse : CustomFieldDto diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Unity.Flex.Web.csproj b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Unity.Flex.Web.csproj index 8929749fc..0c0a6f54b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Unity.Flex.Web.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Unity.Flex.Web.csproj @@ -47,6 +47,10 @@ + + + + @@ -102,6 +106,10 @@ Always + + + + diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/BCAddressWidget/BCAddressWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/BCAddressWidget/BCAddressWidgetController.cs index 8482db6a3..05bcf1394 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/BCAddressWidget/BCAddressWidgetController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/BCAddressWidget/BCAddressWidgetController.cs @@ -16,7 +16,7 @@ public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelNa { if (!ModelState.IsValid) { - Logger.LogWarning("Invalid model state for Refresh: {ModelName}, {FieldModel}", modelName.SanitizeField(), fieldModel); + Logger.LogWarning("Invalid model state for Refresh: {ModelName}, {FieldModel}", modelName.SanitizeField(), fieldModel?.Id.ToString()); return ViewComponent(typeof(BCAddressWidget)); } return ViewComponent(typeof(BCAddressWidget), new { fieldModel, modelName }); diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CheckboxGroupWidget/CheckboxGroupWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CheckboxGroupWidget/CheckboxGroupWidgetController.cs index a193081a2..ea9bdd318 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CheckboxGroupWidget/CheckboxGroupWidgetController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CheckboxGroupWidget/CheckboxGroupWidgetController.cs @@ -21,7 +21,6 @@ public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelNa else { return ViewComponent(typeof(CheckboxGroupWidget)); - } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CurrencyWidget/CurrencyWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CurrencyWidget/CurrencyWidgetController.cs index 0a9f3c7ee..9d177a070 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CurrencyWidget/CurrencyWidgetController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CurrencyWidget/CurrencyWidgetController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget.ViewModels; using Volo.Abp.AspNetCore.Mvc; @@ -10,8 +9,6 @@ namespace Unity.Flex.Web.Views.Shared.Components.CurrencyWidget [Route("Flex/Widgets/Currency")] public class CurrencyWidgetController : AbpController { - protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - [HttpGet] [Route("Refresh")] public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelName) @@ -19,7 +16,7 @@ public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelNa // Check if the model state is valid if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for CurrencyWidgetController"); + Logger.LogWarning("Invalid model state for CurrencyWidgetController"); return ViewComponent(typeof(CurrencyWidget)); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/CustomFieldDefinitionWidget.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/CustomFieldDefinitionWidget.cs index 1b423d98c..baf07d1f6 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/CustomFieldDefinitionWidget.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/CustomFieldDefinitionWidget.cs @@ -28,6 +28,7 @@ public class CustomFieldDefinitionWidget : AbpViewComponent CustomFieldType.Radio => RadioDefinitionWidget.RadioDefinitionWidget.ParseFormValues(form), CustomFieldType.SelectList => SelectListDefinitionWidget.SelectListDefinitionWidget.ParseFormValues(form), CustomFieldType.TextArea => TextAreaDefinitionWidget.TextAreaDefinitionWidget.ParseFormValues(form), + CustomFieldType.DataGrid => DataGridDefinitionWidget.DataGridDefinitionWidget.ParseFormValues(form), _ => null, }; } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/CustomFieldDefinitionWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/CustomFieldDefinitionWidgetController.cs index df69cb545..fb0da6b6d 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/CustomFieldDefinitionWidgetController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/CustomFieldDefinitionWidgetController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.AspNetCore.Mvc; namespace Unity.Flex.Web.Views.Shared.Components.CustomFieldDefinitionWidget @@ -9,8 +8,6 @@ namespace Unity.Flex.Web.Views.Shared.Components.CustomFieldDefinitionWidget [Route("Flex/Widgets/CustomFieldDefinition")] public class CustomFieldDefinitionWidgetController : AbpController { - protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - [HttpGet] [Route("Refresh")] public IActionResult Refresh(string type, string? definition) @@ -18,7 +15,7 @@ public IActionResult Refresh(string type, string? definition) // Check if the model state is valid if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for CustomFieldDefinitionWidgetController"); + Logger.LogWarning("Invalid model state for CustomFieldDefinitionWidgetController"); return ViewComponent(typeof(CustomFieldDefinitionWidget)); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/Default.cshtml index 2a36ae0eb..9317675fe 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/Default.cshtml @@ -2,6 +2,7 @@ @using Unity.Flex.Web.Views.Shared.Components.CheckboxGroupDefinitionWidget @using Unity.Flex.Web.Views.Shared.Components.CurrencyDefinitionWidget @using Unity.Flex.Web.Views.Shared.Components.CustomFieldDefinitionWidget; +@using Unity.Flex.Web.Views.Shared.Components.DataGridDefinitionWidget @using Unity.Flex.Web.Views.Shared.Components.NumericDefinitionWidget @using Unity.Flex.Web.Views.Shared.Components.RadioDefinitionWidget @using Unity.Flex.Web.Views.Shared.Components.SelectListDefinitionWidget @@ -47,6 +48,11 @@ @await Component.InvokeAsync(typeof(TextAreaDefinitionWidget), new { Model.Definition }) break; + case Unity.Flex.Worksheets.CustomFieldType.DataGrid: + + @await Component.InvokeAsync(typeof(DataGridDefinitionWidget), new { Model.Definition }) + + break; default: break; } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/DataGridDefinitionViewModel.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/DataGridDefinitionViewModel.cs new file mode 100644 index 000000000..135b531e9 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/DataGridDefinitionViewModel.cs @@ -0,0 +1,76 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using Unity.Flex.Worksheets; +using Unity.Flex.Worksheets.Definitions; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; + +namespace Unity.Flex.Web.Views.Shared.Components.DataGridDefinitionWidget +{ + public class DataGridDefinitionViewModel : WorksheetFieldDefinitionViewModelBase + { + [DisplayName("Dynamic")] + public bool Dynamic { get; set; } + + public List Columns { get; set; } = []; + + + private readonly List _supportedFieldTypes; + public List SupportedFieldTypes { get { return _supportedFieldTypes; } } + + private readonly List _summaryOptions; + public List SummaryOptions { get { return _summaryOptions; } } + + + [SelectItems(nameof(SummaryOptions))] + [DisplayName("Summary Option")] + public string SummaryOption { get; set; } = DataGridDefinitionSummaryOption.None.ToString(); + + + [BindProperty] + public string SupportedFieldsList => GetSupportedFieldsList(); + + private string GetSupportedFieldsList() + { + return string.Join(",", _supportedFieldTypes.Select(s => s.Text)); + } + + public DataGridDefinitionViewModel() : base() + { + _supportedFieldTypes = + [ + AddGridSupportedCustomFieldType(CustomFieldType.Text), + AddGridSupportedCustomFieldType(CustomFieldType.TextArea), + AddGridSupportedCustomFieldType(CustomFieldType.Currency), + AddGridSupportedCustomFieldType(CustomFieldType.Numeric), + AddGridSupportedCustomFieldType(CustomFieldType.Date), + AddGridSupportedCustomFieldType(CustomFieldType.DateTime), + AddGridSupportedCustomFieldType(CustomFieldType.YesNo), + AddGridSupportedCustomFieldType(CustomFieldType.Checkbox), + AddGridSupportedCustomFieldType(CustomFieldType.Phone), + AddGridSupportedCustomFieldType(CustomFieldType.Email) + ]; + + _summaryOptions = + [ + AddSummaryOption(DataGridDefinitionSummaryOption.None), + AddSummaryOption(DataGridDefinitionSummaryOption.Above), + AddSummaryOption(DataGridDefinitionSummaryOption.Below) + ]; + } + + private static SelectListItem AddSummaryOption(DataGridDefinitionSummaryOption type) + { + var typeStr = type.ToString(); + return new SelectListItem(typeStr, typeStr); + } + + private static SelectListItem AddGridSupportedCustomFieldType(CustomFieldType type) + { + var typeStr = type.ToString(); + return new SelectListItem(typeStr, typeStr); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/DataGridDefinitionWidget.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/DataGridDefinitionWidget.cs new file mode 100644 index 000000000..1720cf09a --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/DataGridDefinitionWidget.cs @@ -0,0 +1,129 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Primitives; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Unity.Flex.Worksheets.Definitions; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Widgets; + +namespace Unity.Flex.Web.Views.Shared.Components.DataGridDefinitionWidget +{ + [ViewComponent(Name = "DataGridDefinitionWidget")] + [Widget(RefreshUrl = "../Flex/Widgets/DataGridDefinition/Refresh", + ScriptTypes = [typeof(DataGridDefinitionWidgetScriptBundleContributor)], + StyleTypes = [typeof(DataGridDefinitionWidgetStyleBundleContributor)], + AutoInitialize = true)] + public partial class DataGridDefinitionWidget : AbpViewComponent + { + const string validInputPattern = @"^[ə̀ə̩ə̥ɛæə̌ə̂ə̧ə̕ə̓ᵒə̄ə̱·ʷəŧⱦʸʋɨⱡɫʔʕⱥɬθᶿɣɔɩłə̈ʼə̲ᶻꭓȼƛλŋƚə̨ə̣ə́ `1234567890-=qwertyuiop[]asdfghjkl;_'_\\zxcvbnm,.~!@#$%^&*()_+QWERTYUIOP{}ASDFGHJKL:""||ZXCVBNM<>?]+$"; + + [GeneratedRegex(validInputPattern)] + protected static partial Regex ValidColumnNames(); + + internal static object? ParseFormValues(IFormCollection form) + { + var dataGridDefinition = new DataGridDefinition(); + + var dynamic = form["Dynamic"].ToString().IsTruthy(); + var summaryOption = form["SummaryOption"].ToString(); + var columnKeys = form["ColumnKeys"]; + var columnTypes = form["ColumnTypes"]; + + dataGridDefinition.Dynamic = dynamic; + dataGridDefinition.SummaryOption = summaryOption; + dataGridDefinition.Columns = ValidateAndGenerateColumns(dynamic, columnKeys, columnTypes); + + return dataGridDefinition; + } + + private static List ValidateAndGenerateColumns(bool dynamic, StringValues columnKeys, StringValues columnTypes) + { + var dataGridsDefinitionColumns = new List(); + + if (!dynamic && (columnKeys.Count == 0 || columnTypes.Count == 0)) + { + throw new UserFriendlyException($"Columns are required for non-dynamic table"); + } + + if (columnKeys.Distinct().Count() != columnKeys.Count) + { + throw new UserFriendlyException("Column names must be unique"); + } + + var indx = 0; + foreach (var column in columnKeys) + { + if (string.IsNullOrWhiteSpace(column)) + { + throw new UserFriendlyException("There are empty column names captured which are required"); + } + + if (!IsValidInput(column)) + { + throw new UserFriendlyException("The following characters are allowed for Keys: " + validInputPattern); + } + + dataGridsDefinitionColumns.Add(new DataGridDefinitionColumn() { Name = column, Type = columnTypes[indx] ?? "Invalid" }); + + indx++; + } + + return dataGridsDefinitionColumns; + } + + protected static bool IsValidInput(string input) + { + Regex regex = ValidColumnNames(); + return regex.IsMatch(input); + } + + public async Task InvokeAsync(string? definition) + { + if (definition != null) + { + DataGridDefinition? dataGridDefinition = JsonSerializer.Deserialize(definition); + if (dataGridDefinition != null) + { + return View(await Task.FromResult(new DataGridDefinitionViewModel() + { + Dynamic = dataGridDefinition.Dynamic, + Columns = dataGridDefinition.Columns, + Type = Flex.Worksheets.CustomFieldType.DataGrid, + Definition = definition, + SummaryOption = dataGridDefinition.SummaryOption + })); + } + } + + return View(await Task.FromResult(new DataGridDefinitionViewModel())); + } + } + + public class DataGridDefinitionWidgetStyleBundleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files + .AddIfNotContains("/Views/Shared/Components/DataGridDefinitionWidget/Default.css"); + } + } + + public class DataGridDefinitionWidgetScriptBundleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files + .AddIfNotContains("/Views/Shared/Components/Common/KeyValueComponents.js"); + + context.Files + .AddIfNotContains("/Views/Shared/Components/DataGridDefinitionWidget/Default.js"); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/DataGridDefinitionWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/DataGridDefinitionWidgetController.cs new file mode 100644 index 000000000..e7d20c767 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/DataGridDefinitionWidgetController.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace Unity.Flex.Web.Views.Shared.Components.DataGridDefinitionWidget +{ + [ApiExplorerSettings(IgnoreApi = true)] + [Route("Flex/Widgets/DataGridDefinition")] + public class DataGridDefinitionWidgetController : AbpController + { + [HttpGet] + [Route("Refresh")] + public IActionResult Refresh() + { + return ViewComponent(typeof(DataGridDefinitionWidget)); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/Default.cshtml new file mode 100644 index 000000000..f5aea8fcc --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/Default.cshtml @@ -0,0 +1,80 @@ +@using Microsoft.AspNetCore.Mvc.Localization +@using Unity.Flex.Localization +@using Unity.Flex.Web.Views.Shared.Components.DataGridDefinitionWidget; + +@inject IHtmlLocalizer L + +@model DataGridDefinitionViewModel; + + + + +
Mapping:
+
+ +
Summary:
+ + +
Columns:
+ + + + + + + + + + + + @foreach (var option in Model.Columns) + { + + + + + + } + +
NameType
+ +
+ + + + + + + + + + + + + + +
+ +
+
+ \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/Default.css new file mode 100644 index 000000000..267f424e7 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/Default.css @@ -0,0 +1,97 @@ +.column-options-actions-column { + width: 1rem; +} + +.column-options-list { + max-height: 200px; + overflow: auto; +} + + .column-options-list table { + width: 100%; + } + +#add-new-column-table { + margin-top: 1rem; + margin-left: 1rem; + border-radius: 5px; +} + + #add-new-column-table td { + margin: 5px; + border-radius: 0px !important; + } + +.new-row-actions { + display: flex; + gap: 4px; +} + +.column-options-edit-controls { + margin-top: 1rem; +} + +.column-input-valid { + border: 1px solid red !important; +} + +#column-options-table { + overflow-y: auto; + width: calc(100% - 0.5rem); + margin-left: 0.5rem; +} + + #column-options-table thead { + position: sticky; + top: 0; + height: 2rem; + color: #5d5d5d; + background-color: #fff; + } + + #column-options-table th { + padding: 5px; + } + +#new-column-row td:first-child { + padding-left: 10px; +} + +#invalid-input-error-summary { + color: var(--bs-red, red); + text-align: center; +} + +.half-width { + width: 50%; +} + +.summary-options-layout { + display: flex; + flex-wrap: wrap; + align-content: center; + justify-content: flex-start; + align-items: center; + gap: 1rem; +} + +.definition-widget-section-title { + margin-top: 1rem; +} + +.dynamic-column-info { + border-color: var(--bc-colors-blue-primary); + text-align: center; + background-color: var(--bc-colors-blue-hover); + border-radius: 5px; + font-size: 0.9rem; + margin-top: 0.5rem; +} + +.check-layout-wrap { + margin-left: 0.75px; +} + +#column-options-body > tr > td > div { + margin-bottom: 0 !important; +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/Default.js b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/Default.js new file mode 100644 index 000000000..1394414a1 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridDefinitionWidget/Default.js @@ -0,0 +1,168 @@ +$(function () { + let addcolumnOption; + let columnOptionsTable; + let newRowControl = 'new-column-row'; + let deleteClass = 'delete-column-option'; + let newColumnTable = 'add-new-column-table'; + let validityClass = 'column-input-valid'; + + function bindRootActions() { + addcolumnOption = $('#add-column-option-btn'); + + bindNewColumnOption(newColumnTable, addcolumnOption, 'column'); + bindSaveOption(); + bindCancelOption(); + bindNewRowKeyCheck(newRowControl); + } + + function bindNewColumnOption(newItemTable, option, keyStart) { + let newRowTable = $('#' + newItemTable); + + if (option) { + option.on('click', function (event) { + if (newRowTable) { + newRowTable.toggleClass('hidden'); + $('#new-row-key').val(keyStart + ($('.key-input')?.toArray()?.length + 1)); + $('#new-row-label').focus(); + option.toggleClass('hidden'); + } + }); + } + } + + function bindNewRowKeyCheck(newRowControl) { + let newKey = $('#new-row-key'); + + newKey.on('change', function (event) { + let valid = validateColumnName(newRowControl); + if (valid === true) { + clearSummaryError(); + } + }); + } + + function bindSaveOption() { + let save = $('#save-column-option-btn'); + if (save) { + save.on('click', function (event) { + let row = getNewColumnRow(newRowControl); + + if (!validateColumnName(newRowControl)) + return; + + // Add valid row to table + $('#column-options-table').find('tbody') + .append(getRowTemplate(row.key, row.label)); + + cancelAddColumn(); + + // bind actions only for last item added + bindNewRowActions(); + bindNewRowInputChanges(); + }); + } + } + + function getNewColumnRow(controlName) { + let newRow = $('#' + controlName); + let columnName = newRow.find('input'); + let columnType = newRow.find('select'); + let row = {}; + + row.key = columnName[0].value; + row.label = columnType[0].value; + + return row; + } + + function validateColumnName(controlName) { + let row = getNewColumnRow(controlName); + + // check against existing rows + let existingRows = $('.key-input').toArray(); + let existing = existingRows.find(o => o.value.toLowerCase() == row.key.toLowerCase()); + + if (existing) { + addSummaryError('Duplicate Column names are not allowed'); + return false; + } + + if (isEmptyOrSpaces(row.key)) { + addSummaryError('You must provide a Column name'); + return false; + } + + return true; + } + + function getRowTemplate(name, type) { + return ` + + ` + } + + function generateOptions(type) { + let options = ($('#SupportedFieldsList').val()).split(','); + let selectOptions = ''; + options.forEach((element) => selectOptions += ``); + return selectOptions; + } + + function bindCancelOption() { + let cancel = $('#cancel-column-option-btn'); + if (cancel) { + cancel.on('click', function () { + let newRowTable = $('#add-new-column-table'); + if (newRowTable) { + cancelAddColumn(); + } + }); + } + } + + function cancelAddColumn() { + let newColumnTable = $('#add-new-column-table'); + + $('#new-row-key').val(''); + $('#new-row-label').val(''); + + $('#new-row-label').blur(); + newColumnTable.toggleClass('hidden'); + addcolumnOption.toggleClass('hidden'); + clearSummaryError(); + } + + function bindNewRowActions() { + // get last row of datatable + let lastRow = columnOptionsTable.rows[columnOptionsTable.rows.length - 1]; + let deleteBtn = $(lastRow).find('.delete-column-option'); + if (deleteBtn) { + bindDeleteAction(deleteBtn, columnOptionsTable); + } + } + + function bindNewRowInputChanges() { + // get last row of datatable + let lastRow = columnOptionsTable.rows[columnOptionsTable.rows.length - 1]; + let keyInput = $(lastRow).find('.key-input'); + if (keyInput) { + bindInputChanges(keyInput, validityClass); + } + } + + function init() { + columnOptionsTable = document.getElementById('column-options-table'); + bindRootActions(); + bindRowActions(deleteClass, columnOptionsTable); + bindInputChanges($('.key-input'), validityClass); + } + + PubSub.subscribe( + 'datagrid_widget_fired', + () => { + init(); + } + ); +}); + diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/DataGridViewModel.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/DataGridViewModel.cs new file mode 100644 index 000000000..5478b2c04 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/DataGridViewModel.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Unity.Flex.Worksheets.Definitions; + +namespace Unity.Flex.Web.Views.Shared.Components.DataGridWidget +{ + public class DataGridViewModel : WorksheetViewModelBase + { + public bool AllowEdit { get; set; } + public string[] Columns { get; set; } = []; + public string TableOptions { get; set; } = string.Empty; + public DataGridViewModelRow[] Rows { get; set; } = []; + public DataGridDefinitionSummaryOption SummaryOption { get; set; } + public DataGridViewSummary Summary { get; set; } = new DataGridViewSummary(); + + public DataGridViewModel() : base() + { + } + } + + public class DataGridViewModelRow + { + public string Id { get; set; } = Guid.Empty.ToString(); + public List Cells { get; set; } = []; + } + + public class DataGridViewModelCell + { + public string Key { get; set; } = string.Empty; + public string Value { get; set; } = string.Empty; + } + + public class DataGridViewSummary + { + public List Fields { get; set; } = []; + } + + public class DataGridViewModelSummaryField + { + public string Label { get; set; } = string.Empty; + public string Key { get; set; } = string.Empty; + public string Value { get; set; } = string.Empty; + } +} + + diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/DataGridWidget.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/DataGridWidget.cs new file mode 100644 index 000000000..6b44c74ec --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/DataGridWidget.cs @@ -0,0 +1,427 @@ +using Microsoft.AspNetCore.Mvc; +using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget.ViewModels; +using Volo.Abp.AspNetCore.Mvc.UI.Widgets; +using Volo.Abp.AspNetCore.Mvc; +using System.Text.Json; +using Unity.Flex.Worksheets.Values; +using System.Linq; +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using System.Globalization; +using System; +using Unity.Flex.Worksheets.Definitions; +using Unity.Flex.Worksheets; +using System.Text; + +namespace Unity.Flex.Web.Views.Shared.Components.DataGridWidget +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "TODO - invalid cast types when implemented")] + [ViewComponent(Name = "DataGridWidget")] + [Widget(RefreshUrl = "../Flex/Widgets/DataGrid/Refresh", + ScriptTypes = [typeof(DataGridWidgetScriptBundleContributor)], + StyleTypes = [typeof(DataGridWidgetStyleBundleContributor)], + AutoInitialize = true)] + public class DataGridWidget : AbpViewComponent + { + private const string _dynamicLabel = "Dynamic"; + private const string _summaryLabelprefix = "Total:"; + private static readonly List _validTotalSummaryTypes = [CustomFieldType.Numeric, CustomFieldType.Currency]; + + public IViewComponentResult Invoke(WorksheetFieldViewModel? fieldModel, string modelName) + { + if (fieldModel == null) return View(new DataGridViewModel()); + var dataGridValue = JsonSerializer.Deserialize(fieldModel.CurrentValue ?? "{}"); + var dataGridDefinition = (DataGridDefinition?)fieldModel.Definition?.ConvertDefinition(fieldModel.Type); + + // No definition, so nothing to display + if (dataGridDefinition == null) return View(new DataGridViewModel() { Field = fieldModel }); + + + if (dataGridValue?.Value == null && fieldModel.UiAnchor == "Preview") + { + return GeneratePreview(fieldModel, dataGridDefinition); + } + else + { + return GenerateView(fieldModel, modelName, dataGridDefinition); + } + } + + private static bool IsDynamicWithNoColumns(DataGridDefinition dataGridDefinition) + { + return dataGridDefinition.Dynamic && dataGridDefinition.Columns.Count == 0; + } + + private static bool IsDynamicWithColumns(DataGridDefinition dataGridDefinition) + { + return dataGridDefinition.Dynamic && dataGridDefinition.Columns.Count > 0; + } + + private static bool IsNotDynamicWithColumns(DataGridDefinition dataGridDefinition) + { + return !dataGridDefinition.Dynamic && dataGridDefinition.Columns.Count > 0; + } + + private IViewComponentResult GenerateView(WorksheetFieldViewModel fieldModel, string modelName, DataGridDefinition dataGridDefinition) + { + var dataGridValue = JsonSerializer.Deserialize(fieldModel.CurrentValue ?? "{}"); + DataGridRowsValue? dataGridRowsValue = null; + + if (dataGridValue != null && dataGridValue.Value != null && dataGridValue.Value.ToString() != null) + { + dataGridRowsValue = JsonSerializer.Deserialize(dataGridValue.Value.ToString() ?? string.Empty); + } + + return GenerateGridView(fieldModel, modelName, dataGridValue, dataGridRowsValue, dataGridDefinition); + } + + private IViewComponentResult GenerateGridView(WorksheetFieldViewModel fieldModel, + string modelName, + DataGridValue? dataGridValue, + DataGridRowsValue? dataGridRowsValue, + DataGridDefinition dataGridDefinition) + { + var dataColumns = GenerateDataColumns(dataGridValue, dataGridDefinition); + var dataRows = GenerateDataRows(dataColumns, dataGridRowsValue); + var columnNames = dataColumns.Select(s => s.Name); + + var viewModel = new DataGridViewModel() + { + Field = fieldModel, + Name = modelName, + Columns = [.. columnNames], + Rows = [.. dataRows], + AllowEdit = true, + SummaryOption = ConvertSummaryOption(dataGridDefinition), + Summary = GenerateSummary([.. dataColumns], [.. dataRows]), + TableOptions = GenerateAvailableTableOptions(!dataGridDefinition.Dynamic) + }; + + return View(viewModel); + } + + private static string GenerateAvailableTableOptions(bool allowAdd) + { + var stringBuilder = new StringBuilder(); + stringBuilder.Append("ExportData"); + if (allowAdd) + { + stringBuilder.Append(",AddRecord"); + } + return stringBuilder.ToString(); + } + + private static List GenerateDataColumns(DataGridValue? dataGridValue, DataGridDefinition dataGridDefinition) + { + // Predefined column definitions + List dataColumns = dataGridValue?.Columns ?? []; + foreach (var dataColumn in dataGridDefinition?.Columns ?? []) + { + dataColumns.Add(new DataGridColumn() + { + Key = dataColumn.Name, + Name = dataColumn.Name, + Type = dataColumn.Type + }); + } + return dataColumns; + } + + private static List GenerateDataRows(List columns, DataGridRowsValue? dataGridRowsValue) + { + if (dataGridRowsValue == null) return []; + List rows = []; + + foreach (var row in dataGridRowsValue.Rows) + { + List cells = []; + + foreach (var column in columns) + { + var fieldMatch = row.Cells.Find(s => s.Key == column.Key); + if (fieldMatch != null) + { + cells.Add(new DataGridViewModelCell() + { + Key = fieldMatch.Key, + Value = fieldMatch.Value.ApplyFormatting(column.Type, column.Format) + }); + } + else + { + cells.Add(new DataGridViewModelCell() + { + Key = column.Key, + Value = string.Empty + }); + } + } + + rows.Add(new DataGridViewModelRow() + { + Cells = cells + }); + } + + return rows; + } + + private IViewComponentResult GeneratePreview(WorksheetFieldViewModel fieldModel, DataGridDefinition dataGridDefinition) + { + if (IsDynamicWithNoColumns(dataGridDefinition)) + { + return GenerateDynamicWithNoColumnsPreview(fieldModel, dataGridDefinition); + } + + if (IsDynamicWithColumns(dataGridDefinition)) + { + return GenerateDynamicWithColumnsPreview(fieldModel, dataGridDefinition); + } + + if (IsNotDynamicWithColumns(dataGridDefinition)) + { + return GenerateNonDynamicWithColumnsPreview(fieldModel, dataGridDefinition); + } + + return View(new DataGridViewModel()); + } + + private IViewComponentResult GenerateNonDynamicWithColumnsPreview(WorksheetFieldViewModel fieldModel, DataGridDefinition dataGridDefinition) + { + var summary = GenerateEmptySummary(dataGridDefinition.Columns, dataGridDefinition.Dynamic); + + var configuredColumns = dataGridDefinition.Columns.Select(s => s.Name).ToList(); + var columnsToRender = configuredColumns; + + var previewRows = GeneratePreviewRows(dataGridDefinition.Columns, dataGridDefinition.Dynamic); + + return View(new DataGridViewModel() + { + Columns = [.. columnsToRender], + Summary = summary, + Rows = [.. previewRows], + SummaryOption = ConvertSummaryOption(dataGridDefinition), + Field = fieldModel, + AllowEdit = true, + TableOptions = GenerateAvailableTableOptions(true) + }); + } + + private IViewComponentResult GenerateDynamicWithColumnsPreview(WorksheetFieldViewModel fieldModel, DataGridDefinition dataGridDefinition) + { + var summary = GenerateEmptySummary(dataGridDefinition.Columns, dataGridDefinition.Dynamic); + + var columnsToRender = GenerateDynamicPlaceholderColumn(); + var configuredColumns = dataGridDefinition.Columns.Select(s => s.Name).ToList(); + columnsToRender.AddRange(configuredColumns); + + var previewRows = GeneratePreviewRows(dataGridDefinition.Columns, dataGridDefinition.Dynamic); + + return View(new DataGridViewModel() + { + Columns = [.. columnsToRender], + Summary = summary, + Rows = [.. previewRows], + SummaryOption = ConvertSummaryOption(dataGridDefinition), + Field = fieldModel, + AllowEdit = true, + TableOptions = GenerateAvailableTableOptions(false) + }); + } + + private IViewComponentResult GenerateDynamicWithNoColumnsPreview(WorksheetFieldViewModel fieldModel, DataGridDefinition dataGridDefinition) + { + return View(new DataGridViewModel() + { + Columns = [.. GenerateDynamicPlaceholderColumn()], + Rows = [.. GenerateDynamicRowPlaceholder()], + Summary = GenerateDynamicPlaceholderSummary(), + SummaryOption = ConvertSummaryOption(dataGridDefinition), + Field = fieldModel, + AllowEdit = false, + TableOptions = GenerateAvailableTableOptions(false) + }); + } + + private static DataGridViewSummary GenerateEmptySummary(List columns, bool dynamic) + { + var summary = new DataGridViewSummary(); + + if (dynamic) + { + summary = GenerateDynamicPlaceholderSummary(); + } + + foreach (var column in columns.Where(s => IsTotalPossibleType(s.Type))) + { + summary.Fields.Add(new DataGridViewModelSummaryField() + { + Key = column.Name, + Label = $"{_summaryLabelprefix} {column.Name}", + Value = column.Type + }); + } + + return summary; + } + + private static bool IsTotalPossibleType(string type) + { + var validType = Enum.TryParse(type, out CustomFieldType customFieldType); + if (!validType) return false; + if (_validTotalSummaryTypes.Contains(customFieldType)) { return true; } + return false; + } + + private static List GeneratePreviewRows(List columnsToRender, bool includeDynamic) + { + var cells = new List(); + if (includeDynamic) + { + cells.Add(new DataGridViewModelCell() { Key = _dynamicLabel, Value = _dynamicLabel }); + } + foreach (var column in columnsToRender) + { + cells.Add(new DataGridViewModelCell() { Key = column.Name, Value = column.Type }); + } + return + [ + new() + { + Cells = cells, + Id = Guid.NewGuid().ToString() + } + ]; + } + + private static List GenerateDynamicRowPlaceholder() + { + return + [ + new() + { + Cells = [new DataGridViewModelCell() { Key = _dynamicLabel, Value = _dynamicLabel }], + Id = Guid.NewGuid().ToString() + } + ]; + } + + private static DataGridDefinitionSummaryOption ConvertSummaryOption(DataGridDefinition dataGridDefinition) + { + return (DataGridDefinitionSummaryOption)Enum.Parse(typeof(DataGridDefinitionSummaryOption), dataGridDefinition.SummaryOption); + } + + private static DataGridViewSummary GenerateDynamicPlaceholderSummary() + { + return new DataGridViewSummary() + { + Fields = + [ + new() + { + Key = _dynamicLabel, + Label = $"{_summaryLabelprefix} {_dynamicLabel}", + Value = _dynamicLabel + } + ] + }; + } + + private static List GenerateDynamicPlaceholderColumn() + { + return [_dynamicLabel]; + } + + private static DataGridViewSummary GenerateSummary(DataGridColumn[]? dataColumns, DataGridViewModelRow[] rows) + { + var summary = new DataGridViewSummary(); + + foreach (var field in dataColumns?.Where(s => IsTotalPossibleType(s.Type)) ?? []) + { + summary.Fields.Add(new DataGridViewModelSummaryField() + { + Key = field.Key, + Value = SumCells(field.Key, rows), + Label = $"{_summaryLabelprefix} {field.Name}" + }); + } + + return summary; + } + + private static string SumCells(string? key, DataGridViewModelRow[] rows) + { + decimal sum = 0; + foreach (var row in rows) + { + var cell = row.Cells.Find(x => x.Key == key); + if (cell != null) + { + sum += decimal.Parse(cell.Value.Replace("$", "").Replace(",", "")); + } + } + return sum.ToString(); + } + } + + public class DataGridWidgetScriptBundleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files + .AddIfNotContains("/Views/Shared/Components/DataGridWidget/Default.js"); + } + } + + public class DataGridWidgetStyleBundleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files + .AddIfNotContains("/Views/Shared/Components/DataGridWidget/Default.css"); + } + } + + public static class DataGridExtensions + { + public static string ApplyFormatting(this string value, string columnType, string? format) + { + if (format == null) { return value; } + + // We can format date fields based on a format + if ((columnType == CustomFieldType.Date.ToString() || columnType == CustomFieldType.DateTime.ToString()) + && value != null + && DateTime.TryParse(value, new CultureInfo("en-CA"), out DateTime dateTime)) + { + var appliedFormat = !string.IsNullOrEmpty(format) ? format : "MM-dd-yyyy"; // Date vs DateTime? + string formattedDateTime = dateTime.ToString(appliedFormat, CultureInfo.InvariantCulture); + return formattedDateTime; + } + + // We format currency fields + if (columnType == "Currency" && value != null && decimal.TryParse(value, out decimal number)) + { + var currencyCode = !string.IsNullOrEmpty(format) ? format : "CAD"; + var culture = GetCultureInfoByCurrencyCode(currencyCode); + string formattedNumber = number.ToString("C", culture); + return formattedNumber; + } + + return value ?? string.Empty; + } + + static CultureInfo GetCultureInfoByCurrencyCode(string currencyCode) + { + foreach (var culture in CultureInfo.GetCultures(CultureTypes.SpecificCultures)) + { + var region = new RegionInfo(culture.Name); + if (region.ISOCurrencySymbol == currencyCode) + { + return culture; + } + } + + throw new ArgumentException("Invalid or unsupported currency code."); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/DataGridWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/DataGridWidgetController.cs new file mode 100644 index 000000000..bd7b9a59e --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/DataGridWidgetController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget.ViewModels; +using Volo.Abp.AspNetCore.Mvc; + +namespace Unity.Flex.Web.Views.Shared.Components.DataGridWidget +{ + [ApiExplorerSettings(IgnoreApi = true)] + [Route("Flex/Widgets/DataGrid")] + public class DataGridWidgetController : AbpController + { + [HttpGet] + [Route("Refresh")] + public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelName) + { + // Check if the model state is valid + if (!ModelState.IsValid) + { + Logger.LogWarning("Invalid model state for DataGridWidget:Refresh"); + return ViewComponent(typeof(DataGridWidget)); + } + + // If the model state is valid, render the view component + return ViewComponent(typeof(DataGridWidget), new { fieldModel, modelName }); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.cshtml new file mode 100644 index 000000000..72082f0d2 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.cshtml @@ -0,0 +1,73 @@ +@using Unity.Flex.Web.Views.Shared.Components.DataGridWidget; + +@model DataGridViewModel; + +@if (Model.Field != null) +{ + +
+ @if (Model.SummaryOption != Unity.Flex.Worksheets.Definitions.DataGridDefinitionSummaryOption.None) + { + +
+ @foreach (var field in Model.Summary.Fields) + { +
+ + +
+ } +
+
+ } + + +
+
+ +
+
+
+
+ + + + + @foreach (var column in Model.Columns) + { + + } + @if (Model.AllowEdit) + { + + } + + + + @foreach (var row in Model.Rows) + { + + @foreach (var cell in row.Cells) + { + + } + @if (Model.AllowEdit) + { + + } + + } + +
@column
+ @cell.Value + + +
+
+
+} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.css new file mode 100644 index 000000000..9c3ce5fc4 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.css @@ -0,0 +1,39 @@ +.custom-grid-action-bar { + display: flex; + justify-content: space-between; +} + +.custom-dynamic-table.custom-table-edit th:last-child { + width: 20px !important; + max-width: 20px !important; +} + +.dynamic-grid-summary { + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: 8px; +} + + .dynamic-grid-summary .form-group { + width: 45%; + } + +.custom-grid-container { + display: flex; + flex-direction: column; +} + +.grid-position { + order: 1; +} + +.grid-position-above { + order: 0; +} + +.grid-position-below { + order: 2; +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.js b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.js new file mode 100644 index 000000000..3ca4c39a6 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.js @@ -0,0 +1,100 @@ +$(function () { + const UIElements = { + editRowBtns: $('button.row-edit-btn'), + tables: $('.custom-dynamic-table'), + tableSearches: $('.custom-tbl-search') + }; + + let actionButtons = [ + { + id: 'AddRecord', + text: 'Add', + title: 'Add Record', + className: 'custom-table-btn flex-none btn btn-secondary' + }, + { + id: 'ExportData', + extend: 'csv', + text: 'Export', + title: 'Data Export', + className: 'custom-table-btn flex-none btn btn-secondary', + exportOptions: { + columns: ':visible:not(.notexport)', + orthogonal: 'fullName', + } + }, + ]; + + init(); + + function init() { + buildDataTables(UIElements.tables); + bindUIEvents(); + } + + function buildDataTables(tables) { + tables.each(function () { + let $element = $(this); + let table = $(this).DataTable({ + paging: false, + bInfo: false, + searching: true, + serverside: false, + info: false, + lengthChange: false, + dom: 'Bftip', + buttons: configureButtons($element[0].id), + order: [[0, 'desc']] + }); + configureTable(table, $element[0].id); + }); + } + + function configureButtons(fieldId) { + let options = ($(`#table-options-${fieldId}`).val()).split(','); + let availableOptions = actionButtons.filter(item => options.includes(item.id)); + return availableOptions; + } + + function configureTable(table, fieldId) { + table.buttons().container().prependTo(`#btn-container-${fieldId}`); + let knownColumns = []; + table.columns().header().to$().each(function (index) { + if ($(this).text() != '') { + knownColumns.push({ title: $(this).text(), visible: true, index: index + 1 }); + } + }); + + table.button().add(actionButtons.length + 1, { + text: 'Columns', + extend: 'collection', + buttons: getColumnToggleButtonsSorted(knownColumns, table), + className: 'custom-table-btn flex-none btn btn-secondary' + }); + } + + function bindUIEvents() { + UIElements.editRowBtns.on('click', handleEditRowClick); + UIElements.tableSearches.on('keyup', function () { + let table = $(`#${this.dataset.tableId}`).DataTable(); + table.search(this.value).draw(); + }); + + UIElements.tableSearches.on('search', function () { + let table = $(`#${this.dataset.tableId}`).DataTable(); + table.search('').draw(); + }); + } + + function handleEditRowClick(e) { + console.info('edit tbd'); + } + + PubSub.subscribe( + 'worksheet_preview_datagrid_refresh', + () => { + // refresh the dom elements binding and init the datagrid view + buildDataTables($('.custom-dynamic-table')); + } + ); +}); diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DateWidget/DateWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DateWidget/DateWidgetController.cs index f4ba7f3e1..34452dde6 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DateWidget/DateWidgetController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DateWidget/DateWidgetController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget.ViewModels; using Volo.Abp.AspNetCore.Mvc; @@ -10,8 +9,6 @@ namespace Unity.Flex.Web.Views.Shared.Components.DateWidget [Route("Flex/Widgets/Date")] public class DateWidgetController : AbpController { - protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - [HttpGet] [Route("Refresh")] public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelName) @@ -20,7 +17,7 @@ public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelNa // Check if the model state is valid if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for DateWidget:Refresh"); + Logger.LogWarning("Invalid model state for DateWidget:Refresh"); return ViewComponent(typeof(DateWidget)); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionDefinitionWidget/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionDefinitionWidget/Default.cshtml index 71082a3d6..5216eea0d 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionDefinitionWidget/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionDefinitionWidget/Default.cshtml @@ -2,6 +2,7 @@ @using Unity.Flex.Web.Views.Shared.Components.NumericDefinitionWidget; @using Unity.Flex.Web.Views.Shared.Components.QuestionYesNoDefinitionWidget; @using Unity.Flex.Web.Views.Shared.Components.QuestionSelectListDefinitionWidget; +@using Unity.Flex.Web.Views.Shared.Components.TextAreaDefinitionWidget @using Unity.Flex.Web.Views.Shared.Components.TextDefinitionWidget; @using Unity.Flex; @@ -29,6 +30,11 @@ @await Component.InvokeAsync(typeof(QuestionSelectListDefinitionWidget), new { Model.Definition }) break; + case Unity.Flex.Scoresheets.QuestionType.TextArea: + + @await Component.InvokeAsync(typeof(TextAreaDefinitionWidget), new { Model.Definition }) + + break; default: break; } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionDefinitionWidget/QuestionDefinitionWidget.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionDefinitionWidget/QuestionDefinitionWidget.cs index a5fc7664d..a2ffea8ae 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionDefinitionWidget/QuestionDefinitionWidget.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionDefinitionWidget/QuestionDefinitionWidget.cs @@ -26,6 +26,7 @@ public class QuestionDefinitionWidget : AbpViewComponent QuestionType.Text => (CustomFieldDefinition?)TextDefinitionWidget.TextDefinitionWidget.ParseFormValues(form), QuestionType.YesNo => (CustomFieldDefinition?)QuestionYesNoDefinitionWidget.QuestionYesNoDefinitionWidget.ParseFormValues(form), QuestionType.SelectList => (CustomFieldDefinition?)QuestionSelectListDefinitionWidget.QuestionSelectListDefinitionWidget.ParseFormValues(form), + QuestionType.TextArea => (CustomFieldDefinition?)TextAreaDefinitionWidget.TextAreaDefinitionWidget.ParseFormValues(form), _ => null, }; } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionDefinitionWidget/QuestionDefinitionWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionDefinitionWidget/QuestionDefinitionWidgetController.cs index db78170dc..a2740845e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionDefinitionWidget/QuestionDefinitionWidgetController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionDefinitionWidget/QuestionDefinitionWidgetController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.AspNetCore.Mvc; namespace Unity.Flex.Web.Views.Shared.Components.QuestionDefinitionWidget @@ -9,8 +8,6 @@ namespace Unity.Flex.Web.Views.Shared.Components.QuestionDefinitionWidget [Route("Flex/Widgets/QuestionDefinition")] public class QuestionDefinitionWidgetController : AbpController { - protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - [HttpGet] [Route("Refresh")] public IActionResult Refresh(string type, string? definition) @@ -18,7 +15,7 @@ public IActionResult Refresh(string type, string? definition) // Check if the model state is valid if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for QuestionDefinitionWidgetController:Refresh"); + Logger.LogWarning("Invalid model state for QuestionDefinitionWidgetController:Refresh"); return ViewComponent(typeof(QuestionDefinitionWidget)); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionNumberWidget/QuestionNumberWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionNumberWidget/QuestionNumberWidgetController.cs index e0f5e66eb..d521228c6 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionNumberWidget/QuestionNumberWidgetController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionNumberWidget/QuestionNumberWidgetController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using System; using Volo.Abp.AspNetCore.Mvc; @@ -10,8 +9,6 @@ namespace Unity.Flex.Web.Views.Shared.Components.QuestionNumberWidget [Route("GrantApplications/Widgets/QuestionNumber")] public class CurrencyWidgetController : AbpController { - protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - [HttpGet] [Route("Refresh")] public IActionResult Refresh(Guid questionId, bool isDisabled, double? answer, int? min, int? max) @@ -19,7 +16,7 @@ public IActionResult Refresh(Guid questionId, bool isDisabled, double? answer, i // Check if the model state is valid if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for CurrencyWidgetController:Refresh"); + Logger.LogWarning("Invalid model state for CurrencyWidgetController:Refresh"); return ViewComponent(typeof(QuestionNumberWidget)); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextAreaWidget/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextAreaWidget/Default.cshtml new file mode 100644 index 000000000..2ac1705de --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextAreaWidget/Default.cshtml @@ -0,0 +1,22 @@ +@using Unity.Flex.Web.Views.Shared.Components.QuestionTextAreaWidget; + +@model QuestionTextAreaViewModel; + +
+ + + +
+
+ + +
\ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextAreaWidget/QuestionTextAreaViewModel.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextAreaWidget/QuestionTextAreaViewModel.cs new file mode 100644 index 000000000..db11c02f4 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextAreaWidget/QuestionTextAreaViewModel.cs @@ -0,0 +1,14 @@ +using System; + +namespace Unity.Flex.Web.Views.Shared.Components.QuestionTextAreaWidget +{ + public class QuestionTextAreaViewModel : RequiredFieldViewModel + { + public Guid QuestionId { get; set; } + public bool IsDisabled { get; set; } + public string Answer { get; set; } = string.Empty; + public string? MinLength { get; set; } + public string? MaxLength { get; set; } + public uint? Rows { get; set; } = 1; + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextAreaWidget/QuestionTextAreaWidget.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextAreaWidget/QuestionTextAreaWidget.cs new file mode 100644 index 000000000..866591f70 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextAreaWidget/QuestionTextAreaWidget.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.UI.Widgets; +using Volo.Abp.AspNetCore.Mvc; +using System; + +namespace Unity.Flex.Web.Views.Shared.Components.QuestionTextAreaWidget +{ + [Widget( + RefreshUrl = "Widgets/QuestionTextArea/Refresh", + AutoInitialize = true)] + public class QuestionTextAreaWidget : AbpViewComponent + { + public async Task InvokeAsync(Guid questionId, + bool isDisabled, + string? answer, + string? minLength, + string? maxLength, + uint? rows = 1, + bool required = false) + { + return View(await Task.FromResult(new QuestionTextAreaViewModel() + { + QuestionId = questionId, + IsDisabled = isDisabled, + Answer = answer ?? string.Empty, + MinLength = minLength, + MaxLength = maxLength, + Required = required, + Rows = rows + })); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextAreaWidget/QuestionTextAreaWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextAreaWidget/QuestionTextAreaWidgetController.cs new file mode 100644 index 000000000..b9bb0c02f --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextAreaWidget/QuestionTextAreaWidgetController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using Volo.Abp.AspNetCore.Mvc; + +namespace Unity.Flex.Web.Views.Shared.Components.QuestionTextAreaWidget +{ + [ApiExplorerSettings(IgnoreApi = true)] + [Route("GrantApplications/Widgets/QuestionTextArea")] + public class QuestionTextAreaWidgetController : AbpController + { + [HttpGet] + [Route("Refresh")] + public IActionResult Refresh(Guid questionId, bool isDisabled, string? answer, int? minLength, int? maxLength) + { + // Check if the model state is valid + if (!ModelState.IsValid) + { + Logger.LogWarning("Invalid model state for QuestionTextAreaWidgetController:Refresh"); + return ViewComponent(typeof(QuestionTextAreaWidget)); + } + + // If the model state is valid, render the view component + return ViewComponent(typeof(QuestionTextAreaWidget), new { questionId, isDisabled, answer, minLength, maxLength }); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextWidget/QuestionTextWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextWidget/QuestionTextWidgetController.cs index cb076a4f8..388814a95 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextWidget/QuestionTextWidgetController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionTextWidget/QuestionTextWidgetController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using System; using Volo.Abp.AspNetCore.Mvc; @@ -10,8 +9,6 @@ namespace Unity.Flex.Web.Views.Shared.Components.QuestionTextWidget [Route("GrantApplications/Widgets/QuestionText")] public class QuestionTextWidgetController : AbpController { - protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - [HttpGet] [Route("Refresh")] public IActionResult Refresh(Guid questionId, bool isDisabled, string? answer, int? minLength, int? maxLength) @@ -19,7 +16,7 @@ public IActionResult Refresh(Guid questionId, bool isDisabled, string? answer, i // Check if the model state is valid if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for QuestionTextWidgetController:Refresh"); + Logger.LogWarning("Invalid model state for QuestionTextWidgetController:Refresh"); return ViewComponent(typeof(QuestionTextWidget)); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionYesNoWidget/QuestionYesNoWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionYesNoWidget/QuestionYesNoWidgetController.cs index d502a8bff..444a38d49 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionYesNoWidget/QuestionYesNoWidgetController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionYesNoWidget/QuestionYesNoWidgetController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using System; using Volo.Abp.AspNetCore.Mvc; @@ -10,8 +9,6 @@ namespace Unity.Flex.Web.Views.Shared.Components.QuestionYesNoWidget [Route("GrantApplications/Widgets/QuestionYesNo")] public class QuestionYesNoWidgetController : AbpController { - protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - [HttpGet] [Route("Refresh")] public IActionResult Refresh(Guid questionId, bool isDisabled, string? answer, int? yesValue, int? noValue) @@ -19,7 +16,7 @@ public IActionResult Refresh(Guid questionId, bool isDisabled, string? answer, i // Check if the model state is valid if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for QuestionYesNoWidgetController:Refresh"); + Logger.LogWarning("Invalid model state for QuestionYesNoWidgetController:Refresh"); return ViewComponent(typeof(QuestionYesNoWidget)); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/RadioWidget/RadioWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/RadioWidget/RadioWidgetController.cs index 2135da566..da523dd55 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/RadioWidget/RadioWidgetController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/RadioWidget/RadioWidgetController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget.ViewModels; using Volo.Abp.AspNetCore.Mvc; @@ -10,8 +9,6 @@ namespace Unity.Flex.Web.Views.Shared.Components.RadioWidget [Route("Flex/Widgets/Radio")] public class RadioWidgetController : AbpController { - protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - [HttpGet] [Route("Refresh")] public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelName) @@ -19,7 +16,7 @@ public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelNa // Check if the model state is valid if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for RadioWidgetController:Refresh"); + Logger.LogWarning("Invalid model state for RadioWidgetController:Refresh"); return ViewComponent(typeof(RadioWidget)); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Default.cshtml index 553e7b826..6a8f2148b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Default.cshtml @@ -76,7 +76,21 @@ @foreach (var question in sec.Fields) { -
+
@question.Label diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Scoresheet.js b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Scoresheet.js index f45986f07..82f5589a9 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Scoresheet.js +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/Scoresheet/Scoresheet.js @@ -2,7 +2,7 @@ $(function () { function makeScoresheetsSortable() { document.querySelectorAll('[id^="sections-questions"]').forEach(function (div) { - + _ = new Sortable(div, { animation: 150, onEnd: function (evt) { @@ -22,7 +22,7 @@ $(function () { const isSecondItemSection = secondItem?.classList.contains('section-item'); if (isTopItem && !isSecondItemSection) { - return false; + return false; } if (isDraggedSection) { @@ -36,28 +36,28 @@ $(function () { }); }); - + _ = new Sortable(document.getElementById('scoresheet-accordion'), { handle: '.draggable-header', animation: 150, ghostClass: 'blue-background', onEnd: function (evt) { - let itemEl = evt.item; + let itemEl = evt.item; itemEl.style.border = ""; updateScoresheetOrder(); }, onStart: function (evt) { - let itemEl = evt.item; - itemEl.style.border = "2px solid lightblue"; + let itemEl = evt.item; + itemEl.style.border = "2px solid lightblue"; }, }); - + } function updateScoresheetOrder() { let order = []; $("#scoresheet-accordion .accordion-item").each(function (index, element) { - let scoresheetId = $(element).find(".accordion-header").attr("id").replace("heading-",""); + let scoresheetId = $(element).find(".accordion-header").attr("id").replace("heading-", ""); order.push(scoresheetId); }); unity.flex.scoresheets.scoresheet.saveScoresheetOrder(order) @@ -86,16 +86,16 @@ $(function () { updatePreviewAccordion(sortedItems); } - + function attachAccordionToggleListeners() { const accordionItems = document.querySelectorAll('#scoresheet-accordion .accordion-button'); accordionItems.forEach(button => { button.addEventListener('click', function () { - setTimeout(updateUnsortedPreview, 500); + setTimeout(updateUnsortedPreview, 500); }); }); - } - + } + function updatePreviewAccordion(sortedItems) { const previewDiv = document.getElementById('preview'); @@ -104,6 +104,14 @@ $(function () { return; } + const previewBuilders = { + "Text": buildTextFieldPreview, + "YesNo": buildYesNoFieldPreview, + "Number": buildNumberFieldPreview, + "SelectList": buildSelectListFieldPreview, + "TextArea": buildTextAreaFieldPreview + }; + let accordionHTML = ''; let currentSectionItem = null; let sectionNumber = 1; @@ -130,78 +138,6 @@ $(function () { currentSectionItem = item; questionNumber = 1; } else { - let questionBody = ''; - if (item.dataset.questiontype === "Text") { - questionBody = ` -

${item.dataset.questiondesc}

-
- - - -
-
- - -
`; - } else if (item.dataset.questiontype === "YesNo") { - questionBody = ` -

${item.dataset.questiondesc}

-
- - -
-
- - -
`; - } else if (item.dataset.questiontype === "Number") { - questionBody = ` -

${item.dataset.questiondesc}

-
- - - -
-
- - -
`; - } else if (item.dataset.questiontype === "SelectList") { - const options = JSON.parse(item.dataset.definition).options || []; - let optionsHTML = ``; - optionsHTML += options.map(option => { - const truncatedValue = option.value.length > 100 ? option.value.substring(0, 100) + " ..." : option.value; - return ``; - }).join(''); - - questionBody = ` -

${item.dataset.questiondesc}

-
- - -
-
- - -
`; - } - accordionHTML += `

@@ -211,7 +147,7 @@ $(function () {

- ${questionBody} + ${buildFieldPreview(previewBuilders[item.dataset.questiontype], item)}
`; @@ -242,6 +178,100 @@ $(function () { updateSubtotal(); } + function buildFieldPreview(builder, item) { + return builder ? builder(item) : null; + } + + function buildTextAreaFieldPreview(item) { + return ` +

${item.dataset.questiondesc}

+
+ + + +
+
+ + +
`; + } + + function buildSelectListFieldPreview(item) { + const options = JSON.parse(item.dataset.definition).options || []; + let optionsHTML = ``; + optionsHTML += options.map(option => { + const truncatedValue = option.value.length > 100 ? option.value.substring(0, 100) + " ..." : option.value; + return ``; + }).join(''); + + return ` +

${item.dataset.questiondesc}

+
+ + +
+
+ + +
`; + } + + function buildNumberFieldPreview(item) { + return ` +

${item.dataset.questiondesc}

+
+ + + +
+
+ + +
`; + } + + function buildYesNoFieldPreview(item) { + return ` +

${item.dataset.questiondesc}

+
+ + +
+
+ + +
`; + } + + function buildTextFieldPreview(item) { + return ` +

${item.dataset.questiondesc}

+
+ + + +
+
+ + +
`; + } function hashCode(str) { let hash = 0; @@ -256,11 +286,11 @@ $(function () { return hash; } - + makeScoresheetsSortable(); attachAccordionToggleListeners(); updateUnsortedPreview(); - + PubSub.subscribe( 'refresh_scoresheet_configuration_page', (msg, data) => { @@ -301,12 +331,10 @@ let sectionModal = new abp.ModalManager({ viewUrl: 'ScoresheetConfiguration/SectionModal' }); - - sectionModal.onResult(function (response) { const actionType = $(response.currentTarget).find('#ActionType').val(); PubSub.publish('refresh_scoresheet_list', { scoresheetId: selectedScoresheetId }); - + abp.notify.success( actionType + ' is successful.', 'Scoresheet Section' @@ -382,7 +410,6 @@ function savePreviewChanges(questionId, inputFieldPrefix, saveButtonPrefix, disc discardButton.disabled = true; updateSubtotal(); - } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/SelectListWidget/SelectListWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/SelectListWidget/SelectListWidgetController.cs index d1a04026b..23bd81d33 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/SelectListWidget/SelectListWidgetController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/SelectListWidget/SelectListWidgetController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget.ViewModels; using Volo.Abp.AspNetCore.Mvc; @@ -10,8 +9,6 @@ namespace Unity.Flex.Web.Views.Shared.Components.SelectListWidget [Route("Flex/Widgets/SelectList")] public class SelectListWidgetController : AbpController { - protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - [HttpGet] [Route("Refresh")] public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelName) @@ -19,7 +16,7 @@ public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelNa // Check if the model state is valid if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for WorksheetFieldViewModel:Refresh"); + Logger.LogWarning("Invalid model state for WorksheetFieldViewModel:Refresh"); return ViewComponent(typeof(SelectListWidget)); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextAreaDefinitionWidget/TextAreaDefinitionViewModel.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextAreaDefinitionWidget/TextAreaDefinitionViewModel.cs index eeb48762a..46263f869 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextAreaDefinitionWidget/TextAreaDefinitionViewModel.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextAreaDefinitionWidget/TextAreaDefinitionViewModel.cs @@ -4,7 +4,7 @@ namespace Unity.Flex.Web.Views.Shared.Components.TextAreaDefinitionWidget { - public class TextAreaDefinitionViewModel : WorksheetFieldDefinitionViewModelBase + public class TextAreaDefinitionViewModel : RequiredFieldViewModel { public TextAreaDefinitionViewModel() : base() { diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextAreaDefinitionWidget/TextAreaDefinitionWidget.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextAreaDefinitionWidget/TextAreaDefinitionWidget.cs index 864bbeb59..69ff00dc8 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextAreaDefinitionWidget/TextAreaDefinitionWidget.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextAreaDefinitionWidget/TextAreaDefinitionWidget.cs @@ -7,12 +7,13 @@ using Microsoft.AspNetCore.Http; using Unity.Flex.Worksheets.Definitions; using System.Text.Json; +using Unity.Flex.Web.Views.Shared.Components.QuestionDefinitionWidget; namespace Unity.Flex.Web.Views.Shared.Components.TextAreaDefinitionWidget { [Widget( RefreshUrl = "../Flex/Widgets/TextAreaDefinition/Refresh", - ScriptTypes = [typeof(TextAreaDefinitionWidgetScriptBundleContributor)], + ScriptTypes = [typeof(TextAreaDefinitionWidgetScriptBundleContributor)], AutoInitialize = true)] public class TextAreaDefinitionWidget : AbpViewComponent { @@ -23,7 +24,8 @@ public class TextAreaDefinitionWidget : AbpViewComponent MinLength = uint.Parse(form["MinLength"].ToString()), MaxLength = uint.Parse(form["MaxLength"].ToString()), Rows = uint.Parse(form["Rows"].ToString()), - }; + } + .ApplyRequired(form); } public async Task InvokeAsync(string? definition) @@ -37,7 +39,8 @@ public async Task InvokeAsync(string? definition) { MinLength = textAreaDefinition.MinLength, MaxLength = textAreaDefinition.MaxLength, - Rows = textAreaDefinition.Rows + Rows = textAreaDefinition.Rows, + Required = textAreaDefinition.Required })); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetInstanceWidget/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetInstanceWidget/Default.cshtml index cda5878bf..8736ebfce 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetInstanceWidget/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetInstanceWidget/Default.cshtml @@ -1,5 +1,6 @@ @using Microsoft.AspNetCore.Authorization; @using Unity.Flex.Web.Views.Shared.Components.BCAddressWidget +@using Unity.Flex.Web.Views.Shared.Components.DataGridWidget @using Unity.Flex.Web.Views.Shared.Components.TextAreaWidget @using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget.ViewModels; @using Unity.Flex.Worksheets; @@ -79,6 +80,11 @@ @await Component.InvokeAsync(typeof(TextAreaWidget), new { fieldModel = field, modelName = Model.Name }) break; } + case CustomFieldType.DataGrid: + { + @await Component.InvokeAsync(typeof(DataGridWidget), new { fieldModel = field, modelName = Model.Name }) + break; + } default: { LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - [HttpGet] [Route("Refresh")] public IActionResult Refresh(Guid instanceCorrelationId, string instanceCorrelationProvider, Guid sheetCorrelationId, string sheetCorrelationProvider, string uiAnchor, Guid worksheetId) @@ -19,7 +16,7 @@ public IActionResult Refresh(Guid instanceCorrelationId, string instanceCorrelat // Check if the model state is valid if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for WorksheetInstanceWidgetController:Refresh"); + Logger.LogWarning("Invalid model state for WorksheetInstanceWidgetController:Refresh"); return ViewComponent(typeof(WorksheetInstanceWidget)); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetListWidget/WorksheetList.js b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetListWidget/WorksheetList.js index ef8f7f718..213ea94b8 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetListWidget/WorksheetList.js +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetListWidget/WorksheetList.js @@ -44,7 +44,7 @@ $(function () { } addWorksheetModal.onResult(function (result, response) { - PubSub.publish('refresh_worksheet_list', { worksheetId: response.responseText.worksheetId, action: response.responseText.action }); + PubSub.publish('refresh_worksheet_list', { worksheetId: response.responseText.worksheetId, action: response.responseText.action }); abp.notify.success( 'Operation completed successfully.', response.responseText.action + ' Worksheet' @@ -66,7 +66,7 @@ $(function () { fetch(url) .then(response => response.text()) .then(data => { - document.getElementById('worksheet-info-widget-list').innerHTML = data; + document.getElementById('worksheet-info-widget-list').innerHTML = data; setTimeout(() => { PubSub.publish('worksheet_list_refreshed'); }, 100); @@ -101,7 +101,7 @@ $(function () { _ = new Sortable(div, { animation: 150, onEnd: function (evt) { - updateSectionSequence(evt); + updateSectionSequence(evt); }, ghostClass: 'blue-background', onMove: function (_) { @@ -159,6 +159,7 @@ $(function () { .then(data => { previewPane.html(data); $("#preview :input").prop("readonly", true); + PubSub.publish('worksheet_preview_datagrid_refresh'); }) .catch(error => { console.error('Error generating preview:', error); @@ -171,10 +172,10 @@ $(function () { PubSub.subscribe( 'refresh_worksheet_list', - () => { + () => { refreshWorksheetListWidget(); makeSectionsAndFieldsSortable(); - updatePreview(); + updatePreview(); } ); diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetListWidget/WorksheetListWidget.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetListWidget/WorksheetListWidget.cs index 38e956794..5c020c82b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetListWidget/WorksheetListWidget.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetListWidget/WorksheetListWidget.cs @@ -9,6 +9,8 @@ using Unity.Flex.Web.Views.Shared.Components.Worksheets; using Unity.Flex.Web.Views.Shared.Components.CheckboxGroupDefinitionWidget; using Unity.Flex.Web.Views.Shared.Components.SelectListDefinitionWidget; +using Unity.Flex.Web.Views.Shared.Components.DataGridDefinitionWidget; +using Unity.Flex.Web.Views.Shared.Components.DataGridWidget; namespace Unity.Flex.Web.Views.Shared.Components.WorksheetList; @@ -18,13 +20,17 @@ namespace Unity.Flex.Web.Views.Shared.Components.WorksheetList; typeof(WorksheetListWidgetScriptBundleContributor), typeof(WorksheetWidgetScriptBundleContributor), typeof(CheckboxGroupDefinitionWidgetScriptBundleContributor), - typeof(SelectListDefinitionWidgetScriptBundleContributor) + typeof(SelectListDefinitionWidgetScriptBundleContributor), + typeof(DataGridDefinitionWidgetScriptBundleContributor), + typeof(DataGridWidgetScriptBundleContributor) ], StyleTypes = [ typeof(WorksheetListWidgetStyleBundleContributor), typeof(WorksheetWidgetStyleBundleContributor), typeof(CheckboxGroupDefinitionWidgetStyleBundleContributor), - typeof(SelectListDefinitionWidgetStyleBundleContributor) + typeof(SelectListDefinitionWidgetStyleBundleContributor), + typeof(DataGridDefinitionWidgetStyleBundleContributor), + typeof(DataGridWidgetStyleBundleContributor) ], AutoInitialize = true)] public class WorksheetListWidget(IWorksheetAppService worksheetAppService) : AbpViewComponent diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetWidget/WorksheetWidget.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetWidget/WorksheetWidget.cs index 11f88b365..19d3afbea 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetWidget/WorksheetWidget.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetWidget/WorksheetWidget.cs @@ -32,11 +32,11 @@ public async Task InvokeAsync(WorksheetDto worksheetDto) { "SelectList", "fl fl-list" }, { "BCAddress", "fl fl-globe" }, { "TextArea", "fl fl-text-area" }, - { "Text", "fl fl-font" }, + { "Text", "fl fl-font" }, { "Currency", "custom-icon-text custom-dollar" }, { "YesNo", "custom-icon-text custom-yesno" }, { "Numeric", "custom-icon-text custom-numeric" }, - + { "DataGrid", "fl fl-datagrid" } } }); return View(worksheet); diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetWidget/WorksheetWidgetViewModelController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetWidget/WorksheetWidgetViewModelController.cs index 4e80105bf..a6a6a9153 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetWidget/WorksheetWidgetViewModelController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetWidget/WorksheetWidgetViewModelController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using System; using System.Threading.Tasks; using Unity.Flex.Worksheets; @@ -12,8 +11,6 @@ namespace Unity.Flex.Web.Views.Shared.Components.Worksheets [Route("Flex/Widgets/Worksheet")] public class WorksheetWidgetViewModelController(IWorksheetAppService worksheetAppService) : AbpController { - protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - [HttpGet] [Route("Refresh")] public async Task Worksheet([FromQuery] Guid worksheetId) @@ -21,7 +18,7 @@ public async Task Worksheet([FromQuery] Guid worksheetId) // Check if the model state is valid if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for WorksheetWidgetViewModelController:Refresh"); + Logger.LogWarning("Invalid model state for WorksheetWidgetViewModelController:Refresh"); return ViewComponent(typeof(WorksheetWidget)); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/YesNoWidget/YesNoWidgetController.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/YesNoWidget/YesNoWidgetController.cs index 7c9dcc2ca..70d2ecd75 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/YesNoWidget/YesNoWidgetController.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/YesNoWidget/YesNoWidgetController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget.ViewModels; using Volo.Abp.AspNetCore.Mvc; @@ -10,8 +9,6 @@ namespace Unity.Flex.Web.Views.Shared.Components.YesNoWidget [Route("Flex/Widgets/YesNo")] public class YesNoWidgetController : AbpController { - protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - [HttpGet] [Route("Refresh")] public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelName) @@ -19,7 +16,7 @@ public IActionResult Refresh(WorksheetFieldViewModel? fieldModel, string modelNa // Check if the model state is valid if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for YesNoWidgetController:Refresh"); + Logger.LogWarning("Invalid model state for YesNoWidgetController:Refresh"); return ViewComponent(typeof(YesNoWidget)); } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Web.Tests/Components/DataGridDefinitionWidgetTests.cs b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Web.Tests/Components/DataGridDefinitionWidgetTests.cs new file mode 100644 index 000000000..62ec7d722 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Web.Tests/Components/DataGridDefinitionWidgetTests.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewComponents; +using Shouldly; +using System.Text.Json; +using Unity.Flex.Web.Views.Shared.Components.DataGridDefinitionWidget; +using Unity.Flex.Worksheets.Definitions; +using Unity.GrantManager; +using Volo.Abp.DependencyInjection; + +namespace Unity.Flex.Web.Tests.Components +{ + public class DataGridDefinitionWidgetTests : GrantManagerWebTestBase + { + private readonly IAbpLazyServiceProvider lazyServiceProvider; + + public DataGridDefinitionWidgetTests() + { + lazyServiceProvider = GetRequiredService(); + } + + [Fact] + public async Task DataGridDefinitionWidgetReturnsCorrectLimits() + { + // Arrange + var viewContext = new ViewContext + { + HttpContext = new DefaultHttpContext() + }; + var viewComponentContext = new ViewComponentContext + { + ViewContext = viewContext + }; + + var viewComponent = new DataGridDefinitionWidget() + { + ViewComponentContext = viewComponentContext, + LazyServiceProvider = lazyServiceProvider + }; + + // Act + var result = await viewComponent.InvokeAsync(JsonSerializer.Serialize(new DataGridDefinition())) as ViewViewComponentResult; + DataGridDefinitionViewModel? resultModel; + + resultModel = result!.ViewData!.Model! as DataGridDefinitionViewModel; + + // Assert + resultModel!.Dynamic.ToString().ShouldBe(bool.FalseString); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Web.Tests/Components/DataGridWidgetTests.cs b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Web.Tests/Components/DataGridWidgetTests.cs new file mode 100644 index 000000000..37b5da574 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Web.Tests/Components/DataGridWidgetTests.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewComponents; +using Shouldly; +using Unity.Flex.Web.Views.Shared.Components.DataGridWidget; +using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget.ViewModels; +using Unity.GrantManager; +using Volo.Abp.DependencyInjection; + +namespace Unity.Flex.Web.Tests.Components +{ + public class DataGridWidgetTests : GrantManagerWebTestBase + { + private readonly IAbpLazyServiceProvider lazyServiceProvider; + + public DataGridWidgetTests() + { + lazyServiceProvider = GetRequiredService(); + } + + [Fact] + public void DataGridWidgetReturnsCorrectType() + { + // Arrange + var viewContext = new ViewContext + { + HttpContext = new DefaultHttpContext() + }; + var viewComponentContext = new ViewComponentContext + { + ViewContext = viewContext + }; + + var viewComponent = new DataGridWidget() + { + ViewComponentContext = viewComponentContext, + LazyServiceProvider = lazyServiceProvider + }; + + // Act + var result = viewComponent.Invoke(new WorksheetFieldViewModel() + { + Type = Worksheets.CustomFieldType.DataGrid + } + , string.Empty) as ViewViewComponentResult; + + DataGridViewModel? resultModel; + + resultModel = result!.ViewData!.Model! as DataGridViewModel; + + // Assert + resultModel!.Field?.Type.ShouldBe(Worksheets.CustomFieldType.DataGrid); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/SupplierInfo/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/SupplierInfo/Default.cshtml index 19275a25f..e7577cd21 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/SupplierInfo/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/SupplierInfo/Default.cshtml @@ -40,6 +40,6 @@ - + diff --git a/applications/Unity.GrantManager/modules/Unity.SharedKernel/Utils/LetterPairGenerator.cs b/applications/Unity.GrantManager/modules/Unity.SharedKernel/Utils/LetterPairGenerator.cs new file mode 100644 index 000000000..2bdcb9266 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.SharedKernel/Utils/LetterPairGenerator.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Unity.Modules.Shared.Utils +{ + public partial class LetterPairGenerator + { + // Protected constructor to prevent direct instantiation + protected LetterPairGenerator() { } + + private const int timeoutSeconds = 30; // Set the timeout (1 second for this example) + + // Generate letter pairs from a word + private static IEnumerable LetterPairs(string str) + { + int numPairs = str.Length - 1; + for (int i = 0; i < numPairs; i++) + { + yield return str.Substring(i, 2); // Generate pair of characters + } + } + + // Method to split the string with a timeout + private static string[] SplitWithTimeout(string str, int timeoutSeconds) + { + var task = Regex.Split(str, @"\s+", RegexOptions.None, TimeSpan.FromSeconds(timeoutSeconds)); + return task; + } + + // Public method to generate letter pairs from words, with timeout handling + public static List WordLetterPairs(string str) + { + var allPairs = new List(); + + // split operation with timeout + string[] words = SplitWithTimeout(str, timeoutSeconds); + + // Generate letter pairs for each word and add them to the list + allPairs.AddRange(words.Where(word => !string.IsNullOrEmpty(word)) // Filter out empty words + .SelectMany(LetterPairs)); // Generate pairs for each word + + return allPairs; + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.SharedKernel/Utils/StringExtensions.cs b/applications/Unity.GrantManager/modules/Unity.SharedKernel/Utils/StringExtensions.cs index 63e93c297..ca6d9a133 100644 --- a/applications/Unity.GrantManager/modules/Unity.SharedKernel/Utils/StringExtensions.cs +++ b/applications/Unity.GrantManager/modules/Unity.SharedKernel/Utils/StringExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; namespace Unity.Modules.Shared.Utils { @@ -14,5 +16,23 @@ public static string SanitizeField(this string inputString) { return inputString.RemoveNewLines(); } + + // Synchronous method to compare strings + public static double CompareStringsAsync(this string str1, string str2) + { + if (string.IsNullOrEmpty(str1) || string.IsNullOrEmpty(str2)) + return 0.0; + + // Await the result of the async method + var pairs1 = LetterPairGenerator.WordLetterPairs(str1.ToUpper()); + var pairs2 = new HashSet(LetterPairGenerator.WordLetterPairs(str2.ToUpper())); + + // Calculate the intersection size using LINQ + int intersection = pairs1.Count(pairs2.Remove); // Remove matching pairs from pairs2 + int union = pairs1.Count + pairs2.Count; + + double percentage = union == 0 ? 0.0 : 2.0 * intersection * 100 / union; + return Math.Round(Math.Min(percentage, 100.0), 2); // Ensure it does not exceed 100% and round to 2 decimals + } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml index b0f865300..30c4071ff 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml @@ -1,5 +1,6 @@ @using Unity.AspNetCore.Mvc.UI.Theme.UX2.Themes.UX2.Components.Brand @using Unity.AspNetCore.Mvc.UI.Theme.UX2.Themes.UX2.Components.Menu +@using Volo.Abp.Authorization.Permissions @using Volo.Abp.Features @using Volo.Abp.MultiTenancy @using Volo.Abp.Users; @@ -7,6 +8,7 @@ @inject ICurrentUser CurrentUser @inject ICurrentTenant CurrentTenant @inject IFeatureChecker FeatureChecker +@inject IPermissionChecker PermissionChecker