From b9cb52927f9dda8760f57fd38e90f24b6ae0f522 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Oct 2025 14:42:41 +0100 Subject: [PATCH] paging, filtering, AI call changes --- .../Controllers/CustomOrderController.cs | 74 ++++- .../Controllers/ManagementPageController.cs | 306 +++++++++--------- .../Models/Order/OrderSearchModelExtended.cs | 3 +- .../Order/FileUploadGridComponent.cshtml | 30 +- .../Areas/Admin/Views/Order/List.cshtml | 6 +- .../Areas/Admin/Views/Product/List.cshtml | 2 +- .../Factories/MgBase/MgOrderModelFactory.cs | 9 +- .../Infrastructure/RouteProvider.cs | 5 + .../Services/AICalculationService.cs | 2 +- 9 files changed, 279 insertions(+), 158 deletions(-) diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs index e9aae89..f95efb3 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs @@ -163,6 +163,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers OrderStatusIds = orderStatuses, PaymentStatusIds = paymentStatuses, ShippingStatusIds = shippingStatuses, + AvailablePageSizes = "20,50,100,500", + }); return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/List.cshtml", model); @@ -214,12 +216,79 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/Edit.cshtml", model); } + //public async Task GetOrderListModelByFilter(OrderSearchModelExtended searchModel) + //{ + // //return _customOrderService. + // var orderListModel = await _orderModelFactory.PrepareOrderListModelExtendedAsync(searchModel); + + // _logger.Detail($"Total: {orderListModel.RecordsTotal}, Data Count: {orderListModel.Data.Count()}"); + // foreach (var item in orderListModel.Data.Take(3)) + // { + // _logger.Detail($"Order: {item.Id}, {item.CustomOrderNumber}"); + // } + + // return orderListModel; + //} + public async Task GetOrderListModelByFilter(OrderSearchModelExtended searchModel) { - //return _customOrderService. + + var sortColumnIndex = Request.Form["order[0][column]"].FirstOrDefault(); + var sortDirection = Request.Form["order[0][dir]"].FirstOrDefault(); + + if (!string.IsNullOrEmpty(sortColumnIndex)) + { + // Get the column name from the column index + var columnName = Request.Form[$"columns[{sortColumnIndex}][data]"].FirstOrDefault(); + + searchModel.SortColumn = columnName; + searchModel.SortColumnDirection = sortDirection; // "asc" or "desc" + } + + // Get the paginated data var orderListModel = await _orderModelFactory.PrepareOrderListModelExtendedAsync(searchModel); - + _logger.Detail($"Total: {orderListModel.RecordsTotal}, Data Count: {orderListModel.Data.Count()}"); + + // Apply sorting if specified + if (!string.IsNullOrEmpty(searchModel.SortColumn) && orderListModel.Data.Any()) + { + var sortedData = orderListModel.Data.AsQueryable(); + + sortedData = searchModel.SortColumn.ToLowerInvariant() switch + { + "id" => searchModel.SortColumnDirection == "asc" + ? sortedData.OrderBy(o => o.Id) + : sortedData.OrderByDescending(o => o.Id), + "customercompany" => searchModel.SortColumnDirection == "asc" + ? sortedData.OrderBy(o => o.CustomerCompany) + : sortedData.OrderByDescending(o => o.CustomerCompany), + "customordernumber" => searchModel.SortColumnDirection == "asc" + ? sortedData.OrderBy(o => o.CustomOrderNumber) + : sortedData.OrderByDescending(o => o.CustomOrderNumber), + "ordertotal" => searchModel.SortColumnDirection == "asc" + ? sortedData.OrderBy(o => o.OrderTotal) + : sortedData.OrderByDescending(o => o.OrderTotal), + "createdon" => searchModel.SortColumnDirection == "asc" + ? sortedData.OrderBy(o => o.CreatedOn) + : sortedData.OrderByDescending(o => o.CreatedOn), + "orderstatusid" => searchModel.SortColumnDirection == "asc" + ? sortedData.OrderBy(o => o.OrderStatusId) + : sortedData.OrderByDescending(o => o.OrderStatusId), + "paymentstatusid" => searchModel.SortColumnDirection == "asc" + ? sortedData.OrderBy(o => o.PaymentStatusId) + : sortedData.OrderByDescending(o => o.PaymentStatusId), + "shippingstatusid" => searchModel.SortColumnDirection == "asc" + ? sortedData.OrderBy(o => o.ShippingStatusId) + : sortedData.OrderByDescending(o => o.ShippingStatusId), + _ => sortedData + }; + + orderListModel.Data = sortedData.ToList(); + + _logger.Detail($"Sorted by {searchModel.SortColumn} {searchModel.SortColumnDirection}"); + } + foreach (var item in orderListModel.Data.Take(3)) { _logger.Detail($"Order: {item.Id}, {item.CustomOrderNumber}"); @@ -228,6 +297,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers return orderListModel; } + public virtual IActionResult Test() { // Your custom logic here diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs index c29ebce..28be353 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs @@ -219,15 +219,32 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers return Json(model); } - - [HttpPost] + public async Task ProcessShippingDocument(int shippingDocumentId) + { + return Ok("Ok"); + } + + + [HttpPost] [RequestSizeLimit(10485760)] // 10MB [RequestFormLimits(MultipartBodyLengthLimit = 10485760)] public async Task UploadFile(List files, int shippingDocumentId, int? partnerId) { + //an empty shippingdocument created by the user var shippingDocument = await _dbContext.ShippingDocuments.GetByIdAsync(shippingDocumentId); + + + + ShippingDocumentAnalysisResult shippingDocumentAnalysisResult = new ShippingDocumentAnalysisResult(); + shippingDocumentAnalysisResult.Partner = new Partner(); + shippingDocumentAnalysisResult.ShippingDocument = shippingDocument; + shippingDocumentAnalysisResult.ShippingItems = new List(); + shippingDocumentAnalysisResult.ShippingDocument.ShippingDocumentToFiles = new List(); + + + //checks // - files exist if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) @@ -243,7 +260,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers _logger.Debug($"Associated with Partner ID: {partnerId.Value}"); //let's get the partner - var partner = await _dbContext.Partners.GetByIdAsync(partnerId.Value); + shippingDocumentAnalysisResult.Partner = await _dbContext.Partners.GetByIdAsync(partnerId.Value); } var filesList = new List(); @@ -252,34 +269,34 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers //iteratation 1: iterate documents to determine their type by AI - foreach (var file in files) + for (int i = 0; i < files.Count; i++) { - var fileName = file.FileName; - var fileSize = file.Length; + var fileName = files[i].FileName; + var fileSize = files[i].Length; var dbFile = new Files(); string pdfText = ""; - _logger.Detail($"Received file: {fileName} for Document ID: {shippingDocumentId}, content type: {file.ContentType}"); + _logger.Detail($"Received file: {fileName} for Document ID: {shippingDocumentId}, content type: {files[i].ContentType}"); - if (!file.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase) && !file.ContentType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase)) + if (!files[i].ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase) && !files[i].ContentType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase)) return Json(new { success = false, errorMessage = "Only PDF or jpg files are allowed" }); // Validate file size (max 20MB) - if (file.Length > 20 * 1024 * 1024) + if (files[i].Length > 20 * 1024 * 1024) return Json(new { success = false, errorMessage = "File size must be less than 10MB" }); // - get text extracted from pdf // Validate file type (PDF only) //if (file.Length > 0 && file.ContentType == "application/pdf") - if (file.Length > 0) + if (files[i].Length > 0) { - if (file.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase)) + if (files[i].ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase)) { try { // Open the PDF from the IFormFile's stream directly in memory - using (var stream = file.OpenReadStream()) + using (var stream = files[i].OpenReadStream()) using (var pdf = UglyToad.PdfPig.PdfDocument.Open(stream)) { // Now you can analyze the PDF content @@ -296,7 +313,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers try { // ✅ Use the service we implemented earlier - pdfText = await _openAIApiService.AnalyzePdfAsync(stream, file.FileName, "Please extract all readable text from this PDF."); + pdfText = await _openAIApiService.AnalyzePdfAsync(stream, files[i].FileName, "Please extract all readable text from this PDF."); } catch (Exception aiEx) { @@ -306,14 +323,14 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers } // For demonstration, let's just log the extracted text - _logger.Detail($"Extracted text from {file.FileName}: {pdfText}"); + _logger.Detail($"Extracted text from {files[i].FileName}: {pdfText}"); } } catch (Exception ex) { // Handle potential exceptions during PDF processing - Console.Error.WriteLine($"Error processing PDF file {file.FileName}: {ex.Message}"); + Console.Error.WriteLine($"Error processing PDF file {files[i].FileName}: {ex.Message}"); return StatusCode(500, $"Error processing PDF file: {ex.Message}"); } } @@ -322,13 +339,13 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers try { // Open the Image from the IFormFile's stream directly in memory - using (var stream = file.OpenReadStream()) + using (var stream = files[i].OpenReadStream()) { try { // ✅ Use the service we implemented earlier - pdfText = await _openAIApiService.AnalyzePdfAsync(stream, file.FileName, "Please extract all readable text from this image."); + pdfText = await _openAIApiService.AnalyzePdfAsync(stream, files[i].FileName, "Please extract all readable text from this image."); } catch (Exception aiEx) { @@ -337,7 +354,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers } // For demonstration, let's just log the extracted text - _logger.Detail($"Extracted text from {file.FileName}: {pdfText}"); + _logger.Detail($"Extracted text from {files[i].FileName}: {pdfText}"); } } @@ -345,7 +362,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers catch (Exception ex) { // Handle potential exceptions during PDF processing - Console.Error.WriteLine($"Error processing PDF file {file.FileName}: {ex.Message}"); + Console.Error.WriteLine($"Error processing PDF file {files[i].FileName}: {ex.Message}"); return StatusCode(500, $"Error processing PDF file: {ex.Message}"); } } @@ -362,31 +379,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers var metaAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText.ToString(), analysisPrompt); var extractedMetaData = ParseMetaDataAIResponse(metaAnalyzis); - - if (extractedMetaData.DocumentNumber != null) - { - dbFile.RawText = pdfText; - dbFile.FileExtension = "pdf"; - dbFile.FileName = extractedMetaData.DocumentNumber; - } - - var transactionSuccess = await _dbContext.TransactionSafeAsync(async _ => - { - await _dbContext.Files.InsertAsync(dbFile); - filesList.Add(dbFile); - - var shippingDocumentToFiles = new ShippingDocumentToFiles - { - ShippingDocumentId = shippingDocumentId, - FilesId = dbFile.Id, - DocumentType = extractedMetaData.DocumentType != null ? (DocumentType)Enum.Parse(typeof(DocumentType), extractedMetaData.DocumentType) : DocumentType.Unknown - }; - - _logger.Detail(shippingDocumentToFiles.DocumentType.ToString()); - - await _dbContext.ShippingDocumentToFiles.InsertAsync(shippingDocumentToFiles); - return true; - }); + bool transactionSuccess = await SaveFileInfoToDB(shippingDocumentId, shippingDocumentAnalysisResult, filesList, dbFile, pdfText, extractedMetaData); if (!transactionSuccess) _logger.Error($"(transactionSuccess == false)"); @@ -399,12 +392,12 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers //find partner in DB... there is a serious chance that the partner Name and Taxid determines the partner var partners = await _dbContext.Partners.GetAll().ToListAsync(); - foreach (var partner in partners) + foreach (var dbpartner in partners) { - if (pdfText.Contains(partner.Name) || (partner.TaxId != null && pdfText.Contains(partner.TaxId))) + if (pdfText.Contains(dbpartner.Name) || (dbpartner.TaxId != null && pdfText.Contains(dbpartner.TaxId))) { - partnerId = partner.Id; - _logger.Detail($"Found existing partner in DB: {partner.Name} (ID: {partner.Id})"); + partnerId = dbpartner.Id; + _logger.Detail($"Found existing partner in DB: {dbpartner.Name} (ID: {dbpartner.Id})"); break; } } @@ -413,48 +406,13 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers { _logger.Detail("No existing partner found in DB, proceeding to extract partner info via AI."); - string partnerAnalysisPrompt = "You are an agent of Fruitbank, helping to analyze pdf content. Determine which partner of Fruitbank sent this document, extract their data and return them as JSON: name, taxId, certificationNumber, postalCode, country, state, county, city, street. " + - "If you can't find information of any of these, return null value for that field."; + // string partnerAnalysisPrompt = "You are an agent of Fruitbank, helping to analyze pdf content. Determine which partner of Fruitbank sent this document, extract their data and return them as JSON: name, taxId, certificationNumber, postalCode, country, state, county, city, street. " + + //"If you can't find information of any of these, return null value for that field."; - //here I can start preparing the file entity - var partnerAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText.ToString(), partnerAnalysisPrompt); - var extractedPartnerData = ParsePartnerDataAIResponse(partnerAnalyzis); + // //here I can start preparing the file entity + // var partnerAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText.ToString(), partnerAnalysisPrompt); - if (extractedPartnerData.Name != null) - { - _logger.Detail("AI Analysis Partner Result:"); - _logger.Detail(extractedPartnerData.Name); - } - - if (extractedPartnerData.TaxId != null) - { - _logger.Detail(extractedPartnerData.TaxId); - } - - if (extractedPartnerData.Country != null) - { - - _logger.Detail(extractedPartnerData.Country); - } - - if (extractedPartnerData.State != null) - { - _logger.Detail(extractedPartnerData.State); - } - - } - - } - - - //shortcut - - - try - { - - - var partnerPrompt = @"Extract the partner/company information from the following text and return ONLY a valid JSON object with these exact fields: + var partnerPrompt = @"Extract the partner/company information from the following text and return ONLY a valid JSON object with these exact fields: { ""Name"": ""company name"", ""TaxId"": ""tax identification number"", @@ -472,13 +430,54 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers Text to analyze: " + pdfText; - var partnerResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( - pdfText, - partnerPrompt - ); + var partnerResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( + pdfText, + partnerPrompt + ); + + shippingDocumentAnalysisResult.Partner = JsonSerializer.Deserialize(CleanJsonResponse(partnerResponse), + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + + if (shippingDocumentAnalysisResult.Partner.Name != null) + { + _logger.Detail("AI Analysis Partner Result:"); + _logger.Detail(shippingDocumentAnalysisResult.Partner.Name); + } + + if (shippingDocumentAnalysisResult.Partner.TaxId != null) + { + _logger.Detail(shippingDocumentAnalysisResult.Partner.TaxId); + } + + if (shippingDocumentAnalysisResult.Partner.Country != null) + { + + _logger.Detail(shippingDocumentAnalysisResult.Partner.Country); + } + + if (shippingDocumentAnalysisResult.Partner.State != null) + { + _logger.Detail(shippingDocumentAnalysisResult.Partner.State); + } + + if (shippingDocumentAnalysisResult.Partner != null) + { + shippingDocumentAnalysisResult.Partner = shippingDocumentAnalysisResult.Partner; + } + + + } + + } + + + //shortcut + + + try + { - var partner = JsonSerializer.Deserialize(CleanJsonResponse(partnerResponse), - new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); // Step 2: Extract Shipping Document Information var shippingDocPrompt = @"Extract the shipping document information from the following text and return ONLY a valid JSON object with these exact fields: @@ -504,20 +503,20 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers // Step 3: Extract Shipping Items var itemsPrompt = @"Extract all shipping items (fruits, vegetables, or products) from the following text and return ONLY a valid JSON array with objects having these exact fields: -[ - { - ""Name"": ""product name"", - ""PalletsOnDocument"": number_of_pallets, - ""QuantityOnDocument"": quantity_count, - ""NetWeightOnDocument"": net_weight_in_kg, - ""GrossWeightOnDocument"": gross_weight_in_kg - } -] + [ + { + ""Name"": ""product name"", + ""PalletsOnDocument"": number_of_pallets, + ""QuantityOnDocument"": quantity_count, + ""NetWeightOnDocument"": net_weight_in_kg, + ""GrossWeightOnDocument"": gross_weight_in_kg + } + ] -If a numeric field is not found, use 0. Return ONLY the JSON array, no additional text or explanation. + If a numeric field is not found, use 0. Return ONLY the JSON array, no additional text or explanation. -Text to analyze: -" + pdfText; + Text to analyze: + " + pdfText; var itemsResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( pdfText, @@ -530,7 +529,7 @@ Text to analyze: // Prepare result var result = new ShippingDocumentAnalysisResult { - Partner = partner, + Partner = shippingDocumentAnalysisResult.Partner, ShippingDocument = new ShippingDocument { DocumentIdNumber = shippingDocData.DocumentIdNumber, @@ -541,56 +540,40 @@ Text to analyze: ShippingItems = items }; - return Ok(result); + //return Ok(result); } catch (JsonException ex) { return BadRequest($"Failed to parse AI response: {ex.Message}"); } - catch (Exception ex) - { - return StatusCode(500, $"An error occurred: {ex.Message}"); - } - - - - - - - - - - - - - - - + // - save the documents to file system - wwwroot/uploads/orders/order-{orderId}/fileId-documentId.pdf // where documentId is the number or id IN the document - //try - //{ - // // Create upload directory if it doesn't exist - // var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "orders", "order" + shippingDocumentId.ToString(), "documents"); - // Directory.CreateDirectory(uploadsPath); + try + { + // Create upload directory if it doesn't exist + var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "orders", "order" + shippingDocumentId.ToString(), "documents"); + Directory.CreateDirectory(uploadsPath); - // // Generate unique filename - // fileName = $"{Guid.NewGuid()}_{file.FileName}"; - // var filePath = Path.Combine(uploadsPath, fileName); + // Generate unique filename + fileName = $"{Guid.NewGuid()}_{files[i].FileName}"; + var filePath = Path.Combine(uploadsPath, fileName); - // // Save file - // using (var stream = new FileStream(filePath, FileMode.Create)) - // { - // await file.CopyToAsync(stream); - // } - //} - //catch (Exception ex) - //{ - // _logger.Error($"Error saving file: {ex.Message}", ex); - // //return Json(new { success = false, errorMessage = ex.Message }); - // return BadRequest("No files were uploaded."); - //} + // Save file + using (var stream = new FileStream(filePath, FileMode.Create)) + { + await files[i].CopyToAsync(stream); + } + + return Ok(shippingDocumentAnalysisResult); + } + catch (Exception ex) + { + _logger.Error($"Error saving file: {ex.Message}", ex); + //return Json(new { success = false, errorMessage = ex.Message }); + return BadRequest("No files were uploaded."); + } // - create a list of documents to read information and to save the document's information to DB @@ -680,6 +663,35 @@ Text to analyze: return Ok($"Files for Shipping Document ID {shippingDocumentId} were uploaded successfully!"); } + private async Task SaveFileInfoToDB(int shippingDocumentId, ShippingDocumentAnalysisResult shippingDocumentAnalysisResult, List filesList, Files dbFile, string pdfText, ExtractedDocumentMetaData extractedMetaData) + { + if (extractedMetaData.DocumentNumber != null) + { + dbFile.RawText = pdfText; + dbFile.FileExtension = "pdf"; + dbFile.FileName = extractedMetaData.DocumentNumber; + } + + var transactionSuccess = await _dbContext.TransactionSafeAsync(async _ => + { + await _dbContext.Files.InsertAsync(dbFile); + filesList.Add(dbFile); + + var shippingDocumentToFiles = new ShippingDocumentToFiles + { + ShippingDocumentId = shippingDocumentId, + FilesId = dbFile.Id, + DocumentType = extractedMetaData.DocumentType != null ? (DocumentType)Enum.Parse(typeof(DocumentType), extractedMetaData.DocumentType) : DocumentType.Unknown + }; + + _logger.Detail(shippingDocumentToFiles.DocumentType.ToString()); + shippingDocumentAnalysisResult.ShippingDocument.ShippingDocumentToFiles.Add(shippingDocumentToFiles); + await _dbContext.ShippingDocumentToFiles.InsertAsync(shippingDocumentToFiles); + return true; + }); + return transactionSuccess; + } + //[HttpPost] //public async Task UploadFile(List files, int shippingDocumentId) diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/Order/OrderSearchModelExtended.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/Order/OrderSearchModelExtended.cs index 05ec6ac..011b20a 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/Order/OrderSearchModelExtended.cs +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/Order/OrderSearchModelExtended.cs @@ -15,7 +15,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.Order [NopResourceDisplayName("Admin.Orders.List.BillingCompany")] public string BillingCompany { get; set; } - //public IList AvailableCompanies { get; set; } + public string SortColumn { get; set; } + public string SortColumnDirection { get; set; } } } diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/FileUploadGridComponent.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/FileUploadGridComponent.cshtml index ad331a6..133fa96 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/FileUploadGridComponent.cshtml +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/FileUploadGridComponent.cshtml @@ -11,8 +11,7 @@
-
- +
@(Html.DevExtreme().FileUploader() .ID("shippingDocumentUploader-" + contextId) @@ -31,6 +30,12 @@ .UseSubmitBehavior(true) )
+ + + +

Selected Files

@@ -56,6 +61,27 @@