From 1997a8aacbdfbd6b5ee9ed54fd26d338bee89627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Wed, 17 Jun 2026 00:19:14 +0300 Subject: [PATCH] =?UTF-8?q?DynamicForm=20Url=20de=C4=9Fi=C5=9Fiklikleri=20?= =?UTF-8?q?ve=20Rapor=20A=C3=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Seeds/LanguagesData.json | 12 +++ .../Seeds/ListFormSeeder_Administration.cs | 8 +- .../Seeds/MenusData.json | 4 +- .../Seeds/TenantData.json | 6 +- .../PredefinedReports/DynamicFormReport.cs | 96 +++++++++++++++---- .../PredefinedReports/DynamicGridReport.cs | 80 ++++++++++++---- .../CustomReportStorageWebExtension.cs | 75 +++++++++++---- ui/src/routes/route.constant.ts | 4 +- ui/src/views/form/FormButtons.tsx | 20 +++- ui/src/views/list/useFilters.tsx | 11 +++ .../views/report/DevexpressReportDesigner.tsx | 15 ++- .../views/report/DevexpressReportViewer.tsx | 26 ++--- ui/src/views/report/reportRouteParams.ts | 49 ++++++++++ 13 files changed, 313 insertions(+), 93 deletions(-) create mode 100644 ui/src/views/report/reportRouteParams.ts diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 5c588db..ed9ff49 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -1398,6 +1398,18 @@ "en": "Menu Management", "tr": "Menü Yönetimi" }, + { + "resourceName": "Platform", + "key": "App.Reports.List", + "en": "List Report", + "tr": "Liste Raporu" + }, + { + "resourceName": "Platform", + "key": "App.Reports.Form", + "en": "Form Report", + "tr": "Form Raporu" + }, { "resourceName": "Platform", "key": "App.Reports", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs index fe694a5..0d0421d 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs @@ -1584,7 +1584,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep Text ="App.Platform.View", UrlTarget="_blank", AuthName = listFormName, - Url=$"/admin/reports/DynamicReport/view?id=@Id", + Url=$"/admin/reports/DynamicReport/view/@Id", IsVisible = true, VisibleExpression = "((e)=> e.row.data.CategoryId === 'Raporlar')" }, @@ -1593,7 +1593,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep Text ="App.Platform.Design", UrlTarget="_blank", AuthName = listFormName + ".Update", - Url=$"/admin/reports/DynamicReport/design?id=@Id", + Url=$"/admin/reports/DynamicReport/design/@Id", IsVisible = true, VisibleExpression = "((e)=> e.row.data.CategoryId === 'Raporlar')" }, @@ -1602,7 +1602,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep Text ="App.Platform.View", UrlTarget="_blank", AuthName = listFormName, - Url=$"/admin/reports/DynamicGrid/view?listFormCode=@Name", + Url=$"/admin/reports/DynamicGrid/view/@Name", IsVisible = true, VisibleExpression = "((e)=> e.row.data.CategoryId === 'Listeler')" }, @@ -1611,7 +1611,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep Text ="App.Platform.View", UrlTarget="_blank", AuthName = listFormName, - Url=$"/admin/reports/DynamicGrid/view?listFormCode=@Name&id=@Id", + Url=$"/admin/reports/DynamicGrid/view/@Id/@Name", IsVisible = true, VisibleExpression = "((e)=> e.row.data.CategoryId === 'Formlar')" }, diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json index 0eda867..8b07c31 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json @@ -418,7 +418,7 @@ }, { "key": "admin.devexpressDynamicReportView", - "path": "/admin/reports/:id/view", + "path": "/admin/reports/:report/view/:id?/:listFormCode?", "componentType": "normal", "componentPath": "@/views/report/DevexpressReportViewer", "routeType": "protected", @@ -426,7 +426,7 @@ }, { "key": "admin.devexpressDynamicReportDesigner", - "path": "/admin/reports/:id/design", + "path": "/admin/reports/:report/design/:id?/:listFormCode?", "componentType": "normal", "componentPath": "@/views/report/DevexpressReportDesigner", "routeType": "protected", diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json index 1b36603..2e224e1 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json @@ -1383,21 +1383,21 @@ "name": "Standard Taahhütname", "description": "Standard Taahhütname", "categoryName": "Raporlar", - "htmlContent": "

Taahhütname

http://localhost:3000/admin/reports/DynamicReport/view?id=ede12de7-0e94-4b84-a953-6c359606478b

", + "htmlContent": "

Taahhütname

http://localhost:3000/admin/reports/DynamicReport/view/ede12de7-0e94-4b84-a953-6c359606478b

", "status": "active" }, { "name": "App.Languages.LanguageText", "description": "Liste Raporları", "categoryName": "Listeler", - "htmlContent": "

Liste Raporları.

http://localhost:3000/admin/reports/DynamicGridReport/view?listFormCode=App.Languages.LanguageText&filter=[%22CultureName%22,%22=%22,%22en%22]

", + "htmlContent": "

Liste Raporları.

http://localhost:3000/admin/reports/DynamicGridReport/view/App.Languages.LanguageText?filter=[%22CultureName%22,%22=%22,%22en%22]

", "status": "active" }, { "name": "App.Languages.Language", "description": "Form Raporları", "categoryName": "Formlar", - "htmlContent": "

Liste Raporları.

http://localhost:3000/admin/reports/DynamicFormReport/view?listFormCode=App.Languages.Language&id=e2c8a2e0-af22-f7bb-5d88-3a21e3079997

", + "htmlContent": "

Liste Raporları.

http://localhost:3000/admin/reports/DynamicFormReport/view/e2c8a2e0-af22-f7bb-5d88-3a21e3079997/App.Languages.Language

", "status": "active" } ], diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs index 60d194b..3adff87 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs @@ -28,6 +28,7 @@ public class DynamicFormReport : XtraReport private const string QueryName = "DynamicForm"; private const int HorizontalMargin = 35; private const float RowHeight = 24; + private static readonly System.Drawing.Color FieldBorderColor = System.Drawing.Color.FromArgb(226, 232, 240); private static IServiceProvider _serviceProvider; public static void SetServiceProvider(IServiceProvider serviceProvider) @@ -107,6 +108,7 @@ public class DynamicFormReport : XtraReport reportFields, defaultValueHelper); selectQuery = ApplyQueryParameters(selectQuery, selectQueryManager.SelectQueryParameters); + selectQuery = StripReportPaging(selectQuery); var row = GetFirstRow(dynamicDataRepository.QueryAsync(selectQuery, connectionString).GetAwaiter().GetResult()); @@ -118,12 +120,23 @@ public class DynamicFormReport : XtraReport private void ConfigurePage() { - Margins = new System.Drawing.Printing.Margins(HorizontalMargin, HorizontalMargin, 35, 35); + Margins = CreateReportMargins(HorizontalMargin, HorizontalMargin, 35, 35); PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4; Landscape = false; Font = new DXFont("Arial", 8.25F); } +#pragma warning disable CA1416 + private static System.Drawing.Printing.Margins CreateReportMargins( + int left, + int right, + int top, + int bottom) + { + return new System.Drawing.Printing.Margins(left, right, top, bottom); + } +#pragma warning restore CA1416 + private void ConfigureDataSource(string selectQuery, string connectionString, DataSourceTypeEnum dataSourceType) { var dataSource = new SqlDataSource(new CustomStringConnectionParameters(ToDevExpressConnectionString(connectionString, dataSourceType))); @@ -195,7 +208,7 @@ public class DynamicFormReport : XtraReport var rowHeight = GetFieldRowHeight(field); var labelWidth = pageWidth * 0.28F; detail.Controls.Add(CreateCaptionLabel(field, localizer, 0, y, labelWidth, rowHeight)); - detail.Controls.Add(CreateValueLabel(field, labelWidth, y, pageWidth - labelWidth, rowHeight)); + detail.Controls.Add(CreateValueLabel(field, row, labelWidth, y, pageWidth - labelWidth, rowHeight)); y += rowHeight + 3; } @@ -230,48 +243,75 @@ public class DynamicFormReport : XtraReport Font = new DXFont("Arial", 8F, DXFontStyle.Bold), BackColor = System.Drawing.Color.FromArgb(248, 250, 252), Borders = BorderSide.All, - BorderWidth = 0.5F, + BorderColor = FieldBorderColor, + BorderWidth = 0.25F, Padding = new PaddingInfo(5, 5, 2, 2), TextAlignment = TextAlignment.MiddleLeft, Multiline = true }; } - private static XRLabel CreateValueLabel(FormReportField field, float x, float y, float width, float height) + private static XRLabel CreateValueLabel( + FormReportField field, + IDictionary row, + float x, + float y, + float width, + float height) { var label = new XRLabel { BoundsF = new System.Drawing.RectangleF(x, y, width, height), Font = new DXFont("Arial", 8F), Borders = BorderSide.All, - BorderWidth = 0.5F, + BorderColor = FieldBorderColor, + BorderWidth = 0.25F, Padding = new PaddingInfo(5, 5, 2, 2), TextAlignment = ToTextAlignment(field.Field.Alignment, field.Field.SourceDbType), Multiline = true, CanGrow = true }; - label.ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", $"[{field.BindingName}]")); - - if (IsBooleanEditor(field) || field.StaticLookupValues?.Count > 0) + if (TryGetFormattedValue(field, row, out var text)) { - label.BeforePrint += (_, _) => - { - var value = Convert.ToString(label.Text, CultureInfo.InvariantCulture); - if (IsBooleanEditor(field) && TryFormatBoolean(value, out var booleanText)) - { - label.Text = booleanText; - } - else if (value != null && field.StaticLookupValues?.TryGetValue(value, out var displayText) == true) - { - label.Text = displayText; - } - }; + label.Text = text; + } + else + { + label.ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", $"[{field.BindingName}]")); } return label; } + private static bool TryGetFormattedValue( + FormReportField field, + IDictionary row, + out string text) + { + text = null; + if (row == null || !TryGetRowValue(row, field.BindingName, out var rawValue)) + { + return false; + } + + var value = Convert.ToString(rawValue, CultureInfo.InvariantCulture); + if (IsBooleanEditor(field) && TryFormatBoolean(value, out var booleanText)) + { + text = booleanText; + return true; + } + + if (value != null && field.StaticLookupValues?.TryGetValue(value, out var displayText) == true) + { + text = displayText; + return true; + } + + text = value ?? string.Empty; + return true; + } + private static float CalculateDetailHeight( IReadOnlyList fields, ListForm listForm, @@ -546,6 +586,20 @@ public class DynamicFormReport : XtraReport return sql; } + private static string StripReportPaging(string sql) + { + if (string.IsNullOrWhiteSpace(sql)) + { + return sql; + } + + return Regex.Replace( + sql, + @"\s+ORDER\s+BY[\s\S]*?(?:\s+OFFSET\s+\d+\s+ROWS(?:\s+FETCH\s+NEXT\s+\d+\s+ROWS\s+ONLY)?)?\s*$", + string.Empty, + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + private static string ToSqlLiteral(object value) { if (value == null || value == DBNull.Value) @@ -741,7 +795,7 @@ public class DynamicFormReport : XtraReport var query = lookupQuery.Trim().TrimEnd(';'); return Regex.Replace( query, - @"\s+ORDER\s+BY\s+[\s\S]*$", + @"(? visibleFields) { - Margins = new System.Drawing.Printing.Margins(HorizontalMargin, HorizontalMargin, 35, 35); + Margins = CreateReportMargins(HorizontalMargin, HorizontalMargin, 35, 35); PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4; var portraitContentWidth = A4PortraitWidth - Margins.Left - Margins.Right; var requestedContentWidth = visibleFields.Sum(x => Math.Max(x.Field.Width ?? 90, 60)); @@ -146,6 +149,16 @@ public class DynamicGridReport : XtraReport Font = new DXFont("Arial", 8.25F); } + private static System.Drawing.Printing.Margins CreateReportMargins( + int left, + int right, + int top, + int bottom) + { + return new System.Drawing.Printing.Margins(left, right, top, bottom); + } +#pragma warning restore CA1416 + private void ConfigureDataSource(string selectQuery, string connectionString, DataSourceTypeEnum dataSourceType) { var dataSource = new SqlDataSource(new CustomStringConnectionParameters(ToDevExpressConnectionString(connectionString, dataSourceType))); @@ -195,6 +208,20 @@ public class DynamicGridReport : XtraReport return sql; } + private static string StripReportPaging(string sql) + { + if (string.IsNullOrWhiteSpace(sql)) + { + return sql; + } + + return Regex.Replace( + sql, + @"\s+ORDER\s+BY[\s\S]*?(?:\s+OFFSET\s+\d+\s+ROWS(?:\s+FETCH\s+NEXT\s+\d+\s+ROWS\s+ONLY)?)?\s*$", + string.Empty, + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + private static string ToSqlLiteral(object value) { if (value == null || value == DBNull.Value) @@ -293,7 +320,8 @@ public class DynamicGridReport : XtraReport { BoundsF = new System.Drawing.RectangleF(0, 0, width, height), Borders = BorderSide.All, - BorderWidth = 0.5F + BorderColor = TableBorderColor, + BorderWidth = 0.25F }; } @@ -353,17 +381,6 @@ public class DynamicGridReport : XtraReport CanGrow = false }; cell.ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", $"[{column.BindingName}]")); - if (column.StaticLookupValues?.Count > 0) - { - cell.BeforePrint += (_, _) => - { - var value = Convert.ToString(cell.Text, CultureInfo.InvariantCulture); - if (value != null && column.StaticLookupValues.TryGetValue(value, out var displayText)) - { - cell.Text = displayText; - } - }; - } return cell; } @@ -455,11 +472,12 @@ public class DynamicGridReport : XtraReport if (lookup.DataSourceType == UiLookupDataSourceTypeEnum.StaticData) { + var staticLookupValues = GetStaticLookupValues(lookup, defaultValueHelper); return new ReportColumn( field, - bindingName, + staticLookupValues?.Count > 0 ? GetLookupBindingName(field) : bindingName, lookup, - GetStaticLookupValues(lookup, defaultValueHelper)); + staticLookupValues); } return new ReportColumn(field, bindingName, null, null); @@ -570,11 +588,15 @@ public class DynamicGridReport : XtraReport IReadOnlyList reportColumns, DefaultValueHelper defaultValueHelper) { - var lookupColumns = reportColumns + var queryLookupColumns = reportColumns .Where(x => x.Lookup != null && IsReportQueryableLookup(x.Lookup)) .ToList(); + var staticLookupColumns = reportColumns + .Where(x => x.StaticLookupValues?.Count > 0) + .ToList(); - if (lookupColumns.Count == 0 || string.IsNullOrWhiteSpace(selectQuery)) + if ((queryLookupColumns.Count == 0 && staticLookupColumns.Count == 0) || + string.IsNullOrWhiteSpace(selectQuery)) { return selectQuery; } @@ -585,7 +607,12 @@ public class DynamicGridReport : XtraReport var selectParts = new List(); var joinParts = new List(); - foreach (var column in lookupColumns) + foreach (var column in staticLookupColumns) + { + selectParts.Add(CreateStaticLookupSelectPart(column, tableAlias)); + } + + foreach (var column in queryLookupColumns) { var lookup = column.Lookup; var alias = GetLookupAlias(column.Field); @@ -612,6 +639,10 @@ public class DynamicGridReport : XtraReport { joinInsertIndex = FindClauseIndex(selectQuery, " ORDER BY "); } + if (joinParts.Count == 0) + { + return selectQuery; + } if (joinInsertIndex < 0) { return selectQuery + " " + string.Join(' ', joinParts); @@ -620,6 +651,15 @@ public class DynamicGridReport : XtraReport return selectQuery.Insert(joinInsertIndex, " " + string.Join(' ', joinParts)); } + private static string CreateStaticLookupSelectPart(ReportColumn column, string tableAlias) + { + var sourceField = $"{QuoteSqlIdentifier(tableAlias)}.{QuoteSqlIdentifier(column.Field.FieldName)}"; + var cases = column.StaticLookupValues + .Select(item => $"WHEN {ToSqlLiteral(item.Key)} THEN {ToSqlLiteral(item.Value)}"); + + return $"CASE {sourceField} {string.Join(" ", cases)} ELSE CAST({sourceField} AS VARCHAR(4000)) END AS {QuoteSqlIdentifier(column.BindingName)}"; + } + private static int FindClauseIndex(string sql, string clause) { return sql.IndexOf(clause, StringComparison.OrdinalIgnoreCase); @@ -630,7 +670,7 @@ public class DynamicGridReport : XtraReport var query = lookupQuery.Trim().TrimEnd(';'); return Regex.Replace( query, - @"\s+ORDER\s+BY\s+[\s\S]*$", + @"(? Parameters) ParseReportUrl(string url) { var decodedUrl = DecodeRepeatedly(url); - var reportName = decodedUrl; var parameters = new Dictionary(StringComparer.OrdinalIgnoreCase); var queryIndex = decodedUrl.IndexOf('?'); - if (queryIndex < 0) + var reportPath = queryIndex < 0 ? decodedUrl : decodedUrl[..queryIndex]; + if (queryIndex >= 0) { - return (reportName, parameters); - } + var queryString = decodedUrl[(queryIndex + 1)..]; + var paramPairs = queryString.Split('&', StringSplitOptions.RemoveEmptyEntries); - reportName = decodedUrl[..queryIndex]; - var queryString = decodedUrl[(queryIndex + 1)..]; - var paramPairs = queryString.Split('&', StringSplitOptions.RemoveEmptyEntries); - - foreach (var pair in paramPairs) - { - var keyValue = pair.Split('=', 2); - if (keyValue.Length == 2 && !string.IsNullOrWhiteSpace(keyValue[0])) + foreach (var pair in paramPairs) { - parameters[keyValue[0]] = keyValue[1]; + var keyValue = pair.Split('=', 2); + if (keyValue.Length == 2 && !string.IsNullOrWhiteSpace(keyValue[0])) + { + parameters[keyValue[0]] = keyValue[1]; + } } } + var pathParts = reportPath + .Split('/', StringSplitOptions.RemoveEmptyEntries) + .Select(Uri.UnescapeDataString) + .ToArray(); + + if (pathParts.Length == 0) + { + return (reportPath, parameters); + } + + var reportName = pathParts[0]; + if (pathParts.Length >= 3 && IsReportActionSegment(pathParts[1])) + { + AddPathParameters(reportName, pathParts.Skip(2).ToArray(), parameters); + } + return (reportName, parameters); } + private static void AddPathParameters(string reportName, string[] pathParameters, Dictionary parameters) + { + if (pathParameters.Length == 0) + { + return; + } + + if (IsDynamicGridReport(reportName) || IsDynamicFormReport(reportName)) + { + if (pathParameters.Length == 1) + { + parameters.TryAdd("listFormCode", pathParameters[0]); + return; + } + + parameters.TryAdd("id", pathParameters[0]); + parameters.TryAdd("listFormCode", pathParameters[1]); + return; + } + + parameters.TryAdd("id", pathParameters[0]); + } + private static bool IsDynamicGridReport(string reportName) { return string.Equals(reportName, "DynamicGrid", StringComparison.OrdinalIgnoreCase) || @@ -219,6 +256,12 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens string.Equals(reportName, Sozsoft.Reports.PredefinedReports.DynamicFormReport.ReportName, StringComparison.OrdinalIgnoreCase); } + private static bool IsReportActionSegment(string value) + { + return string.Equals(value, "view", StringComparison.OrdinalIgnoreCase) || + string.Equals(value, "design", StringComparison.OrdinalIgnoreCase); + } + private static bool HasParameter(Dictionary parameters, string name) { return parameters.TryGetValue(name, out var value) && !string.IsNullOrWhiteSpace(value); diff --git a/ui/src/routes/route.constant.ts b/ui/src/routes/route.constant.ts index 739d754..847ccbd 100644 --- a/ui/src/routes/route.constant.ts +++ b/ui/src/routes/route.constant.ts @@ -50,8 +50,8 @@ export const ROUTES_ENUM = { dynamicServicesEdit: '/admin/developerkit/dynamic-services/edit/:id', }, reports: { - view: '/admin/reports/:id/view', - design: '/admin/reports/:id/design', + view: '/admin/reports/:report/view/:id?/:listFormCode?', + design: '/admin/reports/:report/design/:id?/:listFormCode?', }, }, admin: { diff --git a/ui/src/views/form/FormButtons.tsx b/ui/src/views/form/FormButtons.tsx index eebbd3d..5903350 100644 --- a/ui/src/views/form/FormButtons.tsx +++ b/ui/src/views/form/FormButtons.tsx @@ -11,6 +11,7 @@ import { FaEye, FaSave, FaCog, + FaFileAlt, } from 'react-icons/fa' import { useNavigate } from 'react-router-dom' import { GridColumnData } from '../list/GridColumnData' @@ -80,6 +81,13 @@ const FormButtons = (props: { layout, }) + const openDynamicGridReport = () => { + if (!id) return + + const reportUrl = `/admin/reports/DynamicGrid/view/${encodeURIComponent(id)}/${encodeURIComponent(listFormCode)}` + window.open(reportUrl, isPwaMode ? '_self' : '_blank') + } + const handleDelete = async (e: any) => { //auth check e.preventDefault() @@ -279,7 +287,17 @@ const FormButtons = (props: { {...(permissions.c || permissions.u ? {} : { disabled: true })} > )} - {checkPermission(gridDto?.gridOptions.permissionDto.c) && !isSubForm && ( + {id && !isSubForm && ( + + )} + {checkPermission(gridDto?.gridOptions.permissionDto.u) && !isSubForm && (