From 3f44b23f578f6fafc986cca15b09ed0cf880a227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Wed, 13 May 2026 22:07:50 +0300 Subject: [PATCH] ImportManager problemi giderildi --- .../ListForms/ListFormImportAppService.cs | 23 +- .../Seeds/LanguagesData.json | 78 ++++++ .../Seeds/ListFormSeeder_DefaultJsons.cs | 2 +- .../Seeds/ListFormSeeder_Saas.cs | 2 +- .../importManager/ImportDashboard.tsx | 227 ++++++++++++++++-- ui/src/proxy/imports/models.ts | 5 +- ui/src/services/import.service.ts | 4 +- ui/src/views/list/ChartDrawer.tsx | 11 +- ui/src/views/list/Tree.tsx | 12 + ui/src/views/list/useFilters.tsx | 48 ++-- ui/src/views/setup/DatabaseSetup.tsx | 1 + 11 files changed, 347 insertions(+), 66 deletions(-) diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormImportAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormImportAppService.cs index 5791784..01426ff 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormImportAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormImportAppService.cs @@ -159,9 +159,11 @@ public class ListFormImportAppService : PlatformAppService, IImportAppService try { // Process the selected rows data - var processedCount = request.SelectedRowsData?.Count ?? 0; + var selectedRowsData = request.SelectedRowsData ?? new List>(); + var processedCount = selectedRowsData.Count; var validCount = 0; var errorCount = 0; + var errorDetails = new List(); if (processedCount > 0) { @@ -173,9 +175,9 @@ public class ListFormImportAppService : PlatformAppService, IImportAppService var lastUpdateIndex = 0; // Process each row individually - for (int i = 0; i < request.SelectedRowsData.Count; i++) + for (int i = 0; i < selectedRowsData.Count; i++) { - var rowData = request.SelectedRowsData[i]; + var rowData = selectedRowsData[i]; try { @@ -189,7 +191,7 @@ public class ListFormImportAppService : PlatformAppService, IImportAppService { // If database insert fails, count as error errorCount++; - // Log the error if needed + errorDetails.Add(new { row = i + 1, message = ex.Message }); Logger.LogWarning("Error inserting row {RowIndex} during import for session {SessionId}: {ErrorMessage}", i, session.Id, ex.Message); } @@ -209,23 +211,32 @@ public class ListFormImportAppService : PlatformAppService, IImportAppService } // Update session with final results - execute.Status = "completed"; + execute.Status = errorCount > 0 ? "completed_with_errors" : "completed"; execute.Progress = 100; execute.ExecRows = processedCount; execute.ValidRows = validCount; execute.ErrorRows = errorCount; + execute.ErrorsJson = errorDetails.Count > 0 ? JsonSerializer.Serialize(errorDetails) : null; await _importSessionExecuteRepository.UpdateAsync(execute, autoSave: true); + // Update parent session status to reflect execute result + session.Status = errorCount > 0 ? "executed_with_errors" : "executed"; + await _importSessionRepository.UpdateAsync(session, autoSave: true); + return ObjectMapper.Map(execute); } catch (Exception ex) { - // Update session status to failed + // Update execute status to failed execute.Status = "failed"; execute.Progress = 0; await _importSessionExecuteRepository.UpdateAsync(execute, autoSave: true); + // Update parent session status to reflect failure + session.Status = "execute_failed"; + await _importSessionRepository.UpdateAsync(session, autoSave: true); + throw new UserFriendlyException($"Import failed: {ex.Message}"); } } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index b24f430..ef6796b 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -1476,6 +1476,84 @@ "en": "Charts", "tr": "Grafikler" }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.Uploading", + "en": "Uploading...", + "tr": "Yükleniyor..." + }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.Validating", + "en": "Validating...", + "tr": "Doğrulanıyor..." + }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.Processing", + "en": "Processing...", + "tr": "İşleniyor..." + }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.Uploaded", + "en": "Uploaded", + "tr": "Yüklendi" + }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.UploadedDescription", + "en": "File uploaded but not imported yet. You can review the rows from preview tab and import.", + "tr": "Dosya yüklendi fakat henüz içe aktarılmadı. Preview sekmesinden satırları inceleyip aktarabilirsiniz." + }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.Failed", + "en": "Failed", + "tr": "Başarısız" + }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.FailedDescription", + "en": "An error occurred while uploading the file. Please make sure the file is a valid Excel/CSV file and try again.", + "tr": "Dosya yüklenirken hata oluştu. Dosyanın geçerli bir Excel/CSV dosyası olduğundan emin olun ve tekrar deneyin." + }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.Executed", + "en": "Executed", + "tr": "Başarılı" + }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.ExecutedDescription", + "en": "All rows have been imported successfully.", + "tr": "Tüm satırlar başarıyla içe aktarıldı." + }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.ExecutedWithErrors", + "en": "Executed with errors", + "tr": "Hatalarla Tamamlandı" + }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.ExecutedWithErrorsDescription", + "en": "Some rows have been imported successfully but some rows failed to import. You can review the error details below and fix the related rows to import again.", + "tr": "Bazı satırlar içe aktarılamadı. Hata detaylarını aşağıda inceleyip ilgili satırları düzelterek tekrar yükleyebilirsiniz." + }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.ExecuteFailed", + "en": "Execute Failed", + "tr": "İçe Aktarma Başarısız" + }, + { + "resourceName": "Platform", + "key": "App.Listforms.ImportManager.ExecuteFailedDescription", + "en": "Import failed due to an unexpected error. Please try again or contact your system administrator.", + "tr": "İçe aktarma işlemi beklenmedik bir hata nedeniyle başarısız oldu. Lütfen tekrar deneyin veya sistem yöneticisine başvurun." + }, { "resourceName": "Platform", "key": "App.Listforms.ImportManager.DropHere", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_DefaultJsons.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_DefaultJsons.cs index 701f0a2..bb8e00f 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_DefaultJsons.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_DefaultJsons.cs @@ -167,7 +167,7 @@ public static class ListFormSeeder_DefaultJsons R = permissionName, U = permissionName + ".Update", E = true, - I = false, + I = true, Deny = false }); } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs index d216de5..34820f0 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs @@ -168,7 +168,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency CultureName = LanguageCodes.En, SourceDbType = DbType.Guid, FieldName = "Id", - CaptionName = "App.Listform.ListformField.Id", + CaptionName = "App.Listform.ListformField.Id", Width = 0, ListOrderNo = 1, Visible = false, diff --git a/ui/src/components/importManager/ImportDashboard.tsx b/ui/src/components/importManager/ImportDashboard.tsx index 813e723..ff610b8 100644 --- a/ui/src/components/importManager/ImportDashboard.tsx +++ b/ui/src/components/importManager/ImportDashboard.tsx @@ -11,6 +11,9 @@ import { FaDownload, FaFileExcel, FaFileAlt, + FaExclamationTriangle, + FaChevronDown, + FaChevronUp, } from 'react-icons/fa' import { FileUploadArea } from './FileUploadArea' import { ImportPreview } from './ImportPreview' @@ -173,8 +176,12 @@ export const ImportDashboard: React.FC = ({ gridDto }) => const getStatusIcon = (status: string) => { switch (status) { case 'uploaded': + case 'executed': return + case 'executed_with_errors': + return case 'failed': + case 'execute_failed': return case 'processing': case 'validating': @@ -187,8 +194,12 @@ export const ImportDashboard: React.FC = ({ gridDto }) => const getStatusColor = (status: string) => { switch (status) { case 'uploaded': + case 'executed': return 'bg-green-50 text-green-700 border-green-200' + case 'executed_with_errors': + return 'bg-orange-50 text-orange-700 border-orange-200' case 'failed': + case 'execute_failed': return 'bg-red-50 text-red-700 border-red-200' case 'processing': case 'validating': @@ -198,6 +209,105 @@ export const ImportDashboard: React.FC = ({ gridDto }) => } } + const getSessionStatusLabel = (status: string) => { + switch (status) { + case 'uploading': + return translate('::App.Listforms.ImportManager.Uploading') + case 'validating': + return translate('::App.Listforms.ImportManager.Validating') + case 'processing': + return translate('::App.Listforms.ImportManager.Processing') + case 'uploaded': + return translate('::App.Listforms.ImportManager.Uploaded') + case 'failed': + return translate('::App.Listforms.ImportManager.Failed') + case 'executed': + return translate('::App.Listforms.ImportManager.Executed') + case 'executed_with_errors': + return translate('::App.Listforms.ImportManager.ExecutedWithErrors') + case 'execute_failed': + return translate('::App.Listforms.ImportManager.ExecuteFailed') + default: + return status.charAt(0).toUpperCase() + status.slice(1) + } + } + + const getSessionGuidance = (status: string) => { + switch (status) { + case 'uploaded': + return translate('::App.Listforms.ImportManager.UploadedDescription') + case 'executed': + return translate('::App.Listforms.ImportManager.ExecutedDescription') + case 'executed_with_errors': + return translate('::App.Listforms.ImportManager.ExecutedWithErrorsDescription') + case 'execute_failed': + return translate('::App.Listforms.ImportManager.ExecuteFailedDescription') + case 'failed': + return translate('::App.Listforms.ImportManager.FailedDescription') + default: + return null + } + } + + const getExecuteStatusIcon = (status: string) => { + switch (status) { + case 'completed': + return + case 'completed_with_errors': + return + case 'processing': + return + case 'validating': + return + case 'failed': + return + default: + return null + } + } + + const getExecuteStatusColor = (status: string) => { + switch (status) { + case 'completed': + return 'text-green-600' + case 'completed_with_errors': + return 'text-orange-600' + case 'processing': + return 'text-blue-600' + case 'validating': + return 'text-yellow-600' + case 'failed': + return 'text-red-600' + default: + return 'text-slate-600' + } + } + + const getExecuteStatusLabel = (status: string) => { + if (status === 'completed_with_errors') return 'Completed with errors' + return status.charAt(0).toUpperCase() + status.slice(1) + } + + const [expandedErrors, setExpandedErrors] = useState>(new Set()) + + const toggleErrors = (executeId: string) => { + setExpandedErrors((prev) => { + const next = new Set(prev) + if (next.has(executeId)) next.delete(executeId) + else next.add(executeId) + return next + }) + } + + const parseErrors = (errorsJson?: string): { row: number; message: string }[] => { + if (!errorsJson) return [] + try { + return JSON.parse(errorsJson) + } catch { + return [] + } + } + const generateTemplate = async (format: 'excel' | 'csv') => { setGenerating(true) try { @@ -212,7 +322,7 @@ export const ImportDashboard: React.FC = ({ gridDto }) => } const getEditableColumns = () => { - return gridDto.columnFormats.filter((col: any) => col.permissionDto.i && col.fieldName !== 'Id') + return gridDto.columnFormats.filter((col: any) => col.canCreate && col.fieldName !== 'Id') } const editableColumns = getEditableColumns() @@ -304,7 +414,7 @@ export const ImportDashboard: React.FC = ({ gridDto }) => {editableColumns.map((column: any) => ( - {translate('::' + column.captionName)} + {column.fieldName} = ({ gridDto }) => session.status, )}`} > - {session.status.charAt(0).toUpperCase() + session.status.slice(1)} + {getSessionStatusLabel(session.status)}
@@ -528,6 +638,34 @@ export const ImportDashboard: React.FC = ({ gridDto }) =>
+ {/* Guidance message */} + {getSessionGuidance(session.status) && ( +
+ + {session.status === 'executed_with_errors' || + session.status === 'failed' || + session.status === 'execute_failed' ? ( + + ) : session.status === 'uploaded' ? ( + + ) : ( + + )} + + {getSessionGuidance(session.status)} +
+ )} + {/* Execute Details Section */} {expandedSessions.has(session.id) && (
@@ -582,35 +720,72 @@ export const ImportDashboard: React.FC = ({ gridDto }) => {/* Sağ: Status */}
- {execute.status === 'completed' && ( - - )} - {execute.status === 'processing' && ( - - )} - {execute.status === 'validating' && ( - - )} - {execute.status === 'failed' && ( - - )} + {getExecuteStatusIcon(execute.status)}
- {execute.status.charAt(0).toUpperCase() + - execute.status.slice(1)} + {getExecuteStatusLabel(execute.status)} {execute.status === 'processing' && ` (${execute.progress}%)`}
+ + {/* Error Details */} + {(execute.status === 'completed_with_errors' || + execute.status === 'failed') && + execute.errorRows > 0 && ( +
+ + + {expandedErrors.has(execute.id) && ( +
+ {parseErrors(execute.errorsJson).length > 0 ? ( + + + + + + + + + {parseErrors(execute.errorsJson).map((err, idx) => ( + + + + + ))} + +
+ Satır + + Hata Mesajı +
+ {err.row} + + {err.message} +
+ ) : ( +

+ Hata detayı mevcut değil. +

+ )} +
+ )} +
+ )} ))} diff --git a/ui/src/proxy/imports/models.ts b/ui/src/proxy/imports/models.ts index 050a103..dc0b0fc 100644 --- a/ui/src/proxy/imports/models.ts +++ b/ui/src/proxy/imports/models.ts @@ -4,7 +4,7 @@ export interface ListFormImportDto { id: string listFormCode: string blobName: string - status: 'uploading' | 'validating' | 'processing' | 'uploaded' | 'failed' + status: 'uploading' | 'validating' | 'processing' | 'uploaded' | 'failed' | 'executed' | 'executed_with_errors' | 'execute_failed' totalRows: number creationTime: string } @@ -13,11 +13,12 @@ export interface ListFormImportExecuteDto { id: string importId: string blobName: string - status: 'processing' | 'validating' | 'completed' | 'failed' + status: 'processing' | 'validating' | 'completed' | 'completed_with_errors' | 'failed' execRows: number validRows: number errorRows: number progress: number + errorsJson?: string creationTime: string } diff --git a/ui/src/services/import.service.ts b/ui/src/services/import.service.ts index c35597f..a434efe 100644 --- a/ui/src/services/import.service.ts +++ b/ui/src/services/import.service.ts @@ -16,7 +16,7 @@ export class ImportService { if (format === 'excel') { // Create Excel-compatible content - const headers = editableColumns.map((col) => col.captionName || col.fieldName) + const headers = editableColumns.map((col) => col.fieldName) // Create a simple Excel XML format that works with Excel 2010+ const excelContent = ` @@ -85,7 +85,7 @@ ${headers }) } else { // CSV format - const headers = editableColumns.map((col) => col.captionName || col.fieldName) + const headers = editableColumns.map((col) => col.fieldName) const content = headers.join(',') + '\n' return new Blob([content], { type: 'text/csv' }) } diff --git a/ui/src/views/list/ChartDrawer.tsx b/ui/src/views/list/ChartDrawer.tsx index 1844cee..7aff84d 100644 --- a/ui/src/views/list/ChartDrawer.tsx +++ b/ui/src/views/list/ChartDrawer.tsx @@ -147,10 +147,11 @@ const ChartDrawer = ({ {translate('::App.Platform.ChartDrawer.ChartSeries')} @@ -158,14 +159,14 @@ const ChartDrawer = ({ {/* Add Series Button */}
)} diff --git a/ui/src/views/list/useFilters.tsx b/ui/src/views/list/useFilters.tsx index 80b7293..6c0a980 100644 --- a/ui/src/views/list/useFilters.tsx +++ b/ui/src/views/list/useFilters.tsx @@ -21,8 +21,6 @@ import { usePWA } from '@/utils/hooks/usePWA' import { ROUTES_ENUM } from '@/routes/route.constant' import { GanttRef } from 'devextreme-react/cjs/gantt' import { useStoreState } from '@/store' -import { cssClass } from 'yet-another-react-lightbox/*' -import classNames from 'classnames' export interface ISelectBoxData { value?: string @@ -284,30 +282,34 @@ const useFilters = ({ icon: 'revert', }) } + } - if (checkPermission(gridDto?.gridOptions.permissionDto.i)) { - menus.push({ - text: translate('::ListForms.ListForm.ImportManager'), - id: 'importManager', - icon: 'upload', - }) - } + // Import, Manage ve Export: filtre/state ayarından bağımsız olarak her zaman kontrol et + if (checkPermission(gridDto?.gridOptions.permissionDto.i)) { + menus.push({ + text: translate('::ListForms.ListForm.ImportManager'), + id: 'importManager', + icon: 'upload', + }) + } - if (checkPermission('App.Listforms.Listform.Update')) { - menus.push({ - text: translate('::ListForms.ListForm.Manage'), - id: 'openManage', - icon: 'preferences', - }) - } + if (checkPermission('App.Listforms.Listform.Update')) { + menus.push({ + text: translate('::ListForms.ListForm.Manage'), + id: 'openManage', + icon: 'preferences', + }) + } - if (checkPermission(gridDto?.gridOptions.permissionDto.e)) { - items.push({ - locateInMenu: 'auto', - showText: 'inMenu', - name: 'exportButton', - }) - } + if (checkPermission(gridDto?.gridOptions.permissionDto.e)) { + items.push({ + locateInMenu: 'auto', + showText: 'inMenu', + name: 'exportButton', + }) + } + + if (menus.length > 0) { // filtre islemleri ile ilgili fonksiyonlar sayisi cok oldugu icin menu icerisine alindi const menuFilterButtons: ToolbarItem = { diff --git a/ui/src/views/setup/DatabaseSetup.tsx b/ui/src/views/setup/DatabaseSetup.tsx index 5aa8c97..3e74fe5 100644 --- a/ui/src/views/setup/DatabaseSetup.tsx +++ b/ui/src/views/setup/DatabaseSetup.tsx @@ -250,6 +250,7 @@ const DatabaseSetup = () => {
{(status === 'idle' || status === 'error') && !dbExists && (