ImportManager problemi giderildi
This commit is contained in:
parent
66037bf001
commit
3f44b23f57
11 changed files with 347 additions and 66 deletions
|
|
@ -159,9 +159,11 @@ public class ListFormImportAppService : PlatformAppService, IImportAppService
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Process the selected rows data
|
// Process the selected rows data
|
||||||
var processedCount = request.SelectedRowsData?.Count ?? 0;
|
var selectedRowsData = request.SelectedRowsData ?? new List<Dictionary<string, object>>();
|
||||||
|
var processedCount = selectedRowsData.Count;
|
||||||
var validCount = 0;
|
var validCount = 0;
|
||||||
var errorCount = 0;
|
var errorCount = 0;
|
||||||
|
var errorDetails = new List<object>();
|
||||||
|
|
||||||
if (processedCount > 0)
|
if (processedCount > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -173,9 +175,9 @@ public class ListFormImportAppService : PlatformAppService, IImportAppService
|
||||||
var lastUpdateIndex = 0;
|
var lastUpdateIndex = 0;
|
||||||
|
|
||||||
// Process each row individually
|
// 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
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -189,7 +191,7 @@ public class ListFormImportAppService : PlatformAppService, IImportAppService
|
||||||
{
|
{
|
||||||
// If database insert fails, count as error
|
// If database insert fails, count as error
|
||||||
errorCount++;
|
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);
|
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
|
// Update session with final results
|
||||||
execute.Status = "completed";
|
execute.Status = errorCount > 0 ? "completed_with_errors" : "completed";
|
||||||
execute.Progress = 100;
|
execute.Progress = 100;
|
||||||
execute.ExecRows = processedCount;
|
execute.ExecRows = processedCount;
|
||||||
execute.ValidRows = validCount;
|
execute.ValidRows = validCount;
|
||||||
execute.ErrorRows = errorCount;
|
execute.ErrorRows = errorCount;
|
||||||
|
execute.ErrorsJson = errorDetails.Count > 0 ? JsonSerializer.Serialize(errorDetails) : null;
|
||||||
|
|
||||||
await _importSessionExecuteRepository.UpdateAsync(execute, autoSave: true);
|
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<ListFormImportExecute, ListFormImportExecuteDto>(execute);
|
return ObjectMapper.Map<ListFormImportExecute, ListFormImportExecuteDto>(execute);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Update session status to failed
|
// Update execute status to failed
|
||||||
execute.Status = "failed";
|
execute.Status = "failed";
|
||||||
execute.Progress = 0;
|
execute.Progress = 0;
|
||||||
await _importSessionExecuteRepository.UpdateAsync(execute, autoSave: true);
|
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}");
|
throw new UserFriendlyException($"Import failed: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1476,6 +1476,84 @@
|
||||||
"en": "Charts",
|
"en": "Charts",
|
||||||
"tr": "Grafikler"
|
"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",
|
"resourceName": "Platform",
|
||||||
"key": "App.Listforms.ImportManager.DropHere",
|
"key": "App.Listforms.ImportManager.DropHere",
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ public static class ListFormSeeder_DefaultJsons
|
||||||
R = permissionName,
|
R = permissionName,
|
||||||
U = permissionName + ".Update",
|
U = permissionName + ".Update",
|
||||||
E = true,
|
E = true,
|
||||||
I = false,
|
I = true,
|
||||||
Deny = false
|
Deny = false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.Guid,
|
SourceDbType = DbType.Guid,
|
||||||
FieldName = "Id",
|
FieldName = "Id",
|
||||||
CaptionName = "App.Listform.ListformField.Id",
|
CaptionName = "App.Listform.ListformField.Id",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 1,
|
ListOrderNo = 1,
|
||||||
Visible = false,
|
Visible = false,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ import {
|
||||||
FaDownload,
|
FaDownload,
|
||||||
FaFileExcel,
|
FaFileExcel,
|
||||||
FaFileAlt,
|
FaFileAlt,
|
||||||
|
FaExclamationTriangle,
|
||||||
|
FaChevronDown,
|
||||||
|
FaChevronUp,
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
import { FileUploadArea } from './FileUploadArea'
|
import { FileUploadArea } from './FileUploadArea'
|
||||||
import { ImportPreview } from './ImportPreview'
|
import { ImportPreview } from './ImportPreview'
|
||||||
|
|
@ -173,8 +176,12 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
const getStatusIcon = (status: string) => {
|
const getStatusIcon = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'uploaded':
|
case 'uploaded':
|
||||||
|
case 'executed':
|
||||||
return <FaCheckCircle className="w-5 h-5 text-green-500" />
|
return <FaCheckCircle className="w-5 h-5 text-green-500" />
|
||||||
|
case 'executed_with_errors':
|
||||||
|
return <FaExclamationTriangle className="w-5 h-5 text-orange-500" />
|
||||||
case 'failed':
|
case 'failed':
|
||||||
|
case 'execute_failed':
|
||||||
return <FaRegBell className="w-5 h-5 text-red-500" />
|
return <FaRegBell className="w-5 h-5 text-red-500" />
|
||||||
case 'processing':
|
case 'processing':
|
||||||
case 'validating':
|
case 'validating':
|
||||||
|
|
@ -187,8 +194,12 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'uploaded':
|
case 'uploaded':
|
||||||
|
case 'executed':
|
||||||
return 'bg-green-50 text-green-700 border-green-200'
|
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 'failed':
|
||||||
|
case 'execute_failed':
|
||||||
return 'bg-red-50 text-red-700 border-red-200'
|
return 'bg-red-50 text-red-700 border-red-200'
|
||||||
case 'processing':
|
case 'processing':
|
||||||
case 'validating':
|
case 'validating':
|
||||||
|
|
@ -198,6 +209,105 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ 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 <FaCheckCircle className="w-4 h-4 text-green-500" />
|
||||||
|
case 'completed_with_errors':
|
||||||
|
return <FaExclamationTriangle className="w-4 h-4 text-orange-500" />
|
||||||
|
case 'processing':
|
||||||
|
return <FaSync className="w-4 h-4 text-blue-500 animate-spin" />
|
||||||
|
case 'validating':
|
||||||
|
return <FaClock className="w-4 h-4 text-yellow-500" />
|
||||||
|
case 'failed':
|
||||||
|
return <FaRegBell className="w-4 h-4 text-red-500" />
|
||||||
|
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<Set<string>>(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') => {
|
const generateTemplate = async (format: 'excel' | 'csv') => {
|
||||||
setGenerating(true)
|
setGenerating(true)
|
||||||
try {
|
try {
|
||||||
|
|
@ -212,7 +322,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
const getEditableColumns = () => {
|
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()
|
const editableColumns = getEditableColumns()
|
||||||
|
|
@ -304,7 +414,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
{editableColumns.map((column: any) => (
|
{editableColumns.map((column: any) => (
|
||||||
<tr key={column.fieldName} className="hover:bg-slate-50">
|
<tr key={column.fieldName} className="hover:bg-slate-50">
|
||||||
<td className="px-2 py-2 font-medium text-slate-800">
|
<td className="px-2 py-2 font-medium text-slate-800">
|
||||||
{translate('::' + column.captionName)}
|
{column.fieldName}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2 text-slate-600">
|
<td className="px-4 py-2 text-slate-600">
|
||||||
<span
|
<span
|
||||||
|
|
@ -458,7 +568,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
session.status,
|
session.status,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{session.status.charAt(0).toUpperCase() + session.status.slice(1)}
|
{getSessionStatusLabel(session.status)}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
|
|
@ -528,6 +638,34 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Guidance message */}
|
||||||
|
{getSessionGuidance(session.status) && (
|
||||||
|
<div
|
||||||
|
className={`mt-2 mx-1 px-3 py-2 rounded-md text-xs flex items-start space-x-2 ${
|
||||||
|
session.status === 'executed_with_errors' ||
|
||||||
|
session.status === 'failed' ||
|
||||||
|
session.status === 'execute_failed'
|
||||||
|
? 'bg-red-50 text-red-700 border border-red-200'
|
||||||
|
: session.status === 'uploaded'
|
||||||
|
? 'bg-blue-50 text-blue-700 border border-blue-200'
|
||||||
|
: 'bg-green-50 text-green-700 border border-green-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="mt-0.5 flex-shrink-0">
|
||||||
|
{session.status === 'executed_with_errors' ||
|
||||||
|
session.status === 'failed' ||
|
||||||
|
session.status === 'execute_failed' ? (
|
||||||
|
<FaExclamationTriangle className="w-3 h-3" />
|
||||||
|
) : session.status === 'uploaded' ? (
|
||||||
|
<FaRegBell className="w-3 h-3" />
|
||||||
|
) : (
|
||||||
|
<FaCheckCircle className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span>{getSessionGuidance(session.status)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Execute Details Section */}
|
{/* Execute Details Section */}
|
||||||
{expandedSessions.has(session.id) && (
|
{expandedSessions.has(session.id) && (
|
||||||
<div className="mt-3 bg-gradient-to-r from-indigo-50 to-blue-50 border border-indigo-100 rounded-lg shadow-sm hover:shadow-md transition-shadow">
|
<div className="mt-3 bg-gradient-to-r from-indigo-50 to-blue-50 border border-indigo-100 rounded-lg shadow-sm hover:shadow-md transition-shadow">
|
||||||
|
|
@ -582,35 +720,72 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
|
|
||||||
{/* Sağ: Status */}
|
{/* Sağ: Status */}
|
||||||
<div className="flex items-center space-x-2 flex-shrink-0">
|
<div className="flex items-center space-x-2 flex-shrink-0">
|
||||||
{execute.status === 'completed' && (
|
{getExecuteStatusIcon(execute.status)}
|
||||||
<FaCheckCircle className="w-4 h-4 text-green-500" />
|
|
||||||
)}
|
|
||||||
{execute.status === 'processing' && (
|
|
||||||
<FaSync className="w-4 h-4 text-blue-500 animate-spin" />
|
|
||||||
)}
|
|
||||||
{execute.status === 'validating' && (
|
|
||||||
<FaClock className="w-4 h-4 text-yellow-500" />
|
|
||||||
)}
|
|
||||||
{execute.status === 'failed' && (
|
|
||||||
<FaRegBell className="w-4 h-4 text-red-500" />
|
|
||||||
)}
|
|
||||||
<div
|
<div
|
||||||
className={`text-xs font-medium ${
|
className={`text-xs font-medium ${getExecuteStatusColor(execute.status)}`}
|
||||||
execute.status === 'completed'
|
|
||||||
? 'text-green-600'
|
|
||||||
: execute.status === 'processing'
|
|
||||||
? 'text-blue-600'
|
|
||||||
: execute.status === 'validating'
|
|
||||||
? 'text-yellow-600'
|
|
||||||
: 'text-red-600'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{execute.status.charAt(0).toUpperCase() +
|
{getExecuteStatusLabel(execute.status)}
|
||||||
execute.status.slice(1)}
|
|
||||||
{execute.status === 'processing' && ` (${execute.progress}%)`}
|
{execute.status === 'processing' && ` (${execute.progress}%)`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Error Details */}
|
||||||
|
{(execute.status === 'completed_with_errors' ||
|
||||||
|
execute.status === 'failed') &&
|
||||||
|
execute.errorRows > 0 && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<button
|
||||||
|
onClick={() => toggleErrors(execute.id)}
|
||||||
|
className="flex items-center space-x-1 text-xs text-orange-600 hover:text-orange-700 font-medium"
|
||||||
|
>
|
||||||
|
{expandedErrors.has(execute.id) ? (
|
||||||
|
<FaChevronUp className="w-3 h-3" />
|
||||||
|
) : (
|
||||||
|
<FaChevronDown className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
{execute.errorRows} hata detayı
|
||||||
|
{expandedErrors.has(execute.id) ? ' gizle' : ' göster'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{expandedErrors.has(execute.id) && (
|
||||||
|
<div className="mt-2 max-h-48 overflow-y-auto rounded border border-orange-200 bg-orange-50">
|
||||||
|
{parseErrors(execute.errorsJson).length > 0 ? (
|
||||||
|
<table className="w-full text-xs">
|
||||||
|
<thead className="bg-orange-100 sticky top-0">
|
||||||
|
<tr>
|
||||||
|
<th className="px-3 py-1 text-left font-medium text-orange-700 w-16">
|
||||||
|
Satır
|
||||||
|
</th>
|
||||||
|
<th className="px-3 py-1 text-left font-medium text-orange-700">
|
||||||
|
Hata Mesajı
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-orange-100">
|
||||||
|
{parseErrors(execute.errorsJson).map((err, idx) => (
|
||||||
|
<tr key={idx} className="hover:bg-orange-100">
|
||||||
|
<td className="px-3 py-1 text-orange-700 font-medium">
|
||||||
|
{err.row}
|
||||||
|
</td>
|
||||||
|
<td className="px-3 py-1 text-slate-700">
|
||||||
|
{err.message}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
) : (
|
||||||
|
<p className="px-3 py-2 text-orange-600">
|
||||||
|
Hata detayı mevcut değil.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ export interface ListFormImportDto {
|
||||||
id: string
|
id: string
|
||||||
listFormCode: string
|
listFormCode: string
|
||||||
blobName: string
|
blobName: string
|
||||||
status: 'uploading' | 'validating' | 'processing' | 'uploaded' | 'failed'
|
status: 'uploading' | 'validating' | 'processing' | 'uploaded' | 'failed' | 'executed' | 'executed_with_errors' | 'execute_failed'
|
||||||
totalRows: number
|
totalRows: number
|
||||||
creationTime: string
|
creationTime: string
|
||||||
}
|
}
|
||||||
|
|
@ -13,11 +13,12 @@ export interface ListFormImportExecuteDto {
|
||||||
id: string
|
id: string
|
||||||
importId: string
|
importId: string
|
||||||
blobName: string
|
blobName: string
|
||||||
status: 'processing' | 'validating' | 'completed' | 'failed'
|
status: 'processing' | 'validating' | 'completed' | 'completed_with_errors' | 'failed'
|
||||||
execRows: number
|
execRows: number
|
||||||
validRows: number
|
validRows: number
|
||||||
errorRows: number
|
errorRows: number
|
||||||
progress: number
|
progress: number
|
||||||
|
errorsJson?: string
|
||||||
creationTime: string
|
creationTime: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export class ImportService {
|
||||||
|
|
||||||
if (format === 'excel') {
|
if (format === 'excel') {
|
||||||
// Create Excel-compatible content
|
// 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+
|
// Create a simple Excel XML format that works with Excel 2010+
|
||||||
const excelContent = `<?xml version="1.0"?>
|
const excelContent = `<?xml version="1.0"?>
|
||||||
|
|
@ -85,7 +85,7 @@ ${headers
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// CSV format
|
// CSV format
|
||||||
const headers = editableColumns.map((col) => col.captionName || col.fieldName)
|
const headers = editableColumns.map((col) => col.fieldName)
|
||||||
const content = headers.join(',') + '\n'
|
const content = headers.join(',') + '\n'
|
||||||
return new Blob([content], { type: 'text/csv' })
|
return new Blob([content], { type: 'text/csv' })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,10 +147,11 @@ const ChartDrawer = ({
|
||||||
{translate('::App.Platform.ChartDrawer.ChartSeries')}
|
{translate('::App.Platform.ChartDrawer.ChartSeries')}
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
<Button
|
||||||
|
size="xs"
|
||||||
icon={<FaTimes />}
|
icon={<FaTimes />}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="p-2 hover:bg-gray-200 rounded-full transition-colors"
|
className="border-0"
|
||||||
></Button>
|
></Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -158,14 +159,14 @@ const ChartDrawer = ({
|
||||||
{/* Add Series Button */}
|
{/* Add Series Button */}
|
||||||
<div className="p-3 border-b">
|
<div className="p-3 border-b">
|
||||||
<Button
|
<Button
|
||||||
|
size="sm"
|
||||||
|
block
|
||||||
variant="solid"
|
variant="solid"
|
||||||
type="button"
|
type="button"
|
||||||
size="sm"
|
|
||||||
icon={<FaPlus />}
|
icon={<FaPlus />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFieldValue('series', [...values.series, newSeriesValue()])
|
setFieldValue('series', [...values.series, newSeriesValue()])
|
||||||
}}
|
}}
|
||||||
className="w-full"
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
{translate('::App.Platform.ChartDrawer.AddNewSeries')}
|
{translate('::App.Platform.ChartDrawer.AddNewSeries')}
|
||||||
|
|
@ -190,10 +191,10 @@ const ChartDrawer = ({
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
size="sm"
|
size="xs"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
icon={<FaMinus />}
|
icon={<FaMinus />}
|
||||||
className="text-red-500 hover:bg-red-100"
|
className="border-0 text-red-500 hover:bg-red-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
remove(index)
|
remove(index)
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ import { useListFormColumns } from './useListFormColumns'
|
||||||
import { DataType } from 'devextreme/common'
|
import { DataType } from 'devextreme/common'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
import SubForms from '../form/SubForms'
|
import SubForms from '../form/SubForms'
|
||||||
|
import { ImportDashboard } from '@/components/importManager/ImportDashboard'
|
||||||
|
|
||||||
interface TreeProps {
|
interface TreeProps {
|
||||||
listFormCode: string
|
listFormCode: string
|
||||||
|
|
@ -1241,6 +1242,17 @@ const Tree = (props: TreeProps) => {
|
||||||
<SubForms gridDto={gridDto!} formData={formData} level={level ?? 0} />
|
<SubForms gridDto={gridDto!} formData={formData} level={level ?? 0} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
width={smaller.md ? '100%' : 1000}
|
||||||
|
isOpen={filterData.isImportModalOpen || false}
|
||||||
|
onClose={() => filterData.setIsImportModalOpen(false)}
|
||||||
|
onRequestClose={() => filterData.setIsImportModalOpen(false)}
|
||||||
|
>
|
||||||
|
<Dialog.Body className="flex flex-col">
|
||||||
|
<ImportDashboard gridDto={gridDto} />
|
||||||
|
</Dialog.Body>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@ import { usePWA } from '@/utils/hooks/usePWA'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { GanttRef } from 'devextreme-react/cjs/gantt'
|
import { GanttRef } from 'devextreme-react/cjs/gantt'
|
||||||
import { useStoreState } from '@/store'
|
import { useStoreState } from '@/store'
|
||||||
import { cssClass } from 'yet-another-react-lightbox/*'
|
|
||||||
import classNames from 'classnames'
|
|
||||||
|
|
||||||
export interface ISelectBoxData {
|
export interface ISelectBoxData {
|
||||||
value?: string
|
value?: string
|
||||||
|
|
@ -284,30 +282,34 @@ const useFilters = ({
|
||||||
icon: 'revert',
|
icon: 'revert',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (checkPermission(gridDto?.gridOptions.permissionDto.i)) {
|
// Import, Manage ve Export: filtre/state ayarından bağımsız olarak her zaman kontrol et
|
||||||
menus.push({
|
if (checkPermission(gridDto?.gridOptions.permissionDto.i)) {
|
||||||
text: translate('::ListForms.ListForm.ImportManager'),
|
menus.push({
|
||||||
id: 'importManager',
|
text: translate('::ListForms.ListForm.ImportManager'),
|
||||||
icon: 'upload',
|
id: 'importManager',
|
||||||
})
|
icon: 'upload',
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (checkPermission('App.Listforms.Listform.Update')) {
|
if (checkPermission('App.Listforms.Listform.Update')) {
|
||||||
menus.push({
|
menus.push({
|
||||||
text: translate('::ListForms.ListForm.Manage'),
|
text: translate('::ListForms.ListForm.Manage'),
|
||||||
id: 'openManage',
|
id: 'openManage',
|
||||||
icon: 'preferences',
|
icon: 'preferences',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkPermission(gridDto?.gridOptions.permissionDto.e)) {
|
if (checkPermission(gridDto?.gridOptions.permissionDto.e)) {
|
||||||
items.push({
|
items.push({
|
||||||
locateInMenu: 'auto',
|
locateInMenu: 'auto',
|
||||||
showText: 'inMenu',
|
showText: 'inMenu',
|
||||||
name: 'exportButton',
|
name: 'exportButton',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (menus.length > 0) {
|
||||||
|
|
||||||
// filtre islemleri ile ilgili fonksiyonlar sayisi cok oldugu icin menu icerisine alindi
|
// filtre islemleri ile ilgili fonksiyonlar sayisi cok oldugu icin menu icerisine alindi
|
||||||
const menuFilterButtons: ToolbarItem = {
|
const menuFilterButtons: ToolbarItem = {
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,7 @@ const DatabaseSetup = () => {
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
{(status === 'idle' || status === 'error') && !dbExists && (
|
{(status === 'idle' || status === 'error') && !dbExists && (
|
||||||
<Button
|
<Button
|
||||||
|
variant='solid'
|
||||||
onClick={startMigration}
|
onClick={startMigration}
|
||||||
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-sm font-medium rounded-lg transition-colors"
|
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-sm font-medium rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue