Dialog component draggable özelliği
This commit is contained in:
parent
168768ba98
commit
b2863328cb
11 changed files with 308 additions and 130 deletions
|
|
@ -22,5 +22,8 @@ public class GridEditingPopupDto
|
|||
/// <summary> popup ekranin kapatildiktan sonra eski konumunu hatirlamasini saglar
|
||||
/// </summary>
|
||||
public bool RestorePosition { get; set; } = true;
|
||||
/// <summary>Accepted Values: 'top' | 'bottom' | 'left' | 'right' | 'center'
|
||||
/// </summary>
|
||||
public string Position { get; set; } = "center";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"commit": "dc293fc",
|
||||
"commit": "168768b",
|
||||
"releases": [
|
||||
{
|
||||
"version": "1.1.04",
|
||||
|
|
|
|||
|
|
@ -100,8 +100,8 @@ export const FileUploadArea: React.FC<FileUploadAreaProps> = ({
|
|||
<div
|
||||
className={`relative border-2 border-dashed rounded-lg p-3 transition-all duration-200 ${
|
||||
dragActive
|
||||
? 'border-blue-400 bg-blue-50 dark:bg-blue-950/30'
|
||||
: 'border-slate-300 hover:border-slate-400 dark:border-slate-600 dark:hover:border-slate-500'
|
||||
? 'border-blue-400 bg-blue-50 dark:bg-gray-800'
|
||||
: 'border-gray-300 hover:border-gray-400 dark:border-gray-600 dark:hover:border-gray-500'
|
||||
} ${loading ? 'opacity-50 pointer-events-none' : ''}`}
|
||||
onDragEnter={handleDrag}
|
||||
onDragLeave={handleDrag}
|
||||
|
|
@ -119,39 +119,39 @@ export const FileUploadArea: React.FC<FileUploadAreaProps> = ({
|
|||
|
||||
<div className="text-center">
|
||||
<FaUpload
|
||||
className={`mx-auto h-3 w-3 ${dragActive ? 'text-blue-500 dark:text-blue-400' : 'text-slate-400 dark:text-slate-500'}`}
|
||||
className={`mx-auto h-3 w-3 ${dragActive ? 'text-blue-500 dark:text-blue-400' : 'text-gray-400 dark:text-gray-500'}`}
|
||||
/>
|
||||
<div className="text-lg font-medium text-slate-700 dark:text-slate-200 mb-2">
|
||||
<div className="text-lg font-medium text-gray-700 dark:text-gray-200 mb-2">
|
||||
{dragActive
|
||||
? translate('::App.Listforms.ImportManager.DropHere')
|
||||
: translate('::App.Listforms.ImportManager.UploadYourFile')}
|
||||
</div>
|
||||
<p className="text-slate-500 dark:text-slate-400 mb-4">
|
||||
<p className="text-gray-500 dark:text-gray-400 mb-4">
|
||||
{translate('::App.Listforms.ImportManager.DragOrClick')}
|
||||
</p>
|
||||
<div className="text-sm text-slate-400 dark:text-slate-500">
|
||||
<div className="text-sm text-gray-400 dark:text-gray-500">
|
||||
{translate('::App.Listforms.ImportManager.SupportedFormats')}{' '}
|
||||
{acceptedFormats.join(', ')} • Max size: {maxSize}MB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="border border-slate-200 dark:border-slate-700 rounded-lg p-4 dark:bg-slate-900/40">
|
||||
<div className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 dark:bg-gray-900/40">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3 min-w-0 flex-1">
|
||||
<FaFile className="w-8 h-8 text-blue-500 flex-shrink-0" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="font-medium text-slate-800 dark:text-slate-100 truncate">
|
||||
<div className="font-medium text-gray-800 dark:text-gray-100 truncate">
|
||||
{selectedFile.name}
|
||||
</div>
|
||||
<div className="text-sm text-slate-500 dark:text-slate-400">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{formatFileSize(selectedFile.size)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={clearFile}
|
||||
className="p-2 text-slate-400 hover:text-slate-600 hover:bg-slate-100 dark:text-slate-500 dark:hover:text-slate-300 dark:hover:bg-slate-800 rounded-lg transition-colors"
|
||||
className="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 dark:text-gray-500 dark:hover:text-gray-300 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||
>
|
||||
<FaTimes className="w-4 h-4" />
|
||||
</button>
|
||||
|
|
@ -160,7 +160,7 @@ export const FileUploadArea: React.FC<FileUploadAreaProps> = ({
|
|||
)}
|
||||
|
||||
{error && (
|
||||
<div className="flex items-center space-x-2 p-3 bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-900/60 rounded-lg">
|
||||
<div className="flex items-center space-x-2 p-3 bg-red-50 dark:bg-gray-800 border border-red-200 dark:border-gray-700 rounded-lg">
|
||||
<FaRegCircle className="w-5 h-5 text-red-500" />
|
||||
<span className="text-red-700 dark:text-red-300">{error}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -195,17 +195,17 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
switch (status) {
|
||||
case 'uploaded':
|
||||
case 'executed':
|
||||
return 'bg-green-50 text-green-700 border-green-200 dark:bg-green-950/30 dark:text-green-300 dark:border-green-900/60'
|
||||
return 'bg-green-50 text-green-700 border-green-200 dark:bg-gray-800 dark:text-green-300 dark:border-gray-700'
|
||||
case 'executed_with_errors':
|
||||
return 'bg-orange-50 text-orange-700 border-orange-200 dark:bg-orange-950/30 dark:text-orange-300 dark:border-orange-900/60'
|
||||
return 'bg-orange-50 text-orange-700 border-orange-200 dark:bg-gray-800 dark:text-orange-300 dark:border-gray-700'
|
||||
case 'failed':
|
||||
case 'execute_failed':
|
||||
return 'bg-red-50 text-red-700 border-red-200 dark:bg-red-950/30 dark:text-red-300 dark:border-red-900/60'
|
||||
return 'bg-red-50 text-red-700 border-red-200 dark:bg-gray-800 dark:text-red-300 dark:border-gray-700'
|
||||
case 'processing':
|
||||
case 'validating':
|
||||
return 'bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-950/30 dark:text-blue-300 dark:border-blue-900/60'
|
||||
return 'bg-blue-50 text-blue-700 border-blue-200 dark:bg-gray-800 dark:text-blue-300 dark:border-gray-700'
|
||||
default:
|
||||
return 'bg-yellow-50 text-yellow-700 border-yellow-200 dark:bg-yellow-950/30 dark:text-yellow-300 dark:border-yellow-900/60'
|
||||
return 'bg-yellow-50 text-yellow-700 border-yellow-200 dark:bg-gray-800 dark:text-yellow-300 dark:border-gray-700'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -279,7 +279,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
case 'failed':
|
||||
return 'text-red-600 dark:text-red-300'
|
||||
default:
|
||||
return 'text-slate-600 dark:text-slate-400'
|
||||
return 'text-gray-600 dark:text-gray-400'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -331,7 +331,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
return (
|
||||
<div className="flex flex-col w-full mt-4">
|
||||
{/* Navigation Tabs */}
|
||||
<div className="flex space-x-1 mb-4 bg-white dark:bg-slate-900 rounded-lg p-1 shadow-sm border border-slate-200 dark:border-slate-700 flex-shrink-0">
|
||||
<div className="flex space-x-1 mb-4 bg-white dark:bg-gray-800 dark:border-gray-800 rounded-lg p-1 shadow-sm border border-gray-200 flex-shrink-0">
|
||||
{['import', 'preview', 'history'].map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
|
|
@ -339,7 +339,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
className={`px-3 py-2 rounded-md font-medium transition-all duration-200 flex items-center space-x-2 ${
|
||||
activeTab === tab
|
||||
? 'bg-blue-500 text-white shadow-md'
|
||||
: 'text-slate-600 hover:text-slate-800 hover:bg-slate-50 dark:text-slate-300 dark:hover:text-slate-100 dark:hover:bg-slate-800'
|
||||
: 'text-gray-600 hover:text-gray-800 hover:bg-gray-50 dark:text-gray-300 dark:hover:text-gray-100 dark:hover:bg-gray-800'
|
||||
}`}
|
||||
>
|
||||
{tab === 'import' && <FaUpload className="w-4 h-4" />}
|
||||
|
|
@ -358,9 +358,9 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Template Generator - 2/3 width on large screens, full width on mobile */}
|
||||
<div className="lg:col-span-2">
|
||||
<div className="bg-white dark:bg-slate-900 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700">
|
||||
<div className="px-3 py-3 border-b border-slate-200 dark:border-slate-700 flex items-center justify-between">
|
||||
<h3 className="text-xl font-semibold text-slate-800 dark:text-slate-100 flex items-center">
|
||||
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
|
||||
<div className="px-3 py-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<h3 className="text-xl font-semibold text-gray-800 dark:text-gray-100 flex items-center">
|
||||
<FaDownload className="w-4 h-4 mr-2" />
|
||||
{translate('::App.Listforms.ImportManager.TemplateColumns')} (
|
||||
{editableColumns.length})
|
||||
|
|
@ -371,10 +371,10 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
<button
|
||||
onClick={() => generateTemplate('excel')}
|
||||
disabled={generating}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 border border-green-200 dark:border-green-900/60 rounded-md hover:border-green-300 hover:bg-green-50 dark:hover:bg-green-950/30 transition-all duration-200 group disabled:opacity-50 disabled:cursor-not-allowed bg-white dark:bg-slate-900 text-xs"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 border border-green-200 dark:border-gray-700 rounded-md hover:border-green-300 hover:bg-green-50 dark:hover:bg-gray-800 transition-all duration-200 group disabled:opacity-50 disabled:cursor-not-allowed bg-white dark:bg-gray-900 text-xs"
|
||||
>
|
||||
<FaFileExcel className="w-3.5 h-3.5 text-green-500 group-hover:scale-110 transition-transform" />
|
||||
<span className="font-medium text-slate-700 dark:text-slate-200">
|
||||
<span className="font-medium text-gray-700 dark:text-gray-200">
|
||||
{translate('::App.Listforms.ImportManager.ExcelTemplate')}
|
||||
</span>
|
||||
</button>
|
||||
|
|
@ -382,10 +382,10 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
<button
|
||||
onClick={() => generateTemplate('csv')}
|
||||
disabled={generating}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 border border-blue-200 dark:border-blue-900/60 rounded-md hover:border-blue-300 hover:bg-blue-50 dark:hover:bg-blue-950/30 transition-all duration-200 group disabled:opacity-50 disabled:cursor-not-allowed bg-white dark:bg-slate-900 text-xs"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 border border-blue-200 dark:border-gray-700 rounded-md hover:border-blue-300 hover:bg-blue-50 dark:hover:bg-gray-800 transition-all duration-200 group disabled:opacity-50 disabled:cursor-not-allowed bg-white dark:bg-gray-900 text-xs"
|
||||
>
|
||||
<FaFileAlt className="w-3.5 h-3.5 text-blue-500 group-hover:scale-110 transition-transform" />
|
||||
<span className="font-medium text-slate-700 dark:text-slate-200">
|
||||
<span className="font-medium text-gray-700 dark:text-gray-200">
|
||||
{translate('::App.Listforms.ImportManager.CsvTemplate')}
|
||||
</span>
|
||||
</button>
|
||||
|
|
@ -394,38 +394,38 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
|
||||
<div className="max-h-96 overflow-y-auto">
|
||||
<table className="w-full">
|
||||
<thead className="bg-slate-100 dark:bg-slate-800 sticky top-0">
|
||||
<thead className="bg-gray-100 dark:bg-gray-800 sticky top-0">
|
||||
<tr>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase">
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
|
||||
{translate('::App.Listform.ListformField.Column')}
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase">
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
|
||||
{translate('::ListForms.ListFormEdit.Type')}
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase">
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
|
||||
{translate('::App.Required')}
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase">
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
|
||||
{translate('::Abp.Mailing.Default')}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100 dark:divide-slate-800">
|
||||
<tbody className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
{editableColumns.map((column: any) => (
|
||||
<tr key={column.fieldName} className="hover:bg-slate-50 dark:hover:bg-slate-800/70">
|
||||
<td className="px-2 py-2 font-medium text-slate-800 dark:text-slate-100">
|
||||
<tr key={column.fieldName} className="hover:bg-gray-50 dark:hover:bg-gray-800/70">
|
||||
<td className="px-2 py-2 font-medium text-gray-800 dark:text-gray-100">
|
||||
{column.fieldName}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-slate-600 dark:text-slate-300">
|
||||
<td className="px-4 py-2 text-gray-600 dark:text-gray-300">
|
||||
<span
|
||||
className={`px-2 py-1 rounded text-xs font-medium ${
|
||||
column.dataType === 'string'
|
||||
? 'bg-blue-100 text-blue-800 dark:bg-blue-950/40 dark:text-blue-300'
|
||||
? 'bg-blue-100 text-blue-800 dark:bg-gray-800 dark:text-blue-300'
|
||||
: column.dataType === 'number'
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-950/40 dark:text-green-300'
|
||||
? 'bg-green-100 text-green-800 dark:bg-gray-800 dark:text-green-300'
|
||||
: column.dataType === 'boolean'
|
||||
? 'bg-purple-100 text-purple-800 dark:bg-purple-950/40 dark:text-purple-300'
|
||||
: 'bg-orange-100 text-orange-800 dark:bg-orange-950/40 dark:text-orange-300'
|
||||
? 'bg-purple-100 text-purple-800 dark:bg-gray-800 dark:text-purple-300'
|
||||
: 'bg-orange-100 text-orange-800 dark:bg-gray-800 dark:text-orange-300'
|
||||
}`}
|
||||
>
|
||||
{column.dataType}
|
||||
|
|
@ -439,12 +439,12 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
{translate('::App.Listforms.ImportManager.Yes')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-slate-400 dark:text-slate-500">
|
||||
<span className="text-gray-400 dark:text-gray-500">
|
||||
{translate('::App.Listforms.ImportManager.No')}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-slate-600 dark:text-slate-300 text-sm">
|
||||
<td className="px-4 py-2 text-gray-600 dark:text-gray-300 text-sm">
|
||||
{typeof column.defaultValue === 'object'
|
||||
? JSON.stringify(column.defaultValue)
|
||||
: column.defaultValue}
|
||||
|
|
@ -458,7 +458,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
{generating && (
|
||||
<div className="flex items-center justify-center py-4">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
|
||||
<span className="ml-2 text-slate-600 dark:text-slate-400">
|
||||
<span className="ml-2 text-gray-600 dark:text-gray-400">
|
||||
{translate('::App.Listforms.ImportManager.GeneratingTemplate')}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -468,8 +468,8 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
|
||||
{/* File Upload - 1/3 width on large screens, full width on mobile */}
|
||||
<div className="lg:col-span-1">
|
||||
<div className="bg-white dark:bg-slate-900 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 p-4 h-full">
|
||||
<h2 className="text-xl font-semibold text-slate-800 dark:text-slate-100 mb-4 flex items-center">
|
||||
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-4 h-full">
|
||||
<h2 className="text-xl font-semibold text-gray-800 dark:text-gray-100 mb-4 flex items-center">
|
||||
<FaUpload className="w-5 h-5 mr-2 text-green-500" />
|
||||
{translate('::App.Listforms.ImportManager.UploadData')}
|
||||
</h2>
|
||||
|
|
@ -506,9 +506,9 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white dark:bg-slate-900 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 p-12">
|
||||
<div className="text-center text-slate-500 dark:text-slate-400">
|
||||
<FaEye className="w-16 h-16 mx-auto mb-4 text-slate-300 dark:text-slate-600" />
|
||||
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-12">
|
||||
<div className="text-center text-gray-500 dark:text-gray-400">
|
||||
<FaEye className="w-16 h-16 mx-auto mb-4 text-gray-300 dark:text-gray-600" />
|
||||
<div className="text-xl font-medium mb-2">
|
||||
{translate('::App.Listforms.ImportManager.NoDataToPreview')}
|
||||
</div>
|
||||
|
|
@ -520,22 +520,15 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
)}
|
||||
|
||||
{activeTab === 'history' && (
|
||||
<div className="bg-white dark:bg-slate-900 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700">
|
||||
<div className="p-3 border-b border-slate-200 dark:border-slate-700">
|
||||
<h2 className="text-xl font-semibold text-slate-800 dark:text-slate-100 flex items-center">
|
||||
<FaClock className="w-5 h-5 mr-2 text-indigo-500" />
|
||||
{translate('::App.Listforms.ImportManager.ImportHistory')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-slate-100 dark:divide-slate-800">
|
||||
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
|
||||
<div className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
{importHistory.map((session) => (
|
||||
<div
|
||||
key={session.id}
|
||||
className={`p-2 transition-colors border-l-4 ${
|
||||
currentSession?.id === session.id
|
||||
? 'bg-blue-50 border-l-blue-500 hover:bg-blue-100 dark:bg-blue-950/30 dark:hover:bg-blue-950/40'
|
||||
: 'border-l-transparent hover:bg-slate-50 dark:hover:bg-slate-800/70'
|
||||
? 'bg-blue-50 border-l-blue-500 hover:bg-blue-100 dark:bg-gray-800 dark:hover:bg-gray-700'
|
||||
: 'border-l-transparent hover:bg-gray-50 dark:hover:bg-gray-800/70'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -543,15 +536,15 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
{getStatusIcon(session.status)}
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>
|
||||
<div className="font-medium text-slate-800 dark:text-slate-100">
|
||||
<div className="font-medium text-gray-800 dark:text-gray-100">
|
||||
{session.blobName}
|
||||
</div>
|
||||
<div className="text-sm text-slate-500 dark:text-slate-400">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{new Date(session.creationTime).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
{currentSession?.id === session.id && (
|
||||
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-950/40 dark:text-blue-300">
|
||||
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-gray-800 dark:text-blue-300">
|
||||
{translate('::App.Status.Active')}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -560,7 +553,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="text-right">
|
||||
<div className="text-sm font-medium text-slate-800 dark:text-slate-100">
|
||||
<div className="text-sm font-medium text-gray-800 dark:text-gray-100">
|
||||
{session.totalRows} {translate('::App.Listforms.ImportManager.TotalRows')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -578,8 +571,8 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
onClick={() => toggleSessionExecutes(session.id)}
|
||||
className={`p-2 rounded-lg transition-colors ${
|
||||
expandedSessions.has(session.id)
|
||||
? 'text-red-500 bg-red-50 hover:text-red-600 hover:bg-red-100 dark:bg-red-950/30 dark:text-red-300 dark:hover:bg-red-950/40'
|
||||
: 'text-slate-400 hover:text-slate-600 hover:bg-slate-100 dark:text-slate-500 dark:hover:text-slate-300 dark:hover:bg-slate-800'
|
||||
? 'text-red-500 bg-red-50 hover:text-red-600 hover:bg-red-100 dark:bg-gray-800 dark:text-red-300 dark:hover:bg-gray-700'
|
||||
: 'text-gray-400 hover:text-gray-600 hover:bg-gray-100 dark:text-gray-500 dark:hover:text-gray-300 dark:hover:bg-gray-800'
|
||||
}`}
|
||||
title={translate('::App.Listforms.ImportManager.ViewExecutionDetails')}
|
||||
>
|
||||
|
|
@ -609,7 +602,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
}
|
||||
}
|
||||
}}
|
||||
className="p-2 rounded-lg transition-colors text-slate-400 hover:text-blue-500 hover:bg-blue-50 dark:text-slate-500 dark:hover:text-blue-300 dark:hover:bg-blue-950/30"
|
||||
className="p-2 rounded-lg transition-colors text-gray-400 hover:text-blue-500 hover:bg-blue-50 dark:text-gray-500 dark:hover:text-blue-300 dark:hover:bg-gray-800"
|
||||
title={translate('::App.Listforms.ImportManager.RefreshExecutionDetails')}
|
||||
>
|
||||
<FaSync className="w-4 h-4" />
|
||||
|
|
@ -625,8 +618,8 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
disabled={currentSession?.id === session.id}
|
||||
className={`p-2 rounded-lg transition-colors ${
|
||||
currentSession?.id === session.id
|
||||
? 'text-slate-300 dark:text-slate-600 cursor-not-allowed'
|
||||
: 'text-slate-400 hover:text-red-500 hover:bg-red-50 dark:text-slate-500 dark:hover:text-red-300 dark:hover:bg-red-950/30'
|
||||
? 'text-gray-300 dark:text-gray-600 cursor-not-allowed'
|
||||
: 'text-gray-400 hover:text-red-500 hover:bg-red-50 dark:text-gray-500 dark:hover:text-red-300 dark:hover:bg-gray-800'
|
||||
}`}
|
||||
title={
|
||||
currentSession?.id === session.id
|
||||
|
|
@ -647,10 +640,10 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
session.status === 'executed_with_errors' ||
|
||||
session.status === 'failed' ||
|
||||
session.status === 'execute_failed'
|
||||
? 'bg-red-50 text-red-700 border border-red-200 dark:bg-red-950/30 dark:text-red-300 dark:border-red-900/60'
|
||||
? 'bg-red-50 text-red-700 border border-red-200 dark:bg-gray-800 dark:text-red-300 dark:border-gray-700'
|
||||
: session.status === 'uploaded'
|
||||
? 'bg-blue-50 text-blue-700 border border-blue-200 dark:bg-blue-950/30 dark:text-blue-300 dark:border-blue-900/60'
|
||||
: 'bg-green-50 text-green-700 border border-green-200 dark:bg-green-950/30 dark:text-green-300 dark:border-green-900/60'
|
||||
? 'bg-blue-50 text-blue-700 border border-blue-200 dark:bg-gray-800 dark:text-blue-300 dark:border-gray-700'
|
||||
: 'bg-green-50 text-green-700 border border-green-200 dark:bg-gray-800 dark:text-green-300 dark:border-gray-700'
|
||||
}`}
|
||||
>
|
||||
<span className="mt-0.5 flex-shrink-0">
|
||||
|
|
@ -670,10 +663,10 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
|
||||
{/* Execute Details Section */}
|
||||
{expandedSessions.has(session.id) && (
|
||||
<div className="mt-3 bg-gradient-to-r from-indigo-50 to-blue-50 dark:from-slate-800 dark:to-slate-800 border border-indigo-100 dark:border-slate-700 rounded-lg shadow-sm hover:shadow-md transition-shadow">
|
||||
<div className="mt-3 bg-gradient-to-r from-indigo-50 to-blue-50 dark:from-gray-800 dark:to-gray-800 border border-indigo-100 dark:border-gray-700 rounded-lg shadow-sm hover:shadow-md transition-shadow">
|
||||
<div className="p-3">
|
||||
{loadingExecutes.has(session.id) ? (
|
||||
<div className="flex items-center space-x-2 text-slate-500 dark:text-slate-400 py-2">
|
||||
<div className="flex items-center space-x-2 text-gray-500 dark:text-gray-400 py-2">
|
||||
<FaSync className="w-4 h-4 animate-spin" />
|
||||
<span className="text-sm">
|
||||
{translate('::App.Listforms.ImportManager.LoadingExecutionDetails')}
|
||||
|
|
@ -683,22 +676,22 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
sessionExecutes[session.id].length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{sessionExecutes[session.id].map((execute) => (
|
||||
<div key={execute.id} className="p-3 rounded-lg dark:bg-slate-900/40">
|
||||
<div key={execute.id} className="p-3 rounded-lg dark:bg-gray-900/40">
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Sol: Tarih */}
|
||||
<div className="flex-shrink-0">
|
||||
<div className="text-lg text-slate-500 dark:text-slate-400">
|
||||
<div className="text-lg text-gray-500 dark:text-gray-400">
|
||||
{new Date(execute.creationTime).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Orta: Executed, Valid, Errors */}
|
||||
<div className="flex items-center space-x-4 text-xs text-slate-600 dark:text-slate-400">
|
||||
<div className="flex items-center space-x-4 text-xs text-gray-600 dark:text-gray-400">
|
||||
<div className="text-center">
|
||||
<div className="font-medium text-slate-800 dark:text-slate-100">
|
||||
<div className="font-medium text-gray-800 dark:text-gray-100">
|
||||
{execute.execRows}
|
||||
</div>
|
||||
<div className="text-slate-500 dark:text-slate-400">
|
||||
<div className="text-gray-500 dark:text-gray-400">
|
||||
{translate('::App.Listforms.ImportManager.Executed')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -706,7 +699,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
<div className="font-medium text-green-600 dark:text-green-300">
|
||||
{execute.validRows}
|
||||
</div>
|
||||
<div className="text-slate-500 dark:text-slate-400">
|
||||
<div className="text-gray-500 dark:text-gray-400">
|
||||
{translate('::App.Listforms.ImportManager.Valid')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -714,7 +707,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
<div className="font-medium text-red-600 dark:text-red-300">
|
||||
{execute.errorRows}
|
||||
</div>
|
||||
<div className="text-slate-500 dark:text-slate-400">
|
||||
<div className="text-gray-500 dark:text-gray-400">
|
||||
{translate('::App.Listforms.ImportManager.Errors')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -753,10 +746,10 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
</button>
|
||||
|
||||
{expandedErrors.has(execute.id) && (
|
||||
<div className="mt-2 max-h-48 overflow-y-auto rounded border border-orange-200 bg-orange-50 dark:border-orange-900/60 dark:bg-orange-950/30">
|
||||
<div className="mt-2 max-h-48 overflow-y-auto rounded border border-orange-200 bg-orange-50 dark:border-gray-700 dark:bg-gray-800">
|
||||
{parseErrors(execute.errorsJson).length > 0 ? (
|
||||
<table className="w-full text-xs">
|
||||
<thead className="bg-orange-100 dark:bg-orange-950/50 sticky top-0">
|
||||
<thead className="bg-orange-100 dark:bg-gray-800 sticky top-0">
|
||||
<tr>
|
||||
<th className="px-3 py-1 text-left font-medium text-orange-700 dark:text-orange-300 w-16">
|
||||
Satır
|
||||
|
|
@ -766,16 +759,16 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-orange-100 dark:divide-orange-900/50">
|
||||
<tbody className="divide-y divide-orange-100 dark:divide-gray-700">
|
||||
{parseErrors(execute.errorsJson).map((err, idx) => (
|
||||
<tr
|
||||
key={idx}
|
||||
className="hover:bg-orange-100 dark:hover:bg-orange-950/50"
|
||||
className="hover:bg-orange-100 dark:hover:bg-gray-700"
|
||||
>
|
||||
<td className="px-3 py-1 text-orange-700 dark:text-orange-300 font-medium">
|
||||
{err.row}
|
||||
</td>
|
||||
<td className="px-3 py-1 text-slate-700 dark:text-slate-200">
|
||||
<td className="px-3 py-1 text-gray-700 dark:text-gray-200">
|
||||
{err.message}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -795,7 +788,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-slate-500 dark:text-slate-400 py-2">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 py-2">
|
||||
{translate('::App.Listforms.ImportManager.NoExecutionRecords')}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -806,8 +799,8 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
))}
|
||||
|
||||
{importHistory.length === 0 && (
|
||||
<div className="p-12 text-center text-slate-500 dark:text-slate-400">
|
||||
<FaClock className="w-12 h-12 mx-auto mb-4 text-slate-300 dark:text-slate-600" />
|
||||
<div className="p-12 text-center text-gray-500 dark:text-gray-400">
|
||||
<FaClock className="w-12 h-12 mx-auto mb-4 text-gray-300 dark:text-gray-600" />
|
||||
<div className="text-lg font-medium mb-2">
|
||||
{translate('::App.Listforms.ImportManager.NoImportHistory')}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -100,13 +100,13 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
|||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'uploaded':
|
||||
return 'text-green-600 bg-green-50 border-green-200 dark:text-green-300 dark:bg-green-950/30 dark:border-green-900/60'
|
||||
return 'text-green-600 bg-green-50 border-green-200 dark:text-green-300 dark:bg-gray-800 dark:border-gray-700'
|
||||
case 'failed':
|
||||
return 'text-red-600 bg-red-50 border-red-200 dark:text-red-300 dark:bg-red-950/30 dark:border-red-900/60'
|
||||
return 'text-red-600 bg-red-50 border-red-200 dark:text-red-300 dark:bg-gray-800 dark:border-gray-700'
|
||||
case 'validating':
|
||||
return 'text-yellow-600 bg-yellow-50 border-yellow-200 dark:text-yellow-300 dark:bg-yellow-950/30 dark:border-yellow-900/60'
|
||||
return 'text-yellow-600 bg-yellow-50 border-yellow-200 dark:text-yellow-300 dark:bg-gray-800 dark:border-gray-700'
|
||||
default:
|
||||
return 'text-blue-600 bg-blue-50 border-blue-200 dark:text-blue-300 dark:bg-blue-950/30 dark:border-blue-900/60'
|
||||
return 'text-blue-600 bg-blue-50 border-blue-200 dark:text-blue-300 dark:bg-gray-800 dark:border-gray-700'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -145,14 +145,14 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-slate-900 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700">
|
||||
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
|
||||
{/* Header */}
|
||||
<div className="p-3 border-b border-slate-200 dark:border-slate-700">
|
||||
<div className="p-3 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-center gap-4">
|
||||
{/* Başlık kısmı - Üstte mobile, solda desktop */}
|
||||
<div className="flex items-center order-1 lg:order-none">
|
||||
<FaEye className="w-5 h-5 mr-2 text-blue-500" />
|
||||
<h3 className="text-xl font-semibold text-slate-800 dark:text-slate-100">
|
||||
<h3 className="text-xl font-semibold text-gray-800 dark:text-gray-100">
|
||||
{translate('::App.Listforms.ImportManager.ImportPreviewTitle')}
|
||||
</h3>
|
||||
</div>
|
||||
|
|
@ -160,7 +160,7 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
|||
{/* İstatistik kartları - Mobile'da alt alta, desktop'ta yan yana */}
|
||||
<div className="order-3 lg:order-none lg:absolute lg:left-1/2 lg:transform lg:-translate-x-1/2">
|
||||
<div className="flex flex-col sm:flex-row justify-center gap-2">
|
||||
<div className="text-center px-3 py-1 bg-blue-50 dark:bg-blue-950/30 rounded-full border border-blue-200 dark:border-blue-900/60 font-bold text-blue-600 dark:text-blue-300">
|
||||
<div className="text-center px-3 py-1 bg-blue-50 dark:bg-gray-800 rounded-full border border-blue-200 dark:border-gray-700 font-bold text-blue-600 dark:text-blue-300">
|
||||
{previewData?.rows?.length || session.totalRows || 0}{' '}
|
||||
<span className="text-xs text-blue-700 dark:text-blue-300">
|
||||
{translate('::App.Listforms.ImportManager.TotalRows')}
|
||||
|
|
@ -184,16 +184,16 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
|||
|
||||
{/* Preview Data */}
|
||||
{previewData && previewData.headers && previewData.headers.length > 0 ? (
|
||||
<div className="p-3 border-b border-slate-200 dark:border-slate-700">
|
||||
<h4 className="font-semibold text-slate-800 dark:text-slate-100 mb-4">
|
||||
<div className="p-3 border-b border-gray-200 dark:border-gray-700">
|
||||
<h4 className="font-semibold text-gray-800 dark:text-gray-100 mb-4">
|
||||
{translate('::App.Listforms.ImportManager.DataPreviewTitle')}
|
||||
</h4>
|
||||
|
||||
<div className="overflow-auto border border-slate-200 dark:border-slate-700 rounded-lg max-h-90">
|
||||
<div className="overflow-auto border border-gray-200 dark:border-gray-700 rounded-lg max-h-90">
|
||||
<table className="w-full text-sm min-w-full">
|
||||
<thead className="bg-slate-50 dark:bg-slate-800 sticky top-0 z-10">
|
||||
<thead className="bg-gray-50 dark:bg-gray-800 sticky top-0 z-10">
|
||||
<tr>
|
||||
<th className="px-4 py-2 text-left font-medium text-slate-700 dark:text-slate-200 whitespace-nowrap w-12">
|
||||
<th className="px-4 py-2 text-left font-medium text-gray-700 dark:text-gray-200 whitespace-nowrap w-12">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectAll}
|
||||
|
|
@ -204,19 +204,19 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
|||
{previewData.headers.map((header: string, index: number) => (
|
||||
<th
|
||||
key={index}
|
||||
className="px-4 py-2 text-left font-medium text-slate-700 dark:text-slate-200 whitespace-nowrap"
|
||||
className="px-4 py-2 text-left font-medium text-gray-700 dark:text-gray-200 whitespace-nowrap"
|
||||
>
|
||||
{header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100 dark:divide-slate-800">
|
||||
<tbody className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
{previewData.rows.map((row: any[], rowIndex: number) => (
|
||||
<tr
|
||||
key={rowIndex}
|
||||
className={`hover:bg-slate-50 dark:hover:bg-slate-800/70 ${
|
||||
selectedRows.includes(rowIndex) ? 'bg-blue-50 dark:bg-blue-950/30' : ''
|
||||
className={`hover:bg-gray-50 dark:hover:bg-gray-800/70 ${
|
||||
selectedRows.includes(rowIndex) ? 'bg-blue-50 dark:bg-gray-800' : ''
|
||||
}`}
|
||||
>
|
||||
<td className="px-4 py-2">
|
||||
|
|
@ -230,7 +230,7 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
|||
{row.map((cell, cellIndex) => (
|
||||
<td
|
||||
key={cellIndex}
|
||||
className="px-4 py-2 text-slate-600 dark:text-slate-300 whitespace-nowrap max-w-xs truncate"
|
||||
className="px-4 py-2 text-gray-600 dark:text-gray-300 whitespace-nowrap max-w-xs truncate"
|
||||
>
|
||||
{cell?.toString() || '-'}
|
||||
</td>
|
||||
|
|
@ -242,25 +242,25 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
) : previewData && previewData.headers && previewData.headers.length === 0 ? (
|
||||
<div className="p-6 border-b border-slate-200 dark:border-slate-700">
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="text-center py-8">
|
||||
<FaExclamationTriangle className="w-12 h-12 mx-auto mb-4 text-yellow-500" />
|
||||
<h4 className="font-semibold text-slate-800 dark:text-slate-100 mb-2">
|
||||
<h4 className="font-semibold text-gray-800 dark:text-gray-100 mb-2">
|
||||
{translate('::App.Listforms.ImportManager.NoDataFoundTitle')}
|
||||
</h4>
|
||||
<p className="text-slate-600 dark:text-slate-400">
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
{translate('::App.Listforms.ImportManager.NoDataFoundDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-6 border-b border-slate-200 dark:border-slate-700">
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="text-center py-8">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-4"></div>
|
||||
<h4 className="font-semibold text-slate-800 dark:text-slate-100 mb-2">
|
||||
<h4 className="font-semibold text-gray-800 dark:text-gray-100 mb-2">
|
||||
{translate('::App.Listforms.ImportManager.LoadingPreviewTitle')}
|
||||
</h4>
|
||||
<p className="text-slate-600 dark:text-slate-400">
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
{translate('::App.Listforms.ImportManager.LoadingPreviewDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -271,7 +271,7 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
|||
<div className="p-3">
|
||||
{/* Success Message */}
|
||||
{showSuccessMessage && lastExecutionResult && (
|
||||
<div className="mb-4 p-4 bg-green-50 dark:bg-green-950/30 border border-green-200 dark:border-green-900/60 rounded-lg">
|
||||
<div className="mb-4 p-4 bg-green-50 dark:bg-gray-800 border border-green-200 dark:border-gray-700 rounded-lg">
|
||||
<div className="flex items-center space-x-2 text-green-700 dark:text-green-300">
|
||||
<FaCheckCircle className="w-5 h-5" />
|
||||
<span className="font-medium">
|
||||
|
|
@ -314,7 +314,7 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
|||
</div>
|
||||
|
||||
<div className="flex space-x-3">
|
||||
<button className="px-4 py-2 text-slate-600 hover:text-slate-800 hover:bg-slate-100 dark:text-slate-300 dark:hover:text-slate-100 dark:hover:bg-slate-800 rounded-lg transition-colors flex items-center space-x-2">
|
||||
<button className="px-4 py-2 text-gray-600 hover:text-gray-800 hover:bg-gray-100 dark:text-gray-300 dark:hover:text-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors flex items-center space-x-2">
|
||||
<FaTimes className="w-4 h-4" />
|
||||
<span>{translate('::Cancel')}</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -59,17 +59,17 @@ export const ImportProgress: React.FC<ImportProgressProps> = ({ session }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-slate-900 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 p-6">
|
||||
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||
<div className="text-center space-y-6">
|
||||
{/* Status Icon */}
|
||||
<div className="flex justify-center">{getStatusIcon()}</div>
|
||||
|
||||
{/* Status Message */}
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-slate-800 dark:text-slate-100 mb-2">
|
||||
<h3 className="text-xl font-semibold text-gray-800 dark:text-gray-100 mb-2">
|
||||
{getStatusMessage()}
|
||||
</h3>
|
||||
<p className="text-slate-600 dark:text-slate-400">
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
{translate('::App.Listforms.Status.Processing')}{' '}
|
||||
{session.blobName}
|
||||
</p>
|
||||
|
|
@ -77,11 +77,11 @@ export const ImportProgress: React.FC<ImportProgressProps> = ({ session }) => {
|
|||
|
||||
{/* Progress Bar */}
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<div className="flex justify-between text-sm text-slate-600 dark:text-slate-400 mb-2">
|
||||
<div className="flex justify-between text-sm text-gray-600 dark:text-gray-400 mb-2">
|
||||
<span>{translate('::App.Listforms.ImportManager.ImportProgress.ProgressLabel')}</span>
|
||||
<span>{Math.round(getProgressPercentage())}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2">
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div
|
||||
className="bg-blue-500 h-2 rounded-full transition-all duration-300 ease-out"
|
||||
style={{ width: `${getProgressPercentage()}%` }}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import WindowControls from '../WindowControls'
|
|||
import { motion } from 'framer-motion'
|
||||
import { SCREENS } from '@/utils/tailwind'
|
||||
import useWindowSize from '../hooks/useWindowSize'
|
||||
import { useState, useCallback, createContext, useContext } from 'react'
|
||||
import { useState, useCallback, createContext, useContext, useEffect, useRef } from 'react'
|
||||
import type ReactModal from 'react-modal'
|
||||
import type { MouseEvent, ReactNode } from 'react'
|
||||
|
||||
|
|
@ -63,6 +63,7 @@ DialogFooter.displayName = 'Dialog.Footer'
|
|||
export interface DialogProps extends ReactModal.Props {
|
||||
closable?: boolean
|
||||
contentClassName?: string
|
||||
draggable?: boolean
|
||||
height?: string | number
|
||||
onClose?: (e: MouseEvent<HTMLSpanElement>) => void
|
||||
width?: string | number
|
||||
|
|
@ -74,10 +75,16 @@ export interface DialogProps extends ReactModal.Props {
|
|||
const Dialog = (props: DialogProps) => {
|
||||
const currentSize = useWindowSize()
|
||||
const [isMaximized, setIsMaximized] = useState(false)
|
||||
const [originalDimensions, setOriginalDimensions] = useState<{
|
||||
width?: string | number
|
||||
height?: string | number
|
||||
}>({})
|
||||
const [position, setPosition] = useState<{ x: number; y: number } | null>(null)
|
||||
const [isDragHandleHovered, setIsDragHandleHovered] = useState(false)
|
||||
const dragStartRef = useRef<{
|
||||
pointerX: number
|
||||
pointerY: number
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
} | null>(null)
|
||||
|
||||
const {
|
||||
bodyOpenClassName,
|
||||
|
|
@ -87,6 +94,7 @@ const Dialog = (props: DialogProps) => {
|
|||
closable = true,
|
||||
closeTimeoutMS = 150,
|
||||
contentClassName,
|
||||
draggable = true,
|
||||
height,
|
||||
isOpen,
|
||||
onClose,
|
||||
|
|
@ -100,6 +108,37 @@ const Dialog = (props: DialogProps) => {
|
|||
...rest
|
||||
} = props
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setPosition(null)
|
||||
setIsMaximized(false)
|
||||
setIsDragHandleHovered(false)
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
setPosition((currentPosition) => {
|
||||
if (!currentPosition) {
|
||||
return currentPosition
|
||||
}
|
||||
|
||||
const nextPosition = {
|
||||
x: Math.min(currentPosition.x, window.innerWidth - 24),
|
||||
y: Math.min(currentPosition.y, window.innerHeight - 24),
|
||||
}
|
||||
|
||||
if (nextPosition.x === currentPosition.x && nextPosition.y === currentPosition.y) {
|
||||
return currentPosition
|
||||
}
|
||||
|
||||
return nextPosition
|
||||
})
|
||||
}, [currentSize.width, currentSize.height])
|
||||
|
||||
const onCloseClick = (e: MouseEvent<HTMLSpanElement>) => {
|
||||
onClose?.(e)
|
||||
}
|
||||
|
|
@ -108,12 +147,12 @@ const Dialog = (props: DialogProps) => {
|
|||
(e: MouseEvent<HTMLSpanElement>) => {
|
||||
e.stopPropagation()
|
||||
if (!isMaximized) {
|
||||
setOriginalDimensions({ width, height })
|
||||
setIsMaximized(true)
|
||||
setIsDragHandleHovered(false)
|
||||
onMaximize?.()
|
||||
}
|
||||
},
|
||||
[isMaximized, width, height, onMaximize],
|
||||
[isMaximized, onMaximize],
|
||||
)
|
||||
|
||||
const handleRestore = useCallback(
|
||||
|
|
@ -127,6 +166,125 @@ const Dialog = (props: DialogProps) => {
|
|||
[isMaximized, onRestore],
|
||||
)
|
||||
|
||||
const isDragHandle = (
|
||||
target: EventTarget | null,
|
||||
currentTarget: HTMLDivElement,
|
||||
clientY: number,
|
||||
) => {
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const interactiveSelector = [
|
||||
'a',
|
||||
'button',
|
||||
'input',
|
||||
'textarea',
|
||||
'select',
|
||||
'[role="button"]',
|
||||
'[contenteditable="true"]',
|
||||
'.window-controls',
|
||||
'.close-btn',
|
||||
].join(',')
|
||||
|
||||
if (target.closest(interactiveSelector)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const header = target.closest('.dialog-header')
|
||||
if (header && currentTarget.contains(header)) {
|
||||
return true
|
||||
}
|
||||
|
||||
const topDragArea = 56
|
||||
const bounds = currentTarget.getBoundingClientRect()
|
||||
return clientY - bounds.top <= topDragArea
|
||||
}
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(e: MouseEvent<HTMLDivElement>) => {
|
||||
if (
|
||||
!draggable ||
|
||||
isMaximized ||
|
||||
e.button !== 0 ||
|
||||
!isDragHandle(e.target, e.currentTarget, e.clientY)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const modalElement = e.currentTarget.closest('.dialog')
|
||||
if (!(modalElement instanceof HTMLElement)) {
|
||||
return
|
||||
}
|
||||
|
||||
const bounds = modalElement.getBoundingClientRect()
|
||||
|
||||
dragStartRef.current = {
|
||||
pointerX: e.clientX,
|
||||
pointerY: e.clientY,
|
||||
x: bounds.left,
|
||||
y: bounds.top,
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
}
|
||||
|
||||
setPosition({ x: bounds.left, y: bounds.top })
|
||||
e.preventDefault()
|
||||
},
|
||||
[draggable, isMaximized],
|
||||
)
|
||||
|
||||
const handleDragCursor = useCallback(
|
||||
(e: MouseEvent<HTMLDivElement>) => {
|
||||
const nextIsDragHandle =
|
||||
draggable && !isMaximized && isDragHandle(e.target, e.currentTarget, e.clientY)
|
||||
|
||||
setIsDragHandleHovered((current) =>
|
||||
current === nextIsDragHandle ? current : nextIsDragHandle,
|
||||
)
|
||||
},
|
||||
[draggable, isMaximized],
|
||||
)
|
||||
|
||||
const handleDragCursorLeave = useCallback(() => {
|
||||
setIsDragHandleHovered(false)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
const handleDragMove = (e: globalThis.MouseEvent) => {
|
||||
const dragStart = dragStartRef.current
|
||||
if (!dragStart) {
|
||||
return
|
||||
}
|
||||
|
||||
const nextX = dragStart.x + e.clientX - dragStart.pointerX
|
||||
const nextY = dragStart.y + e.clientY - dragStart.pointerY
|
||||
const maxX = window.innerWidth - Math.min(dragStart.width, 120)
|
||||
const maxY = window.innerHeight - Math.min(dragStart.height, 80)
|
||||
|
||||
setPosition({
|
||||
x: Math.max(0, Math.min(nextX, maxX)),
|
||||
y: Math.max(0, Math.min(nextY, maxY)),
|
||||
})
|
||||
}
|
||||
|
||||
const handleDragEnd = () => {
|
||||
dragStartRef.current = null
|
||||
}
|
||||
|
||||
window.addEventListener('mousemove', handleDragMove)
|
||||
window.addEventListener('mouseup', handleDragEnd)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('mousemove', handleDragMove)
|
||||
window.removeEventListener('mouseup', handleDragEnd)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const renderCloseButton = (
|
||||
<CloseButton absolute className="ltr:right-6 rtl:left-6" onClick={onCloseClick} />
|
||||
)
|
||||
|
|
@ -182,6 +340,15 @@ const Dialog = (props: DialogProps) => {
|
|||
zIndex: 1000,
|
||||
}
|
||||
} else {
|
||||
if (position) {
|
||||
contentStyle.content.position = 'fixed'
|
||||
contentStyle.content.top = position.y
|
||||
contentStyle.content.left = position.x
|
||||
contentStyle.content.right = 'auto'
|
||||
contentStyle.content.bottom = 'auto'
|
||||
contentStyle.content.margin = 0
|
||||
}
|
||||
|
||||
if (currentWidth !== undefined) {
|
||||
contentStyle.content.width = currentWidth
|
||||
|
||||
|
|
@ -232,7 +399,11 @@ const Dialog = (props: DialogProps) => {
|
|||
style={{
|
||||
width: isMaximized ? '100vw' : 'auto',
|
||||
height: isMaximized ? '100vh' : 'auto',
|
||||
cursor: isDragHandleHovered || dragStartRef.current ? 'move' : undefined,
|
||||
}}
|
||||
onMouseDown={handleDragStart}
|
||||
onMouseMove={handleDragCursor}
|
||||
onMouseLeave={handleDragCursorLeave}
|
||||
>
|
||||
{closable && !showWindowControls && renderCloseButton}
|
||||
{closable && showWindowControls && renderWindowControls}
|
||||
|
|
|
|||
|
|
@ -505,6 +505,7 @@ export interface GridEditingPopupDto {
|
|||
resizeEnabled: boolean
|
||||
dragEnabled: boolean
|
||||
restorePosition: boolean
|
||||
position?: any
|
||||
}
|
||||
|
||||
export interface GridFilterRowDto {
|
||||
|
|
|
|||
|
|
@ -2739,7 +2739,7 @@ const SqlTableDesignerDialog = ({
|
|||
|
||||
return (
|
||||
<Dialog isOpen={isOpen} onClose={handleClose} onRequestClose={handleClose} width={1100}>
|
||||
<Dialog.Header className="flex flex-col gap-2">
|
||||
<Dialog.Header className="flex flex-col gap-2 -mt-5 p-2 ">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3 border-b pb-3 flex-shrink-0">
|
||||
<FaTable className="text-2xl text-blue-500" />
|
||||
|
|
|
|||
|
|
@ -1557,7 +1557,7 @@ const Grid = (props: GridProps) => {
|
|||
at: 'top center',
|
||||
of: typeof window !== 'undefined' ? window : undefined,
|
||||
}
|
||||
: undefined,
|
||||
: gridDto.gridOptions.editingOptionDto?.popup?.position,
|
||||
resizeEnabled: gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled,
|
||||
fullScreen: isPopupFullScreen,
|
||||
dragEnabled: gridDto.gridOptions.editingOptionDto?.popup?.dragEnabled,
|
||||
|
|
@ -1935,7 +1935,12 @@ const Grid = (props: GridProps) => {
|
|||
onClose={() => filterData.setIsImportModalOpen(false)}
|
||||
onRequestClose={() => filterData.setIsImportModalOpen(false)}
|
||||
>
|
||||
<Dialog.Body className="flex flex-col">
|
||||
<Dialog.Header className="-mt-5 p-2 border-b border-gray-200 dark:border-gray-700">
|
||||
<h2 className="text-lg font-semibold text-gray-800 dark:text-gray-100">
|
||||
{translate('::ListForms.ListForm.ImportManager')}
|
||||
</h2>
|
||||
</Dialog.Header>
|
||||
<Dialog.Body className="flex flex-col mt-2">
|
||||
<ImportDashboard gridDto={gridDto} />
|
||||
</Dialog.Body>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -1217,7 +1217,7 @@ const Tree = (props: TreeProps) => {
|
|||
at: 'top center',
|
||||
of: typeof window !== 'undefined' ? window : undefined,
|
||||
}
|
||||
: undefined,
|
||||
: gridDto.gridOptions.editingOptionDto?.popup?.position,
|
||||
resizeEnabled: gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled,
|
||||
fullScreen: isPopupFullScreen,
|
||||
dragEnabled: gridDto.gridOptions.editingOptionDto?.popup?.dragEnabled,
|
||||
|
|
@ -1675,6 +1675,11 @@ const Tree = (props: TreeProps) => {
|
|||
onClose={() => filterData.setIsImportModalOpen(false)}
|
||||
onRequestClose={() => filterData.setIsImportModalOpen(false)}
|
||||
>
|
||||
<Dialog.Header className="-mx-6 -mt-6 flex h-11 items-center px-6 pr-20 border-b border-gray-200 dark:border-gray-700">
|
||||
<h2 className="text-lg font-semibold text-gray-800 dark:text-gray-100">
|
||||
{translate('::ListForms.ListForm.ImportManager')}
|
||||
</h2>
|
||||
</Dialog.Header>
|
||||
<Dialog.Body className="flex flex-col">
|
||||
<ImportDashboard gridDto={gridDto} />
|
||||
</Dialog.Body>
|
||||
|
|
|
|||
Loading…
Reference in a new issue