diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs
index f0f4094..7220b59 100644
--- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs
+++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs
@@ -1586,6 +1586,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
AuthName = listFormName,
Url=$"/admin/reports/TemplateReport/view?id=@Id",
IsVisible = true,
+ VisibleExpression = "((e)=> e.row.data.Description !== 'Liste Raporları')"
},
new() {
Hint = "App.Platform.Design",
@@ -1594,6 +1595,16 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
AuthName = listFormName + ".Update",
Url=$"/admin/reports/TemplateReport/design?id=@Id",
IsVisible = true,
+ VisibleExpression = "((e)=> e.row.data.Description !== 'Liste Raporları')"
+ },
+ new() {
+ Hint = "App.Platform.View",
+ Text ="App.Platform.View",
+ UrlTarget="_blank",
+ AuthName = listFormName,
+ Url=$"/admin/reports/DynamicListFormReport/view?listFormCode=@Name",
+ IsVisible = true,
+ VisibleExpression = "((e)=> e.row.data.Description !== 'Liste Raporları')"
},
}),
}
diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json
index 3a8480e..fc10183 100644
--- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json
+++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json
@@ -1373,8 +1373,8 @@
"icon": "✍️"
},
{
- "name": "Sözleşmeler",
- "description": "Tedarikçiler ile ilgili sözleşme raporları",
+ "name": "Listeler",
+ "description": "Tüm listelerin raporları",
"icon": "📜"
}
],
@@ -1384,7 +1384,14 @@
"description": "Standard Taahhütname",
"categoryName": "Taahhütnameler",
"htmlContent": "
Taahhütname
Bu belge, tarafların belirli yükümlülüklerini ve taahhütlerini belirtir.
",
- "status": "Aktif"
+ "status": "active"
+ },
+ {
+ "name": "App.Definitions.AiBot",
+ "description": "Liste Raporları",
+ "categoryName": "Listeler",
+ "htmlContent": "Liste Raporları.
",
+ "status": "active"
}
],
"WorkHours": [
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Controllers/CustomWebDocumentViewerController.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Controllers/CustomWebDocumentViewerController.cs
index e210f04..de6e35c 100644
--- a/api/src/Sozsoft.Platform.HttpApi.Host/Controllers/CustomWebDocumentViewerController.cs
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/Controllers/CustomWebDocumentViewerController.cs
@@ -1,14 +1,16 @@
-using DevExpress.AspNetCore.Reporting.WebDocumentViewer;
+using DevExpress.AspNetCore.Reporting.WebDocumentViewer;
using DevExpress.AspNetCore.Reporting.WebDocumentViewer.Native.Services;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Sozsoft.Platform.Controllers;
[Route("DXXRDV")]
+[Authorize]
[ApiExplorerSettings(IgnoreApi = true)]
public class CustomWebDocumentViewerController : WebDocumentViewerController
{
- public CustomWebDocumentViewerController(IWebDocumentViewerMvcControllerService controllerService)
+ public CustomWebDocumentViewerController(IWebDocumentViewerMvcControllerService controllerService)
: base(controllerService)
{
}
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicListFormReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicListFormReport.cs
new file mode 100644
index 0000000..aa79ce9
--- /dev/null
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicListFormReport.cs
@@ -0,0 +1,480 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Globalization;
+using System.Linq;
+using System.Text.RegularExpressions;
+using DevExpress.DataAccess.ConnectionParameters;
+using DevExpress.DataAccess.Sql;
+using DevExpress.Drawing;
+using DevExpress.XtraPrinting;
+using DevExpress.XtraReports.UI;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Primitives;
+using Sozsoft.Platform.DynamicData;
+using Sozsoft.Platform.Entities;
+using Sozsoft.Platform.Enums;
+using Sozsoft.Platform.ListForms;
+using Sozsoft.Platform.Localization;
+using Sozsoft.Platform.Queries;
+
+namespace Sozsoft.Reports.PredefinedReports;
+
+public class DynamicListFormReport : XtraReport
+{
+ public const string ReportName = nameof(DynamicListFormReport);
+ private const string QueryName = "DynamicList";
+ private const int A4PortraitWidth = 827;
+ private const int HorizontalMargin = 35;
+ private const int LandscapeColumnThreshold = 5;
+ private static IServiceProvider _serviceProvider;
+
+ public static void SetServiceProvider(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ public static DynamicListFormReport Create(IDictionary parameters)
+ {
+ var report = new DynamicListFormReport();
+ report.Build(parameters);
+ return report;
+ }
+
+ public DynamicListFormReport()
+ {
+ }
+
+ private void Build(IDictionary parameters)
+ {
+ if (_serviceProvider == null)
+ {
+ throw new InvalidOperationException("DynamicListFormReport service provider is not configured.");
+ }
+
+ if (!parameters.TryGetValue("listFormCode", out var listFormCode) || string.IsNullOrWhiteSpace(listFormCode))
+ {
+ throw new ArgumentException("listFormCode parameter is required.");
+ }
+
+ using var scope = _serviceProvider.CreateScope();
+ var authManager = scope.ServiceProvider.GetRequiredService();
+ var listFormManager = scope.ServiceProvider.GetRequiredService();
+ var listFormFieldManager = scope.ServiceProvider.GetRequiredService();
+ var customizationManager = scope.ServiceProvider.GetRequiredService();
+ var dynamicDataManager = scope.ServiceProvider.GetRequiredService();
+ var selectQueryManager = scope.ServiceProvider.GetRequiredService();
+ var localizer = scope.ServiceProvider.GetRequiredService>();
+
+ if (!authManager.CanAccess(listFormCode).GetAwaiter().GetResult())
+ {
+ throw new UnauthorizedAccessException($"No read permission for list form '{listFormCode}'.");
+ }
+
+ var listForm = listFormManager.GetUserListForm(listFormCode).GetAwaiter().GetResult();
+ var fields = listFormFieldManager.GetUserListFormFields(listFormCode).GetAwaiter().GetResult()
+ .Where(x => x.CanRead)
+ .OrderBy(x => x.ListOrderNo ?? int.MaxValue)
+ .ThenBy(x => x.CaptionName)
+ .ToList();
+ var customizations = customizationManager.GetUsersServerCustomizations(listFormCode).GetAwaiter().GetResult();
+ var (_, connectionString, dataSourceType) = dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode).GetAwaiter().GetResult();
+
+ var queryParameters = ToStringValues(parameters);
+ var queryParams = new QueryParameters
+ {
+ ListFormCode = listFormCode,
+ Filter = GetParameter(parameters, "filter"),
+ Sort = GetParameter(parameters, "sort"),
+ Group = GetParameter(parameters, "group"),
+ GroupSummary = GetParameter(parameters, "groupSummary"),
+ TotalSummary = GetParameter(parameters, "totalSummary"),
+ Take = 0,
+ Skip = 0
+ };
+
+ selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams);
+
+ var visibleFields = fields
+ .Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")
+ .Where(x => x.IsActive != false && x.Visible == true)
+ .Where(x => selectQueryManager.SelectFields.Any(s => s.FieldName == x.FieldName))
+ .ToList();
+
+ if (visibleFields.Count == 0)
+ {
+ visibleFields = fields
+ .Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")
+ .Where(x => x.IsActive != false)
+ .Where(x => selectQueryManager.SelectFields.Any(s => s.FieldName == x.FieldName))
+ .Take(8)
+ .ToList();
+ }
+
+ var reportColumns = visibleFields
+ .Select(field =>
+ {
+ var selectField = selectQueryManager.SelectFields.First(s => s.FieldName == field.FieldName);
+ return new ReportColumn(field, GetReportFieldName(selectField));
+ })
+ .Where(x => !string.IsNullOrWhiteSpace(x.BindingName) && x.BindingName != "undefined")
+ .ToList();
+
+ ConfigurePage(reportColumns);
+ ConfigureDataSource(
+ ApplyQueryParameters(selectQueryManager.SelectQuery, selectQueryManager.SelectQueryParameters),
+ connectionString,
+ dataSourceType);
+ BuildLayout(listForm, reportColumns, localizer);
+ RequestParameters = false;
+ }
+
+ private void ConfigurePage(IReadOnlyCollection visibleFields)
+ {
+ Margins = new System.Drawing.Printing.Margins(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));
+ Landscape = visibleFields.Count >= LandscapeColumnThreshold || requestedContentWidth > portraitContentWidth;
+ Font = new DXFont("Arial", 8.25F);
+ }
+
+ private void ConfigureDataSource(string selectQuery, string connectionString, DataSourceTypeEnum dataSourceType)
+ {
+ var dataSource = new SqlDataSource(new CustomStringConnectionParameters(ToDevExpressConnectionString(connectionString, dataSourceType)));
+ dataSource.Queries.Add(new CustomSqlQuery(QueryName, selectQuery));
+ ComponentStorage.Add(dataSource);
+ DataSource = dataSource;
+ DataMember = QueryName;
+ }
+
+ private static string ToDevExpressConnectionString(string connectionString, DataSourceTypeEnum dataSourceType)
+ {
+ if (string.IsNullOrWhiteSpace(connectionString) ||
+ connectionString.Contains("XpoProvider=", StringComparison.OrdinalIgnoreCase))
+ {
+ return connectionString;
+ }
+
+ var provider = dataSourceType switch
+ {
+ DataSourceTypeEnum.Mssql => "MSSqlServer",
+ DataSourceTypeEnum.Postgresql => "Postgres",
+ _ => null
+ };
+
+ return string.IsNullOrWhiteSpace(provider)
+ ? connectionString
+ : $"XpoProvider={provider};{connectionString}";
+ }
+
+ private static string ApplyQueryParameters(string sql, Dictionary parameters)
+ {
+ if (string.IsNullOrWhiteSpace(sql) || parameters == null || parameters.Count == 0)
+ {
+ return sql;
+ }
+
+ foreach (var parameter in parameters.OrderByDescending(x => x.Key.Length))
+ {
+ var key = parameter.Key.StartsWith("@") ? parameter.Key : "@" + parameter.Key;
+ sql = Regex.Replace(
+ sql,
+ Regex.Escape(key) + @"\b",
+ ToSqlLiteral(parameter.Value),
+ RegexOptions.CultureInvariant);
+ }
+
+ return sql;
+ }
+
+ private static string ToSqlLiteral(object value)
+ {
+ if (value == null || value == DBNull.Value)
+ {
+ return "NULL";
+ }
+
+ return value switch
+ {
+ string text => $"'{text.Replace("'", "''")}'",
+ Guid guid => $"'{guid}'",
+ DateTime date => $"'{date:yyyy-MM-dd HH:mm:ss.fff}'",
+ DateTimeOffset date => $"'{date:yyyy-MM-dd HH:mm:ss.fff zzz}'",
+ bool boolean => boolean ? "1" : "0",
+ byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal
+ => Convert.ToString(value, CultureInfo.InvariantCulture),
+ _ => $"'{value.ToString()?.Replace("'", "''")}'"
+ };
+ }
+
+ private void BuildLayout(
+ ListForm listForm,
+ IReadOnlyList fields,
+ IStringLocalizer localizer)
+ {
+ Bands.Clear();
+
+ var topMargin = new TopMarginBand { HeightF = Margins.Top };
+ var bottomMargin = new BottomMarginBand { HeightF = 32 };
+ var reportHeader = new ReportHeaderBand { HeightF = 42 };
+ var pageHeader = new PageHeaderBand { HeightF = 28 };
+ var detail = new DetailBand { HeightF = 24 };
+ var reportFooter = new ReportFooterBand { HeightF = HasAnySummary(fields) ? 28 : 0 };
+
+ Bands.AddRange([topMargin, reportHeader, pageHeader, detail, reportFooter, bottomMargin]);
+
+ var pageWidth = PageWidth - Margins.Left - Margins.Right;
+ reportHeader.Controls.Add(new XRLabel
+ {
+ Text = GetLocalizedReportTitle(listForm, localizer),
+ BoundsF = new System.Drawing.RectangleF(0, 0, pageWidth, 30),
+ Font = new DXFont("Arial", 14F, DXFontStyle.Bold),
+ TextAlignment = TextAlignment.MiddleLeft
+ });
+
+ var generatedAt = new XRPageInfo
+ {
+ PageInfo = PageInfo.DateTime,
+ TextFormatString = "{0:dd.MM.yyyy HH:mm}",
+ BoundsF = new System.Drawing.RectangleF(0, 30, pageWidth / 2, 12),
+ Font = new DXFont("Arial", 7.5F),
+ TextAlignment = TextAlignment.MiddleLeft
+ };
+ reportHeader.Controls.Add(generatedAt);
+
+ var pageInfo = new XRPageInfo
+ {
+ TextFormatString = "Sayfa {0} / {1}",
+ BoundsF = new System.Drawing.RectangleF(pageWidth / 2, 0, pageWidth / 2, 18),
+ Font = new DXFont("Arial", 8F),
+ TextAlignment = TextAlignment.MiddleRight
+ };
+ bottomMargin.Controls.Add(pageInfo);
+
+ var widths = CalculateWidths(fields, pageWidth);
+ var headerTable = CreateTable(pageWidth, 28);
+ var headerRow = new XRTableRow();
+ var detailTable = CreateTable(pageWidth, 24);
+ var detailRow = new XRTableRow();
+ var footerTable = CreateTable(pageWidth, 24);
+ var footerRow = new XRTableRow();
+
+ for (var i = 0; i < fields.Count; i++)
+ {
+ var field = fields[i];
+ headerRow.Cells.Add(CreateHeaderCell(field, widths[i], localizer));
+ detailRow.Cells.Add(CreateDetailCell(field, widths[i]));
+ footerRow.Cells.Add(CreateFooterCell(field, widths[i]));
+ }
+
+ headerTable.Rows.Add(headerRow);
+ detailTable.Rows.Add(detailRow);
+ footerTable.Rows.Add(footerRow);
+
+ pageHeader.Controls.Add(headerTable);
+ detail.Controls.Add(detailTable);
+ if (HasAnySummary(fields))
+ {
+ reportFooter.Controls.Add(footerTable);
+ }
+ }
+
+ private static XRTable CreateTable(float width, float height)
+ {
+ return new XRTable
+ {
+ BoundsF = new System.Drawing.RectangleF(0, 0, width, height),
+ Borders = BorderSide.All,
+ BorderWidth = 0.5F
+ };
+ }
+
+ private static XRTableCell CreateHeaderCell(
+ ReportColumn column,
+ float width,
+ IStringLocalizer localizer)
+ {
+ var field = column.Field;
+ return new XRTableCell
+ {
+ Text = GetLocalizedFieldCaption(field, localizer),
+ WidthF = width,
+ Font = new DXFont("Arial", 8F, DXFontStyle.Bold),
+ BackColor = System.Drawing.Color.FromArgb(238, 242, 247),
+ Padding = new PaddingInfo(4, 4, 2, 2),
+ TextAlignment = TextAlignment.MiddleCenter,
+ Multiline = true
+ };
+ }
+
+ private static string GetLocalizedReportTitle(ListForm listForm, IStringLocalizer localizer)
+ {
+ var title = string.IsNullOrWhiteSpace(listForm.Title) ? listForm.Name : listForm.Title;
+ return Localize(localizer, title, listForm.ListFormCode);
+ }
+
+ private static string GetLocalizedFieldCaption(ListFormField field, IStringLocalizer localizer)
+ {
+ var caption = string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName;
+ return Localize(localizer, caption, field.FieldName);
+ }
+
+ private static string Localize(IStringLocalizer localizer, string key, string fallback)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ {
+ return fallback;
+ }
+
+ var localized = localizer[key];
+ return localized.ResourceNotFound || string.IsNullOrWhiteSpace(localized.Value)
+ ? key
+ : localized.Value;
+ }
+
+ private static XRTableCell CreateDetailCell(ReportColumn column, float width)
+ {
+ var field = column.Field;
+ var cell = new XRTableCell
+ {
+ WidthF = width,
+ Font = new DXFont("Arial", 8F),
+ Padding = new PaddingInfo(4, 4, 2, 2),
+ TextAlignment = ToTextAlignment(field.Alignment, field.SourceDbType),
+ Multiline = false,
+ CanGrow = false
+ };
+ cell.ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", $"[{column.BindingName}]"));
+ return cell;
+ }
+
+ private static XRTableCell CreateFooterCell(ReportColumn column, float width)
+ {
+ var field = column.Field;
+ var summaryType = GetSummaryType(field.TotalSummaryJson);
+ var cell = new XRTableCell
+ {
+ WidthF = width,
+ Font = new DXFont("Arial", 8F, DXFontStyle.Bold),
+ Padding = new PaddingInfo(4, 4, 2, 2),
+ TextAlignment = ToTextAlignment(field.Alignment, field.SourceDbType)
+ };
+
+ if (summaryType == null)
+ {
+ return cell;
+ }
+
+ cell.ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", $"sumSum([{column.BindingName}])"));
+ cell.Summary = new XRSummary
+ {
+ Running = SummaryRunning.Report,
+ Func = summaryType.Value,
+ IgnoreNullValues = true
+ };
+
+ return cell;
+ }
+
+ private static float[] CalculateWidths(IReadOnlyList fields, float pageWidth)
+ {
+ var raw = fields.Select(x => (float)Math.Max(x.Width ?? 90, 60)).ToArray();
+ var total = raw.Sum();
+ if (total <= 0)
+ {
+ return fields.Select(_ => pageWidth / fields.Count).ToArray();
+ }
+
+ return raw.Select(x => pageWidth * x / total).ToArray();
+ }
+
+ private static float[] CalculateWidths(IReadOnlyList fields, float pageWidth)
+ {
+ var raw = fields.Select(x => (float)Math.Max(x.Field.Width ?? 90, 60)).ToArray();
+ var total = raw.Sum();
+ if (total <= 0)
+ {
+ return fields.Select(_ => pageWidth / fields.Count).ToArray();
+ }
+
+ return raw.Select(x => pageWidth * x / total).ToArray();
+ }
+
+ private static bool HasAnySummary(IEnumerable fields)
+ {
+ return fields.Any(x => GetSummaryType(x.Field.TotalSummaryJson) != null);
+ }
+
+ private static string GetReportFieldName(SelectField selectField)
+ {
+ if (selectField?.JoinOptions != null && !string.IsNullOrWhiteSpace(selectField.JoinOptions.FieldNameAlias))
+ {
+ return selectField.JoinOptions.FieldNameAlias;
+ }
+
+ return selectField?.FieldName;
+ }
+
+ private sealed record ReportColumn(ListFormField Field, string BindingName);
+
+ private static SummaryFunc? GetSummaryType(string totalSummaryJson)
+ {
+ if (string.IsNullOrWhiteSpace(totalSummaryJson))
+ {
+ return null;
+ }
+
+ var value = totalSummaryJson.ToLowerInvariant();
+ if (value.Contains("\"summarytype\":\"sum\""))
+ {
+ return SummaryFunc.Sum;
+ }
+ if (value.Contains("\"summarytype\":\"avg\""))
+ {
+ return SummaryFunc.Avg;
+ }
+ if (value.Contains("\"summarytype\":\"min\""))
+ {
+ return SummaryFunc.Min;
+ }
+ if (value.Contains("\"summarytype\":\"max\""))
+ {
+ return SummaryFunc.Max;
+ }
+ if (value.Contains("\"summarytype\":\"count\""))
+ {
+ return SummaryFunc.Count;
+ }
+
+ return null;
+ }
+
+ private static TextAlignment ToTextAlignment(string alignment, DbType dbType)
+ {
+ return alignment?.ToLowerInvariant() switch
+ {
+ "center" => TextAlignment.MiddleCenter,
+ "right" => TextAlignment.MiddleRight,
+ "left" => TextAlignment.MiddleLeft,
+ _ => IsNumeric(dbType) ? TextAlignment.MiddleRight : TextAlignment.MiddleLeft
+ };
+ }
+
+ private static bool IsNumeric(DbType dbType)
+ {
+ return dbType is DbType.Currency or DbType.Decimal or DbType.Double or DbType.Int16 or DbType.Int32
+ or DbType.Int64 or DbType.Single or DbType.UInt16 or DbType.UInt32 or DbType.UInt64;
+ }
+
+ private static string GetParameter(IDictionary parameters, string name)
+ {
+ return parameters.TryGetValue(name, out var value) ? value : null;
+ }
+
+ private static Dictionary ToStringValues(IDictionary parameters)
+ {
+ return parameters.ToDictionary(x => x.Key, x => new StringValues(x.Value));
+ }
+}
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/ReportServices/CustomReportStorageWebExtension.cs b/api/src/Sozsoft.Platform.HttpApi.Host/ReportServices/CustomReportStorageWebExtension.cs
index b929a28..90acbf2 100644
--- a/api/src/Sozsoft.Platform.HttpApi.Host/ReportServices/CustomReportStorageWebExtension.cs
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/ReportServices/CustomReportStorageWebExtension.cs
@@ -21,6 +21,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
// TemplateReport için ServiceProvider'ı set et
Sozsoft.Reports.PredefinedReports.TemplateReport.SetServiceProvider(serviceProvider);
+ Sozsoft.Reports.PredefinedReports.DynamicListFormReport.SetServiceProvider(serviceProvider);
if (!Directory.Exists(reportDirectory))
{
@@ -34,6 +35,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
// TemplateReport için ServiceProvider'ı set et
Sozsoft.Reports.PredefinedReports.TemplateReport.SetServiceProvider(serviceProvider);
+ Sozsoft.Reports.PredefinedReports.DynamicListFormReport.SetServiceProvider(serviceProvider);
if (!Directory.Exists(reportDirectory))
{
@@ -65,8 +67,8 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
// Implement your own logic to prohibit URLs that contain spaces or other specific characters.
// Return **true** if no validation is required.
- // Query string varsa sadece rapor adını kontrol et
- string reportName = url.Contains("?") ? url.Split('?')[0] : url;
+ // Query string encoded gelse bile sadece rapor adını kontrol et.
+ var (reportName, _) = ParseReportUrl(url);
return Path.GetFileName(reportName) == reportName;
}
@@ -78,26 +80,14 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
// if the parameters are included in the report URL's query string.
try
{
- // URL'den rapor adını ve parametreleri ayır
- string reportName = url;
- Dictionary parameters = new Dictionary();
+ var (reportName, parameters) = ParseReportUrl(url);
- if (url.Contains("?"))
+ if (reportName == Sozsoft.Reports.PredefinedReports.DynamicListFormReport.ReportName)
{
- var parts = url.Split('?');
- reportName = parts[0];
-
- // Query string parametrelerini parse et
- var queryString = parts[1];
- var paramPairs = queryString.Split('&');
- foreach (var pair in paramPairs)
- {
- var keyValue = pair.Split('=');
- if (keyValue.Length == 2)
- {
- parameters[keyValue[0]] = Uri.UnescapeDataString(keyValue[1]);
- }
- }
+ var report = Sozsoft.Reports.PredefinedReports.DynamicListFormReport.Create(parameters);
+ using MemoryStream ms = new MemoryStream();
+ report.SaveLayoutToXml(ms);
+ return ms.ToArray();
}
// Önce Reports klasöründen yüklemeyi dene
@@ -177,6 +167,51 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
throw new FaultException(new FaultReason(string.Format("Could not find report '{0}'.", url)), new FaultCode("Server"), "GetData");
}
+ private static (string ReportName, Dictionary Parameters) ParseReportUrl(string url)
+ {
+ var decodedUrl = DecodeRepeatedly(url);
+ var reportName = decodedUrl;
+ var parameters = new Dictionary();
+
+ var queryIndex = decodedUrl.IndexOf('?');
+ if (queryIndex < 0)
+ {
+ return (reportName, parameters);
+ }
+
+ 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]))
+ {
+ parameters[keyValue[0]] = keyValue[1];
+ }
+ }
+
+ return (reportName, parameters);
+ }
+
+ private static string DecodeRepeatedly(string value)
+ {
+ var decoded = value;
+ for (var i = 0; i < 3; i++)
+ {
+ var next = Uri.UnescapeDataString(decoded);
+ if (next == decoded)
+ {
+ return decoded;
+ }
+
+ decoded = next;
+ }
+
+ return decoded;
+ }
+
public override Dictionary GetUrls()
{
// Returns a dictionary that contains the report names (URLs) and display names.
diff --git a/ui/src/routes/route.constant.ts b/ui/src/routes/route.constant.ts
index 809c3cd..739d754 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: {
- generator: '/admin/reports/generator',
- view: '/admin/reports/:id',
+ view: '/admin/reports/:id/view',
+ design: '/admin/reports/:id/design',
},
},
admin: {
diff --git a/ui/src/views/report/DevexpressReportViewer.tsx b/ui/src/views/report/DevexpressReportViewer.tsx
index 17dbefb..7ef5c17 100644
--- a/ui/src/views/report/DevexpressReportViewer.tsx
+++ b/ui/src/views/report/DevexpressReportViewer.tsx
@@ -1,15 +1,14 @@
-import React, { useMemo, lazy, Suspense, useEffect } from 'react'
+import React, { useMemo, useEffect, useState } from 'react'
import { Container } from '@/components/shared'
import { Helmet } from 'react-helmet'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { useParams, useLocation } from 'react-router-dom'
-import { RequestOptions } from 'devexpress-reporting-react/dx-report-viewer'
+import ReportViewer, {
+ RequestOptions,
+} from 'devexpress-reporting-react/dx-report-viewer'
+import { ajaxSetup } from '@devexpress/analytics-core/analytics-utils-native'
import { APP_NAME } from '@/constants/app.constant'
-
-// Lazy load
-const ReportViewer = lazy(() =>
- import('devexpress-reporting-react/dx-report-viewer')
-)
+import { useStoreState } from '@/store'
const loadViewerCss = () => {
const styles = [
@@ -31,11 +30,33 @@ const DevexpressReportViewer: React.FC = () => {
const { translate } = useLocalization()
const { id } = useParams<{ id: string }>()
const location = useLocation()
+ const token = useStoreState((state) => state.auth.session.token)
+ const [isRequestAuthReady, setIsRequestAuthReady] = useState(false)
useEffect(() => {
loadViewerCss()
}, [])
+ useEffect(() => {
+ setIsRequestAuthReady(false)
+ const authorization = token ? `Bearer ${token}` : undefined
+
+ const ajaxHeaders = ajaxSetup.ajaxSettings.headers ?? {}
+
+ if (authorization) {
+ ajaxSetup.ajaxSettings.headers = {
+ ...ajaxHeaders,
+ Authorization: authorization,
+ }
+ setIsRequestAuthReady(true)
+ return
+ }
+
+ const { Authorization: _ajaxAuthorization, ...nextAjaxHeaders } = ajaxHeaders
+ ajaxSetup.ajaxSettings.headers = nextAjaxHeaders
+ setIsRequestAuthReady(true)
+ }, [token])
+
const reportUrlWithParams = useMemo(() => {
if (!id) return ''
return location.search ? `${id}${location.search}` : id
@@ -51,14 +72,14 @@ const DevexpressReportViewer: React.FC = () => {
defaultTitle={APP_NAME}
/>
- Rapor yükleniyor...}>
+ {isRequestAuthReady && (
-
+ )}
)
}