Dialog Maximize özelliği eklendi
This commit is contained in:
parent
df9b6ff362
commit
66037bf001
16 changed files with 1336 additions and 1029 deletions
|
|
@ -648,6 +648,12 @@
|
||||||
"en": "Forum",
|
"en": "Forum",
|
||||||
"tr": "Forum"
|
"tr": "Forum"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Forum.Dashboard",
|
||||||
|
"en": "Dashboard",
|
||||||
|
"tr": "Gösterge Paneli"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.Forum.Dashboard.Categories",
|
"key": "App.Forum.Dashboard.Categories",
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,11 @@
|
||||||
|
|
||||||
.dialog-content.maximized {
|
.dialog-content.maximized {
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
max-height: 100vh !important;
|
|
||||||
height: 100vh !important;
|
height: 100vh !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
@apply my-0;
|
@apply my-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useState, useEffect, useMemo } from 'react'
|
import React, { useState, useEffect, useMemo } from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
import {
|
import {
|
||||||
FaUpload,
|
FaUpload,
|
||||||
FaCheckCircle,
|
FaCheckCircle,
|
||||||
|
|
@ -18,6 +19,7 @@ import { ListFormImportDto, ListFormImportExecuteDto } from '@/proxy/imports/mod
|
||||||
import { ImportService } from '@/services/import.service'
|
import { ImportService } from '@/services/import.service'
|
||||||
import { GridDto } from '@/proxy/form/models'
|
import { GridDto } from '@/proxy/form/models'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
|
||||||
|
|
||||||
interface ImportDashboardProps {
|
interface ImportDashboardProps {
|
||||||
gridDto: GridDto
|
gridDto: GridDto
|
||||||
|
|
@ -214,16 +216,17 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
const editableColumns = getEditableColumns()
|
const editableColumns = getEditableColumns()
|
||||||
|
const { isMaximized } = useDialogContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto px-4 py-2">
|
<div className="flex flex-col w-full mt-4">
|
||||||
{/* Navigation Tabs */}
|
{/* Navigation Tabs */}
|
||||||
<div className="flex space-x-1 mb-4 bg-white rounded-lg p-1 shadow-sm border border-slate-200">
|
<div className="flex space-x-1 mb-4 bg-white rounded-lg p-1 shadow-sm border border-slate-200 flex-shrink-0">
|
||||||
{['import', 'preview', 'history'].map((tab) => (
|
{['import', 'preview', 'history'].map((tab) => (
|
||||||
<button
|
<button
|
||||||
key={tab}
|
key={tab}
|
||||||
onClick={() => setActiveTab(tab as TabNames)}
|
onClick={() => setActiveTab(tab as TabNames)}
|
||||||
className={`px-6 py-3 rounded-md font-medium transition-all duration-200 flex items-center space-x-2 ${
|
className={`px-3 py-2 rounded-md font-medium transition-all duration-200 flex items-center space-x-2 ${
|
||||||
activeTab === tab
|
activeTab === tab
|
||||||
? 'bg-blue-500 text-white shadow-md'
|
? 'bg-blue-500 text-white shadow-md'
|
||||||
: 'text-slate-600 hover:text-slate-800 hover:bg-slate-50'
|
: 'text-slate-600 hover:text-slate-800 hover:bg-slate-50'
|
||||||
|
|
@ -238,400 +241,403 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
{activeTab === 'import' && (
|
<div className={classNames(isMaximized ? 'flex-1 min-h-0 overflow-auto' : '')}>
|
||||||
<div className="space-y-6">
|
{activeTab === 'import' && (
|
||||||
{/* Template Generator & File Upload - Side by Side */}
|
<div className="space-y-6">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
{/* Template Generator & File Upload - Side by Side */}
|
||||||
{/* Template Generator - 2/3 width on large screens, full width on mobile */}
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<div className="lg:col-span-2">
|
{/* Template Generator - 2/3 width on large screens, full width on mobile */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200">
|
<div className="lg:col-span-2">
|
||||||
<div className="px-3 py-3 border-b flex items-center justify-between">
|
<div className="bg-white rounded-xl shadow-sm border border-slate-200">
|
||||||
<h3 className="text-xl font-semibold text-slate-800 flex items-center">
|
<div className="px-3 py-3 border-b flex items-center justify-between">
|
||||||
<FaDownload className="w-4 h-4 mr-2" />
|
<h3 className="text-xl font-semibold text-slate-800 flex items-center">
|
||||||
{translate('::App.Listforms.ImportManager.TemplateColumns')} (
|
<FaDownload className="w-4 h-4 mr-2" />
|
||||||
{editableColumns.length})
|
{translate('::App.Listforms.ImportManager.TemplateColumns')} (
|
||||||
</h3>
|
{editableColumns.length})
|
||||||
|
</h3>
|
||||||
|
|
||||||
{/* Template Options */}
|
{/* Template Options */}
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => generateTemplate('excel')}
|
onClick={() => generateTemplate('excel')}
|
||||||
disabled={generating}
|
disabled={generating}
|
||||||
className="flex items-center gap-1.5 px-3 py-1.5 border border-green-200 rounded-md hover:border-green-300 hover:bg-green-50 transition-all duration-200 group disabled:opacity-50 disabled:cursor-not-allowed bg-white text-xs"
|
className="flex items-center gap-1.5 px-3 py-1.5 border border-green-200 rounded-md hover:border-green-300 hover:bg-green-50 transition-all duration-200 group disabled:opacity-50 disabled:cursor-not-allowed bg-white text-xs"
|
||||||
>
|
>
|
||||||
<FaFileExcel className="w-3.5 h-3.5 text-green-500 group-hover:scale-110 transition-transform" />
|
<FaFileExcel className="w-3.5 h-3.5 text-green-500 group-hover:scale-110 transition-transform" />
|
||||||
<span className="font-medium text-slate-700">
|
<span className="font-medium text-slate-700">
|
||||||
{translate('::App.Listforms.ImportManager.ExcelTemplate')}
|
{translate('::App.Listforms.ImportManager.ExcelTemplate')}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => generateTemplate('csv')}
|
onClick={() => generateTemplate('csv')}
|
||||||
disabled={generating}
|
disabled={generating}
|
||||||
className="flex items-center gap-1.5 px-3 py-1.5 border border-blue-200 rounded-md hover:border-blue-300 hover:bg-blue-50 transition-all duration-200 group disabled:opacity-50 disabled:cursor-not-allowed bg-white text-xs"
|
className="flex items-center gap-1.5 px-3 py-1.5 border border-blue-200 rounded-md hover:border-blue-300 hover:bg-blue-50 transition-all duration-200 group disabled:opacity-50 disabled:cursor-not-allowed bg-white text-xs"
|
||||||
>
|
>
|
||||||
<FaFileAlt className="w-3.5 h-3.5 text-blue-500 group-hover:scale-110 transition-transform" />
|
<FaFileAlt className="w-3.5 h-3.5 text-blue-500 group-hover:scale-110 transition-transform" />
|
||||||
<span className="font-medium text-slate-700">
|
<span className="font-medium text-slate-700">
|
||||||
{translate('::App.Listforms.ImportManager.CsvTemplate')}
|
{translate('::App.Listforms.ImportManager.CsvTemplate')}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="max-h-96 overflow-y-auto">
|
<div className="max-h-96 overflow-y-auto">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-slate-100 sticky top-0">
|
<thead className="bg-slate-100 sticky top-0">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
||||||
{translate('::App.Listform.ListformField.Column')}
|
{translate('::App.Listform.ListformField.Column')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
||||||
{translate('::ListForms.ListFormEdit.Type')}
|
{translate('::ListForms.ListFormEdit.Type')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
||||||
{translate('::App.Required')}
|
{translate('::App.Required')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
||||||
{translate('::Abp.Mailing.Default')}
|
{translate('::Abp.Mailing.Default')}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-slate-100">
|
|
||||||
{editableColumns.map((column: any) => (
|
|
||||||
<tr key={column.fieldName} className="hover:bg-slate-50">
|
|
||||||
<td className="px-2 py-2 font-medium text-slate-800">
|
|
||||||
{column.captionName || column.fieldName}
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-2 text-slate-600">
|
|
||||||
<span
|
|
||||||
className={`px-2 py-1 rounded text-xs font-medium ${
|
|
||||||
column.dataType === 'string'
|
|
||||||
? 'bg-blue-100 text-blue-800'
|
|
||||||
: column.dataType === 'number'
|
|
||||||
? 'bg-green-100 text-green-800'
|
|
||||||
: column.dataType === 'boolean'
|
|
||||||
? 'bg-purple-100 text-purple-800'
|
|
||||||
: 'bg-orange-100 text-orange-800'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{column.dataType}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-2">
|
|
||||||
{column.validationRuleDto.some(
|
|
||||||
(rule: any) => rule.type === 'required',
|
|
||||||
) ? (
|
|
||||||
<span className="text-red-500 font-medium">
|
|
||||||
{translate('::App.Listforms.ImportManager.Yes')}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span className="text-slate-400">
|
|
||||||
{translate('::App.Listforms.ImportManager.No')}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-2 text-slate-600 text-sm">
|
|
||||||
{typeof column.defaultValue === 'object'
|
|
||||||
? JSON.stringify(column.defaultValue)
|
|
||||||
: column.defaultValue}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
<tbody className="divide-y divide-slate-100">
|
||||||
</table>
|
{editableColumns.map((column: any) => (
|
||||||
</div>
|
<tr key={column.fieldName} className="hover:bg-slate-50">
|
||||||
|
<td className="px-2 py-2 font-medium text-slate-800">
|
||||||
{generating && (
|
{translate('::' + column.captionName)}
|
||||||
<div className="flex items-center justify-center py-4">
|
</td>
|
||||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
|
<td className="px-4 py-2 text-slate-600">
|
||||||
<span className="ml-2 text-slate-600">
|
<span
|
||||||
{translate('::App.Listforms.ImportManager.GeneratingTemplate')}
|
className={`px-2 py-1 rounded text-xs font-medium ${
|
||||||
</span>
|
column.dataType === 'string'
|
||||||
|
? 'bg-blue-100 text-blue-800'
|
||||||
|
: column.dataType === 'number'
|
||||||
|
? 'bg-green-100 text-green-800'
|
||||||
|
: column.dataType === 'boolean'
|
||||||
|
? 'bg-purple-100 text-purple-800'
|
||||||
|
: 'bg-orange-100 text-orange-800'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{column.dataType}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2">
|
||||||
|
{column.validationRuleDto.some(
|
||||||
|
(rule: any) => rule.type === 'required',
|
||||||
|
) ? (
|
||||||
|
<span className="text-red-500 font-medium">
|
||||||
|
{translate('::App.Listforms.ImportManager.Yes')}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-slate-400">
|
||||||
|
{translate('::App.Listforms.ImportManager.No')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 text-slate-600 text-sm">
|
||||||
|
{typeof column.defaultValue === 'object'
|
||||||
|
? JSON.stringify(column.defaultValue)
|
||||||
|
: column.defaultValue}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* File Upload - 1/3 width on large screens, full width on mobile */}
|
{generating && (
|
||||||
<div className="lg:col-span-1">
|
<div className="flex items-center justify-center py-4">
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-4 h-full">
|
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
|
||||||
<h2 className="text-xl font-semibold text-slate-800 mb-4 flex items-center">
|
<span className="ml-2 text-slate-600">
|
||||||
<FaUpload className="w-5 h-5 mr-2 text-green-500" />
|
{translate('::App.Listforms.ImportManager.GeneratingTemplate')}
|
||||||
{translate('::App.Listforms.ImportManager.UploadData')}
|
</span>
|
||||||
</h2>
|
</div>
|
||||||
<FileUploadArea
|
)}
|
||||||
onFileUpload={handleFileUpload}
|
</div>
|
||||||
loading={loading}
|
|
||||||
acceptedFormats={['.xlsx', '.xls', '.csv']}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === 'preview' && (
|
{/* File Upload - 1/3 width on large screens, full width on mobile */}
|
||||||
<div className="w-full">
|
<div className="lg:col-span-1">
|
||||||
{currentSession ? (
|
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-4 h-full">
|
||||||
<div className="w-full">
|
<h2 className="text-xl font-semibold text-slate-800 mb-4 flex items-center">
|
||||||
{currentSession.status === 'validating' ||
|
<FaUpload className="w-5 h-5 mr-2 text-green-500" />
|
||||||
currentSession.status === 'uploading' ||
|
{translate('::App.Listforms.ImportManager.UploadData')}
|
||||||
currentSession.status === 'processing' ? (
|
</h2>
|
||||||
<ImportProgress session={currentSession} />
|
<FileUploadArea
|
||||||
) : (
|
onFileUpload={handleFileUpload}
|
||||||
<div className="w-full">
|
|
||||||
<ImportPreview
|
|
||||||
session={currentSession}
|
|
||||||
gridDto={gridDto}
|
|
||||||
onExecute={handleImportExecute}
|
|
||||||
loading={loading}
|
loading={loading}
|
||||||
importService={importService}
|
acceptedFormats={['.xlsx', '.xls', '.csv']}
|
||||||
onPreviewLoaded={loadImportHistory}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-12">
|
|
||||||
<div className="text-center text-slate-500">
|
|
||||||
<FaEye className="w-16 h-16 mx-auto mb-4 text-slate-300" />
|
|
||||||
<div className="text-xl font-medium mb-2">
|
|
||||||
{translate('::App.Listforms.ImportManager.NoDataToPreview')}
|
|
||||||
</div>
|
|
||||||
<div>{translate('::App.Listforms.ImportManager.UploadFileToPreview')}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === 'history' && (
|
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200">
|
|
||||||
<div className="p-3 border-b border-slate-200">
|
|
||||||
<h2 className="text-xl font-semibold text-slate-800 flex items-center">
|
|
||||||
<FaClock className="w-5 h-5 mr-2 text-indigo-500" />
|
|
||||||
{translate('::App.Listforms.ImportManager.ImportHistory')}
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="divide-y divide-slate-100">
|
{activeTab === 'preview' && (
|
||||||
{importHistory.map((session) => (
|
<div className="w-full">
|
||||||
<div
|
{currentSession ? (
|
||||||
key={session.id}
|
<div className="w-full">
|
||||||
className={`p-2 transition-colors border-l-4 ${
|
{currentSession.status === 'validating' ||
|
||||||
currentSession?.id === session.id
|
currentSession.status === 'uploading' ||
|
||||||
? 'bg-blue-50 border-l-blue-500 hover:bg-blue-100'
|
currentSession.status === 'processing' ? (
|
||||||
: 'border-l-transparent hover:bg-slate-50'
|
<ImportProgress session={currentSession} />
|
||||||
}`}
|
) : (
|
||||||
>
|
<div className="w-full">
|
||||||
<div className="flex items-center justify-between">
|
<ImportPreview
|
||||||
<div className="flex items-center space-x-4">
|
session={currentSession}
|
||||||
{getStatusIcon(session.status)}
|
gridDto={gridDto}
|
||||||
<div className="flex items-center space-x-2">
|
onExecute={handleImportExecute}
|
||||||
<div>
|
loading={loading}
|
||||||
<div className="font-medium text-slate-800">{session.blobName}</div>
|
importService={importService}
|
||||||
<div className="text-sm text-slate-500">
|
onPreviewLoaded={loadImportHistory}
|
||||||
{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">
|
|
||||||
{translate('::App.Status.Active')}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<div className="text-right">
|
|
||||||
<div className="text-sm font-medium text-slate-800">
|
|
||||||
{session.totalRows} {translate('::App.Listforms.ImportManager.TotalRows')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span
|
|
||||||
className={`px-3 py-1 rounded-full text-xs font-medium border ${getStatusColor(
|
|
||||||
session.status,
|
|
||||||
)}`}
|
|
||||||
>
|
|
||||||
{session.status.charAt(0).toUpperCase() + session.status.slice(1)}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
<button
|
|
||||||
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'
|
|
||||||
: 'text-slate-400 hover:text-slate-600 hover:bg-slate-100'
|
|
||||||
}`}
|
|
||||||
title={translate('::App.Listforms.ImportManager.ViewExecutionDetails')}
|
|
||||||
>
|
|
||||||
<FaEye className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={async () => {
|
|
||||||
// Execute bilgilerini manuel olarak yenile
|
|
||||||
if (sessionExecutes[session.id]) {
|
|
||||||
setLoadingExecutes((prev) => new Set([...prev, session.id]))
|
|
||||||
try {
|
|
||||||
const executes = await importService.getListFormImportExecutes(
|
|
||||||
session.id,
|
|
||||||
)
|
|
||||||
setSessionExecutes((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[session.id]: executes,
|
|
||||||
}))
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to refresh import executes:', error)
|
|
||||||
} finally {
|
|
||||||
setLoadingExecutes((prev) => {
|
|
||||||
const newSet = new Set(prev)
|
|
||||||
newSet.delete(session.id)
|
|
||||||
return newSet
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="p-2 rounded-lg transition-colors text-slate-400 hover:text-blue-500 hover:bg-blue-50"
|
|
||||||
title={translate('::App.Listforms.ImportManager.RefreshExecutionDetails')}
|
|
||||||
>
|
|
||||||
<FaSync className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={async () => {
|
|
||||||
if (currentSession?.id === session.id) {
|
|
||||||
return // Don't delete if it's the current session
|
|
||||||
}
|
|
||||||
await importService.deleteHistory(session.id)
|
|
||||||
await loadImportHistory()
|
|
||||||
}}
|
|
||||||
disabled={currentSession?.id === session.id}
|
|
||||||
className={`p-2 rounded-lg transition-colors ${
|
|
||||||
currentSession?.id === session.id
|
|
||||||
? 'text-slate-300 cursor-not-allowed'
|
|
||||||
: 'text-slate-400 hover:text-red-500 hover:bg-red-50'
|
|
||||||
}`}
|
|
||||||
title={
|
|
||||||
currentSession?.id === session.id
|
|
||||||
? translate('::App.Listforms.ImportManager.CannotDeleteActiveSession')
|
|
||||||
: translate('::App.Listforms.ImportManager.DeleteImportSession')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FaTrashAlt className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Execute Details Section */}
|
|
||||||
{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="p-3">
|
|
||||||
{loadingExecutes.has(session.id) ? (
|
|
||||||
<div className="flex items-center space-x-2 text-slate-500 py-2">
|
|
||||||
<FaSync className="w-4 h-4 animate-spin" />
|
|
||||||
<span className="text-sm">
|
|
||||||
{translate('::App.Listforms.ImportManager.LoadingExecutionDetails')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : sessionExecutes[session.id] && sessionExecutes[session.id].length > 0 ? (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{sessionExecutes[session.id].map((execute) => (
|
|
||||||
<div key={execute.id} className="p-3 rounded-lg">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
{/* Sol: Tarih */}
|
|
||||||
<div className="flex-shrink-0">
|
|
||||||
<div className="text-lg text-slate-500">
|
|
||||||
{new Date(execute.creationTime).toLocaleString()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Orta: Executed, Valid, Errors */}
|
|
||||||
<div className="flex items-center space-x-4 text-xs text-slate-600">
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="font-medium text-slate-800">
|
|
||||||
{execute.execRows}
|
|
||||||
</div>
|
|
||||||
<div className="text-slate-500">
|
|
||||||
{translate('::App.Listforms.ImportManager.Executed')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="font-medium text-green-600">
|
|
||||||
{execute.validRows}
|
|
||||||
</div>
|
|
||||||
<div className="text-slate-500">
|
|
||||||
{translate('::App.Listforms.ImportManager.Valid')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="font-medium text-red-600">
|
|
||||||
{execute.errorRows}
|
|
||||||
</div>
|
|
||||||
<div className="text-slate-500">
|
|
||||||
{translate('::App.Listforms.ImportManager.Errors')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sağ: Status */}
|
|
||||||
<div className="flex items-center space-x-2 flex-shrink-0">
|
|
||||||
{execute.status === 'completed' && (
|
|
||||||
<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
|
|
||||||
className={`text-xs font-medium ${
|
|
||||||
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() +
|
|
||||||
execute.status.slice(1)}
|
|
||||||
{execute.status === 'processing' && ` (${execute.progress}%)`}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="text-sm text-slate-500 py-2">
|
|
||||||
{translate('::App.Listforms.ImportManager.NoExecutionRecords')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
) : (
|
||||||
|
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-12">
|
||||||
{importHistory.length === 0 && (
|
<div className="text-center text-slate-500">
|
||||||
<div className="p-12 text-center text-slate-500">
|
<FaEye className="w-16 h-16 mx-auto mb-4 text-slate-300" />
|
||||||
<FaClock className="w-12 h-12 mx-auto mb-4 text-slate-300" />
|
<div className="text-xl font-medium mb-2">
|
||||||
<div className="text-lg font-medium mb-2">
|
{translate('::App.Listforms.ImportManager.NoDataToPreview')}
|
||||||
{translate('::App.Listforms.ImportManager.NoImportHistory')}
|
</div>
|
||||||
|
<div>{translate('::App.Listforms.ImportManager.UploadFileToPreview')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>{translate('::App.Listforms.ImportManager.ImportHistoryHint')}</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
|
{activeTab === 'history' && (
|
||||||
|
<div className="bg-white rounded-xl shadow-sm border border-slate-200">
|
||||||
|
<div className="p-3 border-b border-slate-200">
|
||||||
|
<h2 className="text-xl font-semibold text-slate-800 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">
|
||||||
|
{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'
|
||||||
|
: 'border-l-transparent hover:bg-slate-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
{getStatusIcon(session.status)}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div>
|
||||||
|
<div className="font-medium text-slate-800">{session.blobName}</div>
|
||||||
|
<div className="text-sm text-slate-500">
|
||||||
|
{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">
|
||||||
|
{translate('::App.Status.Active')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="text-right">
|
||||||
|
<div className="text-sm font-medium text-slate-800">
|
||||||
|
{session.totalRows} {translate('::App.Listforms.ImportManager.TotalRows')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={`px-3 py-1 rounded-full text-xs font-medium border ${getStatusColor(
|
||||||
|
session.status,
|
||||||
|
)}`}
|
||||||
|
>
|
||||||
|
{session.status.charAt(0).toUpperCase() + session.status.slice(1)}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<button
|
||||||
|
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'
|
||||||
|
: 'text-slate-400 hover:text-slate-600 hover:bg-slate-100'
|
||||||
|
}`}
|
||||||
|
title={translate('::App.Listforms.ImportManager.ViewExecutionDetails')}
|
||||||
|
>
|
||||||
|
<FaEye className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
// Execute bilgilerini manuel olarak yenile
|
||||||
|
if (sessionExecutes[session.id]) {
|
||||||
|
setLoadingExecutes((prev) => new Set([...prev, session.id]))
|
||||||
|
try {
|
||||||
|
const executes = await importService.getListFormImportExecutes(
|
||||||
|
session.id,
|
||||||
|
)
|
||||||
|
setSessionExecutes((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[session.id]: executes,
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to refresh import executes:', error)
|
||||||
|
} finally {
|
||||||
|
setLoadingExecutes((prev) => {
|
||||||
|
const newSet = new Set(prev)
|
||||||
|
newSet.delete(session.id)
|
||||||
|
return newSet
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="p-2 rounded-lg transition-colors text-slate-400 hover:text-blue-500 hover:bg-blue-50"
|
||||||
|
title={translate('::App.Listforms.ImportManager.RefreshExecutionDetails')}
|
||||||
|
>
|
||||||
|
<FaSync className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
if (currentSession?.id === session.id) {
|
||||||
|
return // Don't delete if it's the current session
|
||||||
|
}
|
||||||
|
await importService.deleteHistory(session.id)
|
||||||
|
await loadImportHistory()
|
||||||
|
}}
|
||||||
|
disabled={currentSession?.id === session.id}
|
||||||
|
className={`p-2 rounded-lg transition-colors ${
|
||||||
|
currentSession?.id === session.id
|
||||||
|
? 'text-slate-300 cursor-not-allowed'
|
||||||
|
: 'text-slate-400 hover:text-red-500 hover:bg-red-50'
|
||||||
|
}`}
|
||||||
|
title={
|
||||||
|
currentSession?.id === session.id
|
||||||
|
? translate('::App.Listforms.ImportManager.CannotDeleteActiveSession')
|
||||||
|
: translate('::App.Listforms.ImportManager.DeleteImportSession')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaTrashAlt className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Execute Details Section */}
|
||||||
|
{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="p-3">
|
||||||
|
{loadingExecutes.has(session.id) ? (
|
||||||
|
<div className="flex items-center space-x-2 text-slate-500 py-2">
|
||||||
|
<FaSync className="w-4 h-4 animate-spin" />
|
||||||
|
<span className="text-sm">
|
||||||
|
{translate('::App.Listforms.ImportManager.LoadingExecutionDetails')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : sessionExecutes[session.id] &&
|
||||||
|
sessionExecutes[session.id].length > 0 ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{sessionExecutes[session.id].map((execute) => (
|
||||||
|
<div key={execute.id} className="p-3 rounded-lg">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
{/* Sol: Tarih */}
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<div className="text-lg text-slate-500">
|
||||||
|
{new Date(execute.creationTime).toLocaleString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Orta: Executed, Valid, Errors */}
|
||||||
|
<div className="flex items-center space-x-4 text-xs text-slate-600">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="font-medium text-slate-800">
|
||||||
|
{execute.execRows}
|
||||||
|
</div>
|
||||||
|
<div className="text-slate-500">
|
||||||
|
{translate('::App.Listforms.ImportManager.Executed')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="font-medium text-green-600">
|
||||||
|
{execute.validRows}
|
||||||
|
</div>
|
||||||
|
<div className="text-slate-500">
|
||||||
|
{translate('::App.Listforms.ImportManager.Valid')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="font-medium text-red-600">
|
||||||
|
{execute.errorRows}
|
||||||
|
</div>
|
||||||
|
<div className="text-slate-500">
|
||||||
|
{translate('::App.Listforms.ImportManager.Errors')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sağ: Status */}
|
||||||
|
<div className="flex items-center space-x-2 flex-shrink-0">
|
||||||
|
{execute.status === 'completed' && (
|
||||||
|
<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
|
||||||
|
className={`text-xs font-medium ${
|
||||||
|
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() +
|
||||||
|
execute.status.slice(1)}
|
||||||
|
{execute.status === 'processing' && ` (${execute.progress}%)`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-sm text-slate-500 py-2">
|
||||||
|
{translate('::App.Listforms.ImportManager.NoExecutionRecords')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{importHistory.length === 0 && (
|
||||||
|
<div className="p-12 text-center text-slate-500">
|
||||||
|
<FaClock className="w-12 h-12 mx-auto mb-4 text-slate-300" />
|
||||||
|
<div className="text-lg font-medium mb-2">
|
||||||
|
{translate('::App.Listforms.ImportManager.NoImportHistory')}
|
||||||
|
</div>
|
||||||
|
<div>{translate('::App.Listforms.ImportManager.ImportHistoryHint')}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,54 @@ import WindowControls from '../WindowControls'
|
||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import { SCREENS } from '@/utils/tailwind'
|
import { SCREENS } from '@/utils/tailwind'
|
||||||
import useWindowSize from '../hooks/useWindowSize'
|
import useWindowSize from '../hooks/useWindowSize'
|
||||||
import { useState, useCallback } from 'react'
|
import { useState, useCallback, createContext, useContext } from 'react'
|
||||||
import type ReactModal from 'react-modal'
|
import type ReactModal from 'react-modal'
|
||||||
import type { MouseEvent } from 'react'
|
import type { MouseEvent, ReactNode } from 'react'
|
||||||
import { Container } from '@/components/shared'
|
|
||||||
|
interface DialogContextValue {
|
||||||
|
isMaximized: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogContext = createContext<DialogContextValue>({ isMaximized: false })
|
||||||
|
|
||||||
|
export const useDialogContext = () => useContext(DialogContext)
|
||||||
|
|
||||||
|
const DialogBody = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
children?: ReactNode
|
||||||
|
className?: string
|
||||||
|
}) => {
|
||||||
|
const { isMaximized } = useContext(DialogContext)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
isMaximized && 'flex-1 min-h-0 flex flex-col overflow-hidden',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DialogBody.displayName = 'Dialog.Body'
|
||||||
|
|
||||||
|
const DialogFooter = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
children?: ReactNode
|
||||||
|
className?: string
|
||||||
|
}) => {
|
||||||
|
const { isMaximized } = useContext(DialogContext)
|
||||||
|
return (
|
||||||
|
<div className={classNames(isMaximized && 'flex-shrink-0', className)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DialogFooter.displayName = 'Dialog.Footer'
|
||||||
|
|
||||||
export interface DialogProps extends ReactModal.Props {
|
export interface DialogProps extends ReactModal.Props {
|
||||||
closable?: boolean
|
closable?: boolean
|
||||||
|
|
@ -186,7 +230,15 @@ const Dialog = (props: DialogProps) => {
|
||||||
>
|
>
|
||||||
{closable && !showWindowControls && renderCloseButton}
|
{closable && !showWindowControls && renderCloseButton}
|
||||||
{closable && showWindowControls && renderWindowControls}
|
{closable && showWindowControls && renderWindowControls}
|
||||||
{children}
|
<DialogContext.Provider value={{ isMaximized }}>
|
||||||
|
{isMaximized ? (
|
||||||
|
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</DialogContext.Provider>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
|
|
@ -194,4 +246,13 @@ const Dialog = (props: DialogProps) => {
|
||||||
|
|
||||||
Dialog.displayName = 'Dialog'
|
Dialog.displayName = 'Dialog'
|
||||||
|
|
||||||
export default Dialog
|
type DialogType = typeof Dialog & {
|
||||||
|
Body: typeof DialogBody
|
||||||
|
Footer: typeof DialogFooter
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogWithSubComponents = Dialog as DialogType
|
||||||
|
DialogWithSubComponents.Body = DialogBody
|
||||||
|
DialogWithSubComponents.Footer = DialogFooter
|
||||||
|
|
||||||
|
export default DialogWithSubComponents
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
import classNames from 'classnames'
|
||||||
import { FaChevronRight, FaChevronDown } from 'react-icons/fa'
|
import { FaChevronRight, FaChevronDown } from 'react-icons/fa'
|
||||||
import Container from '@/components/shared/Container'
|
import Container from '@/components/shared/Container'
|
||||||
import { Button, Checkbox, Dialog, Input, Menu, toast } from '@/components/ui'
|
import { Button, Checkbox, Dialog, Input, Menu, toast } from '@/components/ui'
|
||||||
import { useConfig } from '@/components/ui/ConfigProvider'
|
import { useConfig } from '@/components/ui/ConfigProvider'
|
||||||
|
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
|
||||||
import Notification from '@/components/ui/Notification'
|
import Notification from '@/components/ui/Notification'
|
||||||
import {
|
import {
|
||||||
GetPermissionListResultDto,
|
GetPermissionListResultDto,
|
||||||
|
|
@ -17,6 +19,166 @@ import { ChangeEvent, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
type OpenState = Record<string, boolean>
|
type OpenState = Record<string, boolean>
|
||||||
|
|
||||||
|
function PermissionDialogContent({
|
||||||
|
name,
|
||||||
|
translate,
|
||||||
|
permissionList,
|
||||||
|
selectedGroup,
|
||||||
|
selectedGroupPermissions,
|
||||||
|
filteredPermissions,
|
||||||
|
isAllSelected,
|
||||||
|
isAllSelectedForGroup,
|
||||||
|
isLoading,
|
||||||
|
searchTerm,
|
||||||
|
openPermissions,
|
||||||
|
mode,
|
||||||
|
onSelectAll,
|
||||||
|
onDialogClose,
|
||||||
|
onDialogOk,
|
||||||
|
handleExpandAll,
|
||||||
|
handleCollapseAll,
|
||||||
|
changeGroup,
|
||||||
|
togglePermission,
|
||||||
|
isParent,
|
||||||
|
getChildren,
|
||||||
|
onClickCheckbox,
|
||||||
|
setSearchTerm,
|
||||||
|
setCopyDialogOpen,
|
||||||
|
}: any) {
|
||||||
|
const { isMaximized } = useDialogContext()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h5 className="mb-1 flex-shrink-0">
|
||||||
|
{translate('::Permission')} - {name}
|
||||||
|
</h5>
|
||||||
|
<hr className="mt-1 mb-2 flex-shrink-0"></hr>
|
||||||
|
|
||||||
|
<div className="flex flex-col md:flex-row gap-4 mb-1 flex-shrink-0">
|
||||||
|
<div className="w-full md:w-1/3">
|
||||||
|
<Checkbox name="all" checked={isAllSelected} onChange={onSelectAll}>
|
||||||
|
{translate('AbpPermissionManagement::SelectAllInAllTabs')}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
<div className="w-full md:w-2/3">
|
||||||
|
<Checkbox name="group" checked={isAllSelectedForGroup} onChange={onSelectAll}>
|
||||||
|
{translate('AbpPermissionManagement::SelectAllInThisTab')}
|
||||||
|
</Checkbox>
|
||||||
|
<Button size="sm" variant="plain" onClick={handleExpandAll} icon={<FaChevronRight />}>
|
||||||
|
{translate('::ListForms.ListFormEdit.ExpandAll')}
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" variant="plain" onClick={handleCollapseAll} icon={<FaChevronDown />}>
|
||||||
|
{translate('::ListForms.ListFormEdit.CollapseAll')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'flex flex-col md:flex-row gap-4',
|
||||||
|
isMaximized ? 'flex-1 min-h-0' : '',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'w-full md:w-1/3 overflow-y-auto',
|
||||||
|
isMaximized ? 'min-h-0' : 'max-h-[450px]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<hr className="mb-2"></hr>
|
||||||
|
<Menu
|
||||||
|
className="w-full"
|
||||||
|
variant={mode}
|
||||||
|
defaultActiveKeys={[selectedGroup?.displayName ?? '']}
|
||||||
|
>
|
||||||
|
{permissionList?.groups.map((group: any) => (
|
||||||
|
<Menu.MenuItem
|
||||||
|
key={group.name}
|
||||||
|
className="break-all whitespace-normal"
|
||||||
|
eventKey={group.name}
|
||||||
|
onSelect={changeGroup}
|
||||||
|
>
|
||||||
|
{translate('::' + group.displayName)} (
|
||||||
|
{group.permissions.filter((a: any) => a.isGranted).length})
|
||||||
|
</Menu.MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'w-full md:w-2/3 overflow-y-auto flex flex-col',
|
||||||
|
isMaximized ? 'min-h-0' : 'max-h-[450px]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<hr className="mb-2 flex-shrink-0"></hr>
|
||||||
|
<div className="flex items-center gap-2 mb-2 flex-shrink-0">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
placeholder={translate('::Search')}
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e: any) => setSearchTerm(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="my-1">
|
||||||
|
{filteredPermissions.map((permission: any) => {
|
||||||
|
const isParentPerm = isParent(permission)
|
||||||
|
const permKey = permission.name || ''
|
||||||
|
return (
|
||||||
|
<div key={permission.name} className={`ml-${permission.level * 4} group`}>
|
||||||
|
<div className="flex items-center gap-2 px-2 py-0.5 rounded-md hover:bg-gray-50 transition-all">
|
||||||
|
{isParentPerm ? (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
togglePermission(permKey)
|
||||||
|
}}
|
||||||
|
className="w-5 h-5 flex items-center justify-center rounded hover:bg-gray-200 transition"
|
||||||
|
>
|
||||||
|
{openPermissions[permKey] || searchTerm ? (
|
||||||
|
<FaChevronDown className="text-gray-500 text-xs" />
|
||||||
|
) : (
|
||||||
|
<FaChevronRight className="text-gray-500 text-xs" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<div className="w-5" />
|
||||||
|
)}
|
||||||
|
<Checkbox
|
||||||
|
name={permission.name}
|
||||||
|
checked={permission.isGranted}
|
||||||
|
onChange={() => onClickCheckbox(permission)}
|
||||||
|
>
|
||||||
|
<span className="text-sm text-gray-700 group-hover:text-gray-900 transition">
|
||||||
|
{translate('::' + permission.displayName)}
|
||||||
|
</span>
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog.Footer className="flex justify-between items-center mt-4 pt-3 border-t">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Button variant="solid" color="sky-500" onClick={() => setCopyDialogOpen(true)}>
|
||||||
|
{translate('::AbpIdentity.Roles.CopyPermissions')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<Button className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={onDialogClose}>
|
||||||
|
{translate('::Cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button variant="solid" loading={isLoading} onClick={onDialogOk}>
|
||||||
|
{isLoading ? translate('::SavingWithThreeDot') : translate('::Save')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function RolesPermission({
|
function RolesPermission({
|
||||||
open,
|
open,
|
||||||
onDialogClose,
|
onDialogClose,
|
||||||
|
|
@ -368,122 +530,34 @@ function RolesPermission({
|
||||||
onClose={onDialogClose}
|
onClose={onDialogClose}
|
||||||
onRequestClose={onDialogClose}
|
onRequestClose={onDialogClose}
|
||||||
>
|
>
|
||||||
<h5 className="mb-1">
|
<Dialog.Body className="flex flex-col">
|
||||||
{translate('::Permission')} - {name}
|
<PermissionDialogContent
|
||||||
</h5>
|
name={name}
|
||||||
<hr className="mt-1 mb-2"></hr>
|
translate={translate}
|
||||||
|
permissionList={permissionList}
|
||||||
<div className="flex flex-col md:flex-row gap-4 mb-1">
|
selectedGroup={selectedGroup}
|
||||||
<div className="w-full md:w-1/3">
|
selectedGroupPermissions={selectedGroupPermissions}
|
||||||
<Checkbox name="all" checked={isAllSelected} onChange={onSelectAll}>
|
filteredPermissions={filteredPermissions}
|
||||||
{translate('AbpPermissionManagement::SelectAllInAllTabs')}
|
isAllSelected={isAllSelected}
|
||||||
</Checkbox>
|
isAllSelectedForGroup={isAllSelectedForGroup}
|
||||||
</div>
|
isLoading={isLoading}
|
||||||
<div className="w-full md:w-2/3">
|
searchTerm={searchTerm}
|
||||||
<Checkbox name="group" checked={isAllSelectedForGroup} onChange={onSelectAll}>
|
openPermissions={openPermissions}
|
||||||
{translate('AbpPermissionManagement::SelectAllInThisTab')}
|
mode={mode}
|
||||||
</Checkbox>
|
onSelectAll={onSelectAll}
|
||||||
|
onDialogClose={onDialogClose}
|
||||||
<Button size="sm" variant="plain" onClick={handleExpandAll} icon={<FaChevronRight />}>
|
onDialogOk={onDialogOk}
|
||||||
{translate('::ListForms.ListFormEdit.ExpandAll')}
|
handleExpandAll={handleExpandAll}
|
||||||
</Button>
|
handleCollapseAll={handleCollapseAll}
|
||||||
<Button size="sm" variant="plain" onClick={handleCollapseAll} icon={<FaChevronDown />}>
|
changeGroup={changeGroup}
|
||||||
{translate('::ListForms.ListFormEdit.CollapseAll')}
|
togglePermission={togglePermission}
|
||||||
</Button>
|
isParent={isParent}
|
||||||
</div>
|
getChildren={getChildren}
|
||||||
</div>
|
onClickCheckbox={onClickCheckbox}
|
||||||
|
setSearchTerm={setSearchTerm}
|
||||||
<div className="flex flex-col md:flex-row gap-4">
|
setCopyDialogOpen={setCopyDialogOpen}
|
||||||
<div className="w-full md:w-1/3 max-h-[450px] overflow-y-auto">
|
/>
|
||||||
<hr className="mb-2"></hr>
|
</Dialog.Body>
|
||||||
<Menu
|
|
||||||
className="w-full"
|
|
||||||
variant={mode}
|
|
||||||
defaultActiveKeys={[selectedGroup?.displayName ?? '']}
|
|
||||||
>
|
|
||||||
{permissionList?.groups.map((group: any) => (
|
|
||||||
<Menu.MenuItem
|
|
||||||
key={group.name}
|
|
||||||
className="break-all whitespace-normal"
|
|
||||||
eventKey={group.name}
|
|
||||||
onSelect={changeGroup}
|
|
||||||
>
|
|
||||||
{translate('::' + group.displayName)} (
|
|
||||||
{group.permissions.filter((a: any) => a.isGranted).length})
|
|
||||||
</Menu.MenuItem>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
<div className="w-full md:w-2/3 max-h-[450px] overflow-y-auto">
|
|
||||||
<hr className="mb-2"></hr>
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
<Input
|
|
||||||
size="sm"
|
|
||||||
className=""
|
|
||||||
placeholder={translate('::Search')}
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="my-1">
|
|
||||||
{filteredPermissions.map((permission) => {
|
|
||||||
const isParentPerm = isParent(permission)
|
|
||||||
const permKey = permission.name || ''
|
|
||||||
const children = getChildren(permKey)
|
|
||||||
return (
|
|
||||||
<div key={permission.name} className={`ml-${permission.level * 4} group`}>
|
|
||||||
<div className="flex items-center gap-2 px-2 py-0.5 rounded-md hover:bg-gray-50 transition-all">
|
|
||||||
{/* Expand Icon */}
|
|
||||||
{isParentPerm ? (
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
togglePermission(permKey)
|
|
||||||
}}
|
|
||||||
className="w-5 h-5 flex items-center justify-center rounded hover:bg-gray-200 transition"
|
|
||||||
>
|
|
||||||
{openPermissions[permKey] || searchTerm ? (
|
|
||||||
<FaChevronDown className="text-gray-500 text-xs" />
|
|
||||||
) : (
|
|
||||||
<FaChevronRight className="text-gray-500 text-xs" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<div className="w-5" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Checkbox */}
|
|
||||||
<Checkbox
|
|
||||||
name={permission.name}
|
|
||||||
checked={permission.isGranted}
|
|
||||||
onChange={() => onClickCheckbox(permission)}
|
|
||||||
>
|
|
||||||
<span className="text-sm text-gray-700 group-hover:text-gray-900 transition">
|
|
||||||
{translate('::' + permission.displayName)}
|
|
||||||
</span>
|
|
||||||
</Checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center mt-6">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Button variant="solid" color="sky-500" onClick={() => setCopyDialogOpen(true)}>
|
|
||||||
{translate('::AbpIdentity.Roles.CopyPermissions')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<Button className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={onDialogClose}>
|
|
||||||
{translate('::Cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button variant="solid" loading={isLoading} onClick={onDialogOk}>
|
|
||||||
{isLoading ? translate('::SavingWithThreeDot') : translate('::Save')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{/* Copy Permissions Dialog */}
|
{/* Copy Permissions Dialog */}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
import classNames from 'classnames'
|
||||||
import { FaChevronRight, FaChevronDown } from 'react-icons/fa'
|
import { FaChevronRight, FaChevronDown } from 'react-icons/fa'
|
||||||
import { Badge, Button, Checkbox, Dialog, Input, Menu, toast } from '@/components/ui'
|
import { Badge, Button, Checkbox, Dialog, Input, Menu, toast } from '@/components/ui'
|
||||||
type OpenState = Record<string, boolean>
|
type OpenState = Record<string, boolean>
|
||||||
import { useConfig } from '@/components/ui/ConfigProvider'
|
import { useConfig } from '@/components/ui/ConfigProvider'
|
||||||
|
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
|
||||||
import Notification from '@/components/ui/Notification'
|
import Notification from '@/components/ui/Notification'
|
||||||
import {
|
import {
|
||||||
GetPermissionListResultDto,
|
GetPermissionListResultDto,
|
||||||
|
|
@ -16,6 +18,166 @@ import { useStoreActions, useStoreState } from '@/store'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { ChangeEvent, useEffect, useMemo, useState } from 'react'
|
import { ChangeEvent, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
function UserPermissionDialogContent({
|
||||||
|
name,
|
||||||
|
translate,
|
||||||
|
permissionList,
|
||||||
|
selectedGroup,
|
||||||
|
selectedGroupPermissions,
|
||||||
|
filteredPermissions,
|
||||||
|
isAllSelected,
|
||||||
|
isAllSelectedForGroup,
|
||||||
|
isLoading,
|
||||||
|
searchTerm,
|
||||||
|
openPermissions,
|
||||||
|
mode,
|
||||||
|
providerName,
|
||||||
|
onSelectAll,
|
||||||
|
onDialogClose,
|
||||||
|
onDialogOk,
|
||||||
|
handleExpandAll,
|
||||||
|
handleCollapseAll,
|
||||||
|
changeGroup,
|
||||||
|
togglePermission,
|
||||||
|
isParent,
|
||||||
|
onClickCheckbox,
|
||||||
|
setSearchTerm,
|
||||||
|
}: any) {
|
||||||
|
const { isMaximized } = useDialogContext()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h5 className="mb-1 flex-shrink-0">
|
||||||
|
{translate('::Permission')} - {name}
|
||||||
|
</h5>
|
||||||
|
<hr className="mt-1 mb-2 flex-shrink-0"></hr>
|
||||||
|
|
||||||
|
<div className="flex flex-col md:flex-row gap-4 mb-1 flex-shrink-0">
|
||||||
|
<div className="w-full md:w-1/3">
|
||||||
|
<Checkbox name="all" checked={isAllSelected} onChange={onSelectAll}>
|
||||||
|
{translate('AbpPermissionManagement::SelectAllInAllTabs')}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
<div className="w-full md:w-2/3">
|
||||||
|
<Checkbox name="group" checked={isAllSelectedForGroup} onChange={onSelectAll}>
|
||||||
|
{translate('AbpPermissionManagement::SelectAllInThisTab')}
|
||||||
|
</Checkbox>
|
||||||
|
<Button size="sm" variant="plain" onClick={handleExpandAll} icon={<FaChevronRight />}>
|
||||||
|
{translate('::ListForms.ListFormEdit.ExpandAll')}
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" variant="plain" onClick={handleCollapseAll} icon={<FaChevronDown />}>
|
||||||
|
{translate('::ListForms.ListFormEdit.CollapseAll')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'flex flex-col md:flex-row gap-4',
|
||||||
|
isMaximized ? 'flex-1 min-h-0' : '',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'w-full md:w-1/3 overflow-y-auto',
|
||||||
|
isMaximized ? 'min-h-0' : 'max-h-[450px]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<hr className="mb-2"></hr>
|
||||||
|
<Menu variant={mode} defaultActiveKeys={[selectedGroup?.displayName ?? '']}>
|
||||||
|
{permissionList?.groups.map((group: any) => (
|
||||||
|
<Menu.MenuItem
|
||||||
|
key={group.name}
|
||||||
|
className="break-all whitespace-normal"
|
||||||
|
eventKey={group.name}
|
||||||
|
onSelect={() => changeGroup(group.name)}
|
||||||
|
>
|
||||||
|
{translate('::' + group.displayName)} (
|
||||||
|
{group.permissions.filter((a: any) => a.isGranted).length})
|
||||||
|
</Menu.MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'w-full md:w-2/3 overflow-y-auto flex flex-col',
|
||||||
|
isMaximized ? 'min-h-0' : 'max-h-[450px]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<hr className="mb-2 flex-shrink-0"></hr>
|
||||||
|
<div className="flex items-center gap-2 mb-2 flex-shrink-0">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
placeholder={translate('::Search')}
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e: any) => setSearchTerm(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="my-1">
|
||||||
|
{filteredPermissions.map((permission: any) => {
|
||||||
|
const isParentPerm = isParent(permission)
|
||||||
|
const permKey = permission.name || ''
|
||||||
|
return (
|
||||||
|
<div key={permission.name} className={`ml-${permission.level * 4} group`}>
|
||||||
|
<div className="flex items-center gap-2 px-2 py-0.5 rounded-md hover:bg-gray-50 transition-all">
|
||||||
|
{isParentPerm ? (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
togglePermission(permKey)
|
||||||
|
}}
|
||||||
|
className="w-5 h-5 flex items-center justify-center rounded hover:bg-gray-200 transition"
|
||||||
|
>
|
||||||
|
{openPermissions[permKey] || searchTerm ? (
|
||||||
|
<FaChevronDown className="text-gray-500 text-xs" />
|
||||||
|
) : (
|
||||||
|
<FaChevronRight className="text-gray-500 text-xs" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<div className="w-5" />
|
||||||
|
)}
|
||||||
|
<Checkbox
|
||||||
|
name={permission.name}
|
||||||
|
checked={permission.isGranted}
|
||||||
|
onChange={() => onClickCheckbox(permission)}
|
||||||
|
disabled={permission.grantedProviders.some(
|
||||||
|
(provider: any) => provider.providerName === 'R',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="text-sm text-gray-700 group-hover:text-gray-900 transition">
|
||||||
|
{translate('::' + permission.displayName)}
|
||||||
|
{permission.grantedProviders.map((provider: any) => {
|
||||||
|
const badgeContent =
|
||||||
|
provider.providerName !== providerName
|
||||||
|
? `${provider.providerName}: ${provider.providerKey}`
|
||||||
|
: provider.providerName
|
||||||
|
return (
|
||||||
|
<Badge key={provider.providerKey} className="m-2" content={badgeContent} />
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog.Footer className="flex justify-end items-center mt-4 pt-3 border-t gap-2">
|
||||||
|
<Button variant="plain" onClick={onDialogClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="solid" loading={isLoading} onClick={onDialogOk}>
|
||||||
|
{isLoading ? translate('::SavingWithThreeDot') : translate('::Save')}
|
||||||
|
</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function UsersPermission({
|
function UsersPermission({
|
||||||
open,
|
open,
|
||||||
onDialogClose,
|
onDialogClose,
|
||||||
|
|
@ -297,125 +459,33 @@ function UsersPermission({
|
||||||
onClose={onDialogClose}
|
onClose={onDialogClose}
|
||||||
onRequestClose={onDialogClose}
|
onRequestClose={onDialogClose}
|
||||||
>
|
>
|
||||||
<h5 className="mb-1">
|
<Dialog.Body className="flex flex-col">
|
||||||
{translate('::Permission')} - {name}
|
<UserPermissionDialogContent
|
||||||
</h5>
|
name={name}
|
||||||
<hr className="mt-1 mb-2"></hr>
|
translate={translate}
|
||||||
|
permissionList={permissionList}
|
||||||
<div className="flex flex-col md:flex-row gap-4">
|
selectedGroup={selectedGroup}
|
||||||
<div className="w-full md:w-1/3">
|
selectedGroupPermissions={selectedGroupPermissions}
|
||||||
<Checkbox name="all" checked={isAllSelected} onChange={onSelectAll}>
|
filteredPermissions={filteredPermissions}
|
||||||
{translate('AbpPermissionManagement::SelectAllInAllTabs')}
|
isAllSelected={isAllSelected}
|
||||||
</Checkbox>
|
isAllSelectedForGroup={isAllSelectedForGroup}
|
||||||
</div>
|
isLoading={isLoading}
|
||||||
<div className="w-full md:w-2/3">
|
searchTerm={searchTerm}
|
||||||
<Checkbox name="group" checked={isAllSelectedForGroup} onChange={onSelectAll}>
|
openPermissions={openPermissions}
|
||||||
{translate('AbpPermissionManagement::SelectAllInThisTab')}
|
mode={mode}
|
||||||
</Checkbox>
|
providerName={providerName}
|
||||||
<Button size="sm" variant="plain" onClick={handleExpandAll} icon={<FaChevronRight />}>
|
onSelectAll={onSelectAll}
|
||||||
{translate('::ListForms.ListFormEdit.ExpandAll')}
|
onDialogClose={onDialogClose}
|
||||||
</Button>
|
onDialogOk={onDialogOk}
|
||||||
<Button size="sm" variant="plain" onClick={handleCollapseAll} icon={<FaChevronDown />}>
|
handleExpandAll={handleExpandAll}
|
||||||
{translate('::ListForms.ListFormEdit.CollapseAll')}
|
handleCollapseAll={handleCollapseAll}
|
||||||
</Button>
|
changeGroup={changeGroup}
|
||||||
</div>
|
togglePermission={togglePermission}
|
||||||
</div>
|
isParent={isParent}
|
||||||
|
onClickCheckbox={onClickCheckbox}
|
||||||
<div className="flex flex-col md:flex-row gap-4">
|
setSearchTerm={setSearchTerm}
|
||||||
<div className="w-full md:w-1/3 max-h-[450px] overflow-y-auto">
|
/>
|
||||||
<hr className="mb-2"></hr>
|
</Dialog.Body>
|
||||||
<Menu variant={mode} defaultActiveKeys={[selectedGroup?.displayName ?? '']}>
|
|
||||||
{permissionList?.groups.map((group) => (
|
|
||||||
<Menu.MenuItem
|
|
||||||
key={group.name}
|
|
||||||
className="break-all whitespace-normal"
|
|
||||||
eventKey={group.name}
|
|
||||||
onSelect={() => {
|
|
||||||
changeGroup(group.name)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{translate('::' + group.displayName)} (
|
|
||||||
{group.permissions.filter((a) => a.isGranted).length})
|
|
||||||
</Menu.MenuItem>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
<div className="w-full md:w-2/3 max-h-[450px] overflow-y-auto">
|
|
||||||
<hr className="mb-2"></hr>
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
<Input
|
|
||||||
size="sm"
|
|
||||||
className=""
|
|
||||||
placeholder={translate('::Search')}
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="my-1">
|
|
||||||
{filteredPermissions.map((permission) => {
|
|
||||||
const isParentPerm = isParent(permission)
|
|
||||||
const permKey = permission.name || ''
|
|
||||||
const children = getChildren(permKey)
|
|
||||||
return (
|
|
||||||
<div key={permission.name} className={`ml-${permission.level * 4} group`}>
|
|
||||||
<div className="flex items-center gap-2 px-2 py-0.5 rounded-md hover:bg-gray-50 transition-all">
|
|
||||||
{/* Expand Icon */}
|
|
||||||
{isParentPerm ? (
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
togglePermission(permKey)
|
|
||||||
}}
|
|
||||||
className="w-5 h-5 flex items-center justify-center rounded hover:bg-gray-200 transition"
|
|
||||||
>
|
|
||||||
{openPermissions[permKey] || searchTerm ? (
|
|
||||||
<FaChevronDown className="text-gray-500 text-xs" />
|
|
||||||
) : (
|
|
||||||
<FaChevronRight className="text-gray-500 text-xs" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<div className="w-5" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Checkbox */}
|
|
||||||
<Checkbox
|
|
||||||
name={permission.name}
|
|
||||||
checked={permission.isGranted}
|
|
||||||
onChange={() => onClickCheckbox(permission)}
|
|
||||||
disabled={permission.grantedProviders.some((provider) => provider.providerName === 'R')}
|
|
||||||
>
|
|
||||||
<span className="text-sm text-gray-700 group-hover:text-gray-900 transition">
|
|
||||||
{translate('::' + permission.displayName)}
|
|
||||||
{permission.grantedProviders.map((provider) => {
|
|
||||||
const badgeContent = provider.providerName !== providerName
|
|
||||||
? `${provider.providerName}: ${provider.providerKey}`
|
|
||||||
: provider.providerName;
|
|
||||||
return (
|
|
||||||
<Badge
|
|
||||||
key={provider.providerKey}
|
|
||||||
className="m-2"
|
|
||||||
content={badgeContent}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</Checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-right mt-6">
|
|
||||||
<Button className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={onDialogClose}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button variant="solid" loading={isLoading} onClick={onDialogOk}>
|
|
||||||
{isLoading ? translate('::SavingWithThreeDot') : translate('::Save')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,20 @@
|
||||||
|
import classNames from 'classnames'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Badge, Button } from '@/components/ui'
|
import { Badge, Button } from '@/components/ui'
|
||||||
import { Container } from '@/components/shared'
|
import { Container } from '@/components/shared'
|
||||||
import { Dialog, Notification, toast } from '@/components/ui'
|
import { Dialog, Notification, toast } from '@/components/ui'
|
||||||
|
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
|
||||||
import type { BranchSeedResultDto } from '@/proxy/branch/seed'
|
import type { BranchSeedResultDto } from '@/proxy/branch/seed'
|
||||||
import { runBranchSeed } from '@/services/branch.service'
|
import { runBranchSeed } from '@/services/branch.service'
|
||||||
|
|
||||||
function BranchSeed({
|
function BranchSeedContent({
|
||||||
open,
|
|
||||||
onDialogClose,
|
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
}: {
|
}: {
|
||||||
open: boolean
|
|
||||||
onDialogClose: () => void
|
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
}) {
|
}) {
|
||||||
|
const { isMaximized } = useDialogContext()
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [result, setResult] = useState<BranchSeedResultDto | null>(null)
|
const [result, setResult] = useState<BranchSeedResultDto | null>(null)
|
||||||
|
|
||||||
|
|
@ -51,6 +50,103 @@ function BranchSeed({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h5 className="mb-4 text-lg font-semibold flex-shrink-0">Branch Seed - {name}</h5>
|
||||||
|
<hr className="mb-3 flex-shrink-0" />
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'mt-3 space-y-4',
|
||||||
|
isMaximized ? 'flex-1 min-h-0 flex flex-col overflow-hidden' : '',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* Sonuç durumu */}
|
||||||
|
{result && (
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
{result.success ? (
|
||||||
|
<span className="text-green-600 font-semibold">✅ Seed İşlemi Başarılı</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-red-600 font-semibold">❌ Seed İşlemi Başarısız</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Sonuç Detayları */}
|
||||||
|
{result && (
|
||||||
|
<>
|
||||||
|
<p className="font-medium text-gray-700 flex-shrink-0">{result.message}</p>
|
||||||
|
|
||||||
|
<p className="text-sm text-gray-600 flex-shrink-0">
|
||||||
|
Toplam eklenen kayıt:{' '}
|
||||||
|
<span className="font-bold text-blue-600">{result.totalInsertedCount}</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Detay Tablosu */}
|
||||||
|
{result.details.length > 0 && (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'overflow-x-auto overflow-y-auto border border-gray-200 rounded-md',
|
||||||
|
isMaximized ? 'flex-1 min-h-0' : 'max-h-[50vh]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<table className="min-w-full text-sm">
|
||||||
|
<thead className="bg-gray-100 sticky top-0">
|
||||||
|
<tr>
|
||||||
|
<th className="border px-3 py-2 text-left">Entity</th>
|
||||||
|
<th className="border px-3 py-2 text-left">Eklenen</th>
|
||||||
|
<th className="border px-3 py-2 text-left">Uyarılar</th>
|
||||||
|
<th className="border px-3 py-2 text-left">Hatalar</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{result.details.map((d) => (
|
||||||
|
<tr key={d.entityName} className="hover:bg-gray-50">
|
||||||
|
<td className="border px-3 py-2 font-semibold text-gray-700">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>{d.entityName}</span>
|
||||||
|
<Badge content={d.insertedCount} />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="border px-3 py-2">{d.insertedItems.join(', ')}</td>
|
||||||
|
<td className="border px-3 py-2 text-yellow-700">
|
||||||
|
{d.warnings.join(', ')}
|
||||||
|
</td>
|
||||||
|
<td className="border px-3 py-2 text-red-700">{d.errors.join(', ')}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!result.details.length && (
|
||||||
|
<p className="text-gray-500 text-sm flex-shrink-0">Hiç detay bilgisi bulunamadı.</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog.Footer className="flex justify-end items-center pt-3 border-t mt-4">
|
||||||
|
<Button variant="solid" onClick={handleRunSeed} loading={isLoading}>
|
||||||
|
{isLoading ? 'Seed Çalıştırılıyor...' : 'Seed İşlemini Başlat'}
|
||||||
|
</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function BranchSeed({
|
||||||
|
open,
|
||||||
|
onDialogClose,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
}: {
|
||||||
|
open: boolean
|
||||||
|
onDialogClose: () => void
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Dialog
|
<Dialog
|
||||||
|
|
@ -60,79 +156,9 @@ function BranchSeed({
|
||||||
onClose={onDialogClose}
|
onClose={onDialogClose}
|
||||||
onRequestClose={onDialogClose}
|
onRequestClose={onDialogClose}
|
||||||
>
|
>
|
||||||
<h5 className="mb-4 text-lg font-semibold">Branch Seed - {name}</h5>
|
<Dialog.Body className="flex flex-col">
|
||||||
<hr className="mb-3" />
|
<BranchSeedContent id={id} name={name} />
|
||||||
|
</Dialog.Body>
|
||||||
<div className="mt-3 space-y-4">
|
|
||||||
{/* Başlık ve buton aynı satırda */}
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
{result &&
|
|
||||||
(result.success ? (
|
|
||||||
<span className="text-green-600 font-semibold">✅ Seed İşlemi Başarılı</span>
|
|
||||||
) : (
|
|
||||||
<span className="text-red-600 font-semibold">❌ Seed İşlemi Başarısız</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Button size="sm" variant="solid" onClick={handleRunSeed} loading={isLoading}>
|
|
||||||
{isLoading ? 'Seed Çalıştırılıyor...' : 'Seed İşlemini Başlat'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sonuç Detayları */}
|
|
||||||
{result && (
|
|
||||||
<>
|
|
||||||
<p className="font-medium text-gray-700">{result.message}</p>
|
|
||||||
|
|
||||||
<p className="text-sm text-gray-600">
|
|
||||||
Toplam eklenen kayıt:{' '}
|
|
||||||
<span className="font-bold text-blue-600">{result.totalInsertedCount}</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Detay Tablosu */}
|
|
||||||
{result.details.length > 0 && (
|
|
||||||
<div className="overflow-x-auto overflow-y-auto border border-gray-200 rounded-md max-h-[50vh]">
|
|
||||||
<table className="min-w-full text-sm">
|
|
||||||
<thead className="bg-gray-100 sticky top-0">
|
|
||||||
<tr>
|
|
||||||
<th className="border px-3 py-2 text-left">Entity</th>
|
|
||||||
<th className="border px-3 py-2 text-left">Eklenen</th>
|
|
||||||
<th className="border px-3 py-2 text-left">Uyarılar</th>
|
|
||||||
<th className="border px-3 py-2 text-left">Hatalar</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{result.details.map((d) => (
|
|
||||||
<tr key={d.entityName} className="hover:bg-gray-50">
|
|
||||||
<td className="border px-3 py-2 font-semibold text-gray-700">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span>{d.entityName}</span>
|
|
||||||
<Badge content={d.insertedCount} />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
{/* Eklenen kolonunu tek satır formatında göster */}
|
|
||||||
<td className="border px-3 py-2">{d.insertedItems.join(', ')}</td>
|
|
||||||
|
|
||||||
<td className="border px-3 py-2 text-yellow-700">
|
|
||||||
{d.warnings.join(', ')}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td className="border px-3 py-2 text-red-700">{d.errors.join(', ')}</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!result.details.length && (
|
|
||||||
<p className="text-gray-500 text-sm">Hiç detay bilgisi bulunamadı.</p>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { useState, useCallback, useMemo, useEffect } from 'react'
|
import { useState, useCallback, useMemo, useEffect } from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { Button, Dialog, Notification, toast, Checkbox } from '@/components/ui'
|
import { Button, Dialog, Notification, toast, Checkbox } from '@/components/ui'
|
||||||
|
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
|
||||||
import {
|
import {
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaTrash,
|
FaTrash,
|
||||||
|
|
@ -761,6 +763,15 @@ function generateAlterTableSql(
|
||||||
const STEPS = ['Sütun Tasarımı', 'Entity Ayarları', 'Index / Key', 'İlişkiler', 'T-SQL Önizleme'] as const
|
const STEPS = ['Sütun Tasarımı', 'Entity Ayarları', 'Index / Key', 'İlişkiler', 'T-SQL Önizleme'] as const
|
||||||
type Step = 0 | 1 | 2 | 3 | 4
|
type Step = 0 | 1 | 2 | 3 | 4
|
||||||
|
|
||||||
|
function StepContentWrapper({ children }: { children: React.ReactNode }) {
|
||||||
|
const { isMaximized } = useDialogContext()
|
||||||
|
return (
|
||||||
|
<div className={isMaximized ? 'flex-1 min-h-0 overflow-auto' : 'min-h-[420px]'}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Simple Menu Tree (read-only selection) ───────────────────────────────────
|
// ─── Simple Menu Tree (read-only selection) ───────────────────────────────────
|
||||||
|
|
||||||
interface SimpleMenuTreeNodeProps {
|
interface SimpleMenuTreeNodeProps {
|
||||||
|
|
@ -1646,14 +1657,14 @@ const SqlTableDesignerDialog = ({
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between py-2">
|
<div className="flex items-center justify-between py-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button size="sm" variant="solid" color="blue-600" onClick={addFullAuditedColumns}>
|
<Button size="xs" variant="solid" color="blue-600" onClick={addFullAuditedColumns}>
|
||||||
{translate('::App.SqlQueryManager.AddFullAuditedColumns')}
|
{translate('::App.SqlQueryManager.AddFullAuditedColumns')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="solid" color="green-600" onClick={addMultiTenantColumns}>
|
<Button size="xs" variant="solid" color="green-600" onClick={addMultiTenantColumns}>
|
||||||
{translate('::App.SqlQueryManager.AddMultiTenantColumns')}
|
{translate('::App.SqlQueryManager.AddMultiTenantColumns')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="xs"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="amber-600"
|
color="amber-600"
|
||||||
onClick={importColumnsFromRememberedCreateTable}
|
onClick={importColumnsFromRememberedCreateTable}
|
||||||
|
|
@ -1664,7 +1675,7 @@ const SqlTableDesignerDialog = ({
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="xs"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="red-600"
|
color="red-600"
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
|
|
@ -1673,7 +1684,7 @@ const SqlTableDesignerDialog = ({
|
||||||
{translate('::App.SqlQueryManager.ClearAllColumns')}
|
{translate('::App.SqlQueryManager.ClearAllColumns')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="xs"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="blue-600"
|
color="blue-600"
|
||||||
icon={<FaPlus />}
|
icon={<FaPlus />}
|
||||||
|
|
@ -2634,9 +2645,9 @@ const SqlTableDesignerDialog = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog isOpen={isOpen} onClose={handleClose} onRequestClose={handleClose} width={900}>
|
<Dialog isOpen={isOpen} onClose={handleClose} onRequestClose={handleClose} width={900}>
|
||||||
<div className="flex flex-col gap-2">
|
<Dialog.Body className="flex flex-col gap-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center gap-3 border-b pb-3">
|
<div className="flex items-center gap-3 border-b pb-3 flex-shrink-0">
|
||||||
<FaTable className="text-2xl text-blue-500" />
|
<FaTable className="text-2xl text-blue-500" />
|
||||||
<div>
|
<div>
|
||||||
<h5 className="font-bold">
|
<h5 className="font-bold">
|
||||||
|
|
@ -2653,51 +2664,50 @@ const SqlTableDesignerDialog = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Steps */}
|
{/* Steps */}
|
||||||
{renderStepIndicator()}
|
<div className="flex-shrink-0">{renderStepIndicator()}</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="min-h-[420px]">
|
<StepContentWrapper>
|
||||||
{step === 0 && renderColumnDesigner()}
|
{step === 0 && renderColumnDesigner()}
|
||||||
{step === 1 && renderEntitySettings()}
|
{step === 1 && renderEntitySettings()}
|
||||||
{step === 2 && renderIndexes()}
|
{step === 2 && renderIndexes()}
|
||||||
{step === 3 && renderRelationships()}
|
{step === 3 && renderRelationships()}
|
||||||
{step === 4 && renderSqlPreview()}
|
{step === 4 && renderSqlPreview()}
|
||||||
</div>
|
</StepContentWrapper>
|
||||||
|
</Dialog.Body>
|
||||||
|
|
||||||
{/* Footer */}
|
<Dialog.Footer className="flex justify-between items-center border-t pt-3 mt-1">
|
||||||
<div className="flex justify-between items-center border-t pt-3 mt-1">
|
<Button variant="plain" onClick={handleClose}>
|
||||||
<Button variant="plain" onClick={handleClose}>
|
{translate('::Cancel')}
|
||||||
{translate('::Cancel')}
|
</Button>
|
||||||
</Button>
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
{step > 0 && (
|
||||||
{step > 0 && (
|
<Button variant="default" onClick={handleBack}>
|
||||||
<Button variant="default" onClick={handleBack}>
|
{translate('::Back')}
|
||||||
{translate('::Back')}
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
)}
|
{step < 4 ? (
|
||||||
{step < 4 ? (
|
<Button variant="solid" color="blue-600" onClick={handleNext} disabled={!canGoNext()}>
|
||||||
<Button variant="solid" color="blue-600" onClick={handleNext} disabled={!canGoNext()}>
|
{translate('::App.SqlQueryManager.Next')}
|
||||||
{translate('::App.SqlQueryManager.Next')}
|
</Button>
|
||||||
</Button>
|
) : (
|
||||||
) : (
|
<Button
|
||||||
<Button
|
variant="solid"
|
||||||
variant="solid"
|
color="green-600"
|
||||||
color="green-600"
|
icon={<FaCloudUploadAlt />}
|
||||||
icon={<FaCloudUploadAlt />}
|
onClick={handleDeploy}
|
||||||
onClick={handleDeploy}
|
loading={isDeploying}
|
||||||
loading={isDeploying}
|
disabled={
|
||||||
disabled={
|
!dataSource ||
|
||||||
!dataSource ||
|
isDeploying ||
|
||||||
isDeploying ||
|
(isEditMode && generatedSql.includes('Henüz değişiklik yapılmadı'))
|
||||||
(isEditMode && generatedSql.includes('Henüz değişiklik yapılmadı'))
|
}
|
||||||
}
|
>
|
||||||
>
|
{translate('::App.Platform.Deploy')}
|
||||||
{translate('::App.Platform.Deploy')}
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Dialog.Footer>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
import { Button, Input, Dialog, FormContainer, FormItem, Upload, Radio } from '@/components/ui'
|
import { Button, Input, Dialog, FormContainer, FormItem, Upload, Radio } from '@/components/ui'
|
||||||
|
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
|
||||||
import { FaFileAlt, FaFileUpload, FaPlus, FaTrash } from 'react-icons/fa'
|
import { FaFileAlt, FaFileUpload, FaPlus, FaTrash } from 'react-icons/fa'
|
||||||
import { Field, FieldProps, Form, Formik } from 'formik'
|
import { Field, FieldProps, Form, Formik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
@ -29,13 +31,18 @@ interface NoteModalProps {
|
||||||
onNoteAdded?: (note: any) => void
|
onNoteAdded?: (note: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NoteModal: React.FC<NoteModalProps> = ({
|
function NoteModalContent({
|
||||||
entityName,
|
entityName,
|
||||||
entityId,
|
entityId,
|
||||||
isOpen,
|
|
||||||
onClose,
|
onClose,
|
||||||
onNoteAdded: onActivityAdded,
|
onNoteAdded,
|
||||||
}) => {
|
}: {
|
||||||
|
entityName: string
|
||||||
|
entityId: string
|
||||||
|
onClose: () => void
|
||||||
|
onNoteAdded?: (note: any) => void
|
||||||
|
}) {
|
||||||
|
const { isMaximized } = useDialogContext()
|
||||||
const [uploading, setUploading] = useState(false)
|
const [uploading, setUploading] = useState(false)
|
||||||
const [fileList, setFileList] = useState<File[]>([])
|
const [fileList, setFileList] = useState<File[]>([])
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
@ -57,7 +64,7 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
fileList.forEach((file) => formData.append('Files', file))
|
fileList.forEach((file) => formData.append('Files', file))
|
||||||
|
|
||||||
const createdActivity = await noteService.create(formData)
|
const createdActivity = await noteService.create(formData)
|
||||||
if (onActivityAdded) onActivityAdded(createdActivity)
|
if (onNoteAdded) onNoteAdded(createdActivity)
|
||||||
setFileList([])
|
setFileList([])
|
||||||
onClose()
|
onClose()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -81,201 +88,223 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog isOpen={isOpen} onClose={onClose} width={700}>
|
<>
|
||||||
<div className="p-2 w-full mx-auto">
|
{/* Başlık */}
|
||||||
{/* Başlık */}
|
<div className="flex items-center justify-between mb-5 flex-shrink-0">
|
||||||
<div className="flex items-center justify-between mb-5">
|
<h3 className="text-xl font-semibold flex items-center gap-3">
|
||||||
<h3 className="text-xl font-semibold flex items-center gap-3">
|
<div className="p-2 bg-purple-100 rounded-full">
|
||||||
<div className="p-2 bg-purple-100 rounded-full">
|
<FaPlus className="text-purple-600 text-lg" />
|
||||||
<FaPlus className="text-purple-600 text-lg" />
|
</div>
|
||||||
</div>
|
{translate('::ListForms.ListForm.AddNote')}
|
||||||
{translate('::ListForms.ListForm.AddNote')}
|
</h3>
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Formik
|
|
||||||
initialValues={{ type: 'note', subject: '', content: '' }}
|
|
||||||
validationSchema={validationSchema}
|
|
||||||
onSubmit={handleSave}
|
|
||||||
>
|
|
||||||
{({ values, touched, errors, setFieldValue, isSubmitting }) => (
|
|
||||||
<Form>
|
|
||||||
<FormContainer size="sm">
|
|
||||||
{/* NOT TİPİ */}
|
|
||||||
<FormItem invalid={!!(errors.type && touched.type)} errorMessage={errors.type}>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{types.map((t) => (
|
|
||||||
<label
|
|
||||||
key={t.value}
|
|
||||||
className={`flex items-center gap-2 px-2 py-1 rounded-md cursor-pointer transition-all duration-200 ${
|
|
||||||
values.type === t.value
|
|
||||||
? 'border-purple-500 bg-purple-50 text-purple-700'
|
|
||||||
: 'border-gray-300 hover:border-purple-400'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Radio
|
|
||||||
value={t.value}
|
|
||||||
checked={values.type === t.value}
|
|
||||||
onChange={() => setFieldValue('type', t.value)}
|
|
||||||
/>
|
|
||||||
{t.label}
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
{/* KONUSU */}
|
|
||||||
<FormItem
|
|
||||||
label="Konu"
|
|
||||||
asterisk
|
|
||||||
invalid={!!(errors.subject && touched.subject)}
|
|
||||||
errorMessage={errors.subject}
|
|
||||||
>
|
|
||||||
<Field
|
|
||||||
type="text"
|
|
||||||
name="subject"
|
|
||||||
as={Input}
|
|
||||||
placeholder={translate('::ListForms.ListForm.NoteModal.Subject')}
|
|
||||||
/>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
{/* İÇERİK */}
|
|
||||||
<FormItem
|
|
||||||
label="İçerik"
|
|
||||||
asterisk
|
|
||||||
invalid={!!(errors.content && touched.content)}
|
|
||||||
errorMessage={errors.content}
|
|
||||||
>
|
|
||||||
<Field name="content">
|
|
||||||
{({ field }: FieldProps) => (
|
|
||||||
<HtmlEditor
|
|
||||||
value={field.value}
|
|
||||||
onValueChanged={(e) => setFieldValue('content', e.value)}
|
|
||||||
height={220}
|
|
||||||
placeholder={translate('::ListForms.ListForm.NoteModal.Content')}
|
|
||||||
>
|
|
||||||
<MediaResizing enabled={true} />
|
|
||||||
<ImageUpload fileUploadMode="base64" />
|
|
||||||
<Toolbar multiline>
|
|
||||||
<Item name="undo" />
|
|
||||||
<Item name="redo" />
|
|
||||||
<Item name="separator" />
|
|
||||||
<Item name="size" acceptedValues={sizeValues} options={fontSizeOptions} />
|
|
||||||
<Item
|
|
||||||
name="font"
|
|
||||||
acceptedValues={fontValues}
|
|
||||||
options={fontFamilyOptions}
|
|
||||||
/>
|
|
||||||
<Item name="separator" />
|
|
||||||
<Item name="bold" />
|
|
||||||
<Item name="italic" />
|
|
||||||
<Item name="underline" />
|
|
||||||
<Item name="strike" />
|
|
||||||
<Item name="separator" />
|
|
||||||
<Item name="orderedList" />
|
|
||||||
<Item name="bulletList" />
|
|
||||||
<Item name="separator" />
|
|
||||||
<Item
|
|
||||||
name="header"
|
|
||||||
acceptedValues={headerValues}
|
|
||||||
options={headerOptions}
|
|
||||||
/>
|
|
||||||
<Item name="separator" />
|
|
||||||
<Item name="color" />
|
|
||||||
<Item name="background" />
|
|
||||||
<Item name="separator" />
|
|
||||||
<Item name="alignLeft" />
|
|
||||||
<Item name="alignCenter" />
|
|
||||||
<Item name="alignRight" />
|
|
||||||
<Item name="alignJustify" />
|
|
||||||
<Item name="separator" />
|
|
||||||
<Item name="link" />
|
|
||||||
<Item name="image" />
|
|
||||||
<Item name="separator" />
|
|
||||||
<Item name="clear" />
|
|
||||||
</Toolbar>
|
|
||||||
</HtmlEditor>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
{/* DOSYA YÜKLEME */}
|
|
||||||
<FormItem>
|
|
||||||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-3 text-center hover:border-purple-400 transition-colors duration-200">
|
|
||||||
<Upload
|
|
||||||
className="cursor-pointer"
|
|
||||||
showList={false}
|
|
||||||
multiple
|
|
||||||
fileList={fileList}
|
|
||||||
beforeUpload={beforeUpload}
|
|
||||||
onChange={(files: File[]) => {
|
|
||||||
setFileList((prev) => [
|
|
||||||
...prev,
|
|
||||||
...files.filter(
|
|
||||||
(f) => !prev.some((p) => p.name === f.name && p.size === f.size),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
icon={<FaFileUpload />}
|
|
||||||
type="button"
|
|
||||||
variant="twoTone"
|
|
||||||
className="flex items-center justify-center mx-auto"
|
|
||||||
>
|
|
||||||
{translate('::App.Listforms.ImportManager.UploadFile')}
|
|
||||||
</Button>
|
|
||||||
</Upload>
|
|
||||||
|
|
||||||
{fileList.length > 0 && (
|
|
||||||
<div className="mt-2 max-h-28 overflow-y-auto border rounded-md bg-gray-50 text-left">
|
|
||||||
{fileList.map((file, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="flex items-center justify-between p-2 border-b last:border-none"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2 truncate">
|
|
||||||
<FaFileAlt className="text-blue-500 text-sm flex-shrink-0" />
|
|
||||||
<span className="text-sm truncate">{file.name}</span>
|
|
||||||
<span className="text-xs text-gray-500">
|
|
||||||
({(file.size / 1024).toFixed(1)} KB)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="plain"
|
|
||||||
size="sm"
|
|
||||||
icon={<FaTrash />}
|
|
||||||
type="button"
|
|
||||||
onClick={() => removeFile(index)}
|
|
||||||
className="text-red-500 hover:text-red-700"
|
|
||||||
></Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
</FormContainer>
|
|
||||||
|
|
||||||
{/* ALT BUTONLAR */}
|
|
||||||
<div className="mt-5 flex justify-between items-center pt-4 border-t border-gray-200">
|
|
||||||
<Button variant="default" size="md" onClick={onClose} disabled={uploading}>
|
|
||||||
{translate('::Cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="solid"
|
|
||||||
size="md"
|
|
||||||
type="submit"
|
|
||||||
disabled={isSubmitting || !values.subject || !values.type}
|
|
||||||
className="px-6 flex items-center gap-2"
|
|
||||||
>
|
|
||||||
{uploading ? translate('::App.Loading') : translate('::ListForms.Wizard.Add')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</Formik>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Formik
|
||||||
|
initialValues={{ type: 'note', subject: '', content: '' }}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
onSubmit={handleSave}
|
||||||
|
>
|
||||||
|
{({ values, touched, errors, setFieldValue, isSubmitting }) => (
|
||||||
|
<Form className={classNames('flex flex-col', isMaximized ? 'flex-1 min-h-0' : '')}>
|
||||||
|
<FormContainer
|
||||||
|
size="sm"
|
||||||
|
className={classNames(isMaximized ? 'flex-1 min-h-0 overflow-y-auto' : '')}
|
||||||
|
>
|
||||||
|
{/* NOT TİPİ */}
|
||||||
|
<FormItem invalid={!!(errors.type && touched.type)} errorMessage={errors.type}>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{types.map((t) => (
|
||||||
|
<label
|
||||||
|
key={t.value}
|
||||||
|
className={`flex items-center gap-2 px-2 py-1 rounded-md cursor-pointer transition-all duration-200 ${
|
||||||
|
values.type === t.value
|
||||||
|
? 'border-purple-500 bg-purple-50 text-purple-700'
|
||||||
|
: 'border-gray-300 hover:border-purple-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Radio
|
||||||
|
value={t.value}
|
||||||
|
checked={values.type === t.value}
|
||||||
|
onChange={() => setFieldValue('type', t.value)}
|
||||||
|
/>
|
||||||
|
{t.label}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
{/* KONUSU */}
|
||||||
|
<FormItem
|
||||||
|
label="Konu"
|
||||||
|
asterisk
|
||||||
|
invalid={!!(errors.subject && touched.subject)}
|
||||||
|
errorMessage={errors.subject}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
type="text"
|
||||||
|
name="subject"
|
||||||
|
as={Input}
|
||||||
|
placeholder={translate('::ListForms.ListForm.NoteModal.Subject')}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
{/* İÇERİK */}
|
||||||
|
<FormItem
|
||||||
|
label="İçerik"
|
||||||
|
asterisk
|
||||||
|
invalid={!!(errors.content && touched.content)}
|
||||||
|
errorMessage={errors.content}
|
||||||
|
>
|
||||||
|
<Field name="content">
|
||||||
|
{({ field }: FieldProps) => (
|
||||||
|
<HtmlEditor
|
||||||
|
value={field.value}
|
||||||
|
onValueChanged={(e) => setFieldValue('content', e.value)}
|
||||||
|
height={isMaximized ? '50vh' : 220}
|
||||||
|
placeholder={translate('::ListForms.ListForm.NoteModal.Content')}
|
||||||
|
>
|
||||||
|
<MediaResizing enabled={true} />
|
||||||
|
<ImageUpload fileUploadMode="base64" />
|
||||||
|
<Toolbar multiline>
|
||||||
|
<Item name="undo" />
|
||||||
|
<Item name="redo" />
|
||||||
|
<Item name="separator" />
|
||||||
|
<Item name="size" acceptedValues={sizeValues} options={fontSizeOptions} />
|
||||||
|
<Item
|
||||||
|
name="font"
|
||||||
|
acceptedValues={fontValues}
|
||||||
|
options={fontFamilyOptions}
|
||||||
|
/>
|
||||||
|
<Item name="separator" />
|
||||||
|
<Item name="bold" />
|
||||||
|
<Item name="italic" />
|
||||||
|
<Item name="underline" />
|
||||||
|
<Item name="strike" />
|
||||||
|
<Item name="separator" />
|
||||||
|
<Item name="orderedList" />
|
||||||
|
<Item name="bulletList" />
|
||||||
|
<Item name="separator" />
|
||||||
|
<Item
|
||||||
|
name="header"
|
||||||
|
acceptedValues={headerValues}
|
||||||
|
options={headerOptions}
|
||||||
|
/>
|
||||||
|
<Item name="separator" />
|
||||||
|
<Item name="color" />
|
||||||
|
<Item name="background" />
|
||||||
|
<Item name="separator" />
|
||||||
|
<Item name="alignLeft" />
|
||||||
|
<Item name="alignCenter" />
|
||||||
|
<Item name="alignRight" />
|
||||||
|
<Item name="alignJustify" />
|
||||||
|
<Item name="separator" />
|
||||||
|
<Item name="link" />
|
||||||
|
<Item name="image" />
|
||||||
|
<Item name="separator" />
|
||||||
|
<Item name="clear" />
|
||||||
|
</Toolbar>
|
||||||
|
</HtmlEditor>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
{/* DOSYA YÜKLEME */}
|
||||||
|
<FormItem>
|
||||||
|
<div className="border-2 border-dashed border-gray-300 rounded-lg p-3 text-center hover:border-purple-400 transition-colors duration-200">
|
||||||
|
<Upload
|
||||||
|
className="cursor-pointer"
|
||||||
|
showList={false}
|
||||||
|
multiple
|
||||||
|
fileList={fileList}
|
||||||
|
beforeUpload={beforeUpload}
|
||||||
|
onChange={(files: File[]) => {
|
||||||
|
setFileList((prev) => [
|
||||||
|
...prev,
|
||||||
|
...files.filter(
|
||||||
|
(f) => !prev.some((p) => p.name === f.name && p.size === f.size),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<FaFileUpload />}
|
||||||
|
type="button"
|
||||||
|
variant="twoTone"
|
||||||
|
className="flex items-center justify-center mx-auto"
|
||||||
|
>
|
||||||
|
{translate('::App.Listforms.ImportManager.UploadFile')}
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
|
||||||
|
{fileList.length > 0 && (
|
||||||
|
<div className="mt-2 max-h-28 overflow-y-auto border rounded-md bg-gray-50 text-left">
|
||||||
|
{fileList.map((file, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex items-center justify-between p-2 border-b last:border-none"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 truncate">
|
||||||
|
<FaFileAlt className="text-blue-500 text-sm flex-shrink-0" />
|
||||||
|
<span className="text-sm truncate">{file.name}</span>
|
||||||
|
<span className="text-xs text-gray-500">
|
||||||
|
({(file.size / 1024).toFixed(1)} KB)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
size="sm"
|
||||||
|
icon={<FaTrash />}
|
||||||
|
type="button"
|
||||||
|
onClick={() => removeFile(index)}
|
||||||
|
className="text-red-500 hover:text-red-700"
|
||||||
|
></Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
</FormContainer>
|
||||||
|
|
||||||
|
{/* ALT BUTONLAR */}
|
||||||
|
<Dialog.Footer className="mt-5 flex justify-between items-center pt-4 border-t border-gray-200">
|
||||||
|
<Button variant="default" size="md" onClick={onClose} disabled={uploading}>
|
||||||
|
{translate('::Cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
size="md"
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || !values.subject || !values.type}
|
||||||
|
className="px-6 flex items-center gap-2"
|
||||||
|
>
|
||||||
|
{uploading ? translate('::App.Loading') : translate('::ListForms.Wizard.Add')}
|
||||||
|
</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
|
entityName,
|
||||||
|
entityId,
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onNoteAdded,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Dialog isOpen={isOpen} onClose={onClose} width={700}>
|
||||||
|
<Dialog.Body className="flex flex-col p-2">
|
||||||
|
<NoteModalContent
|
||||||
|
entityName={entityName}
|
||||||
|
entityId={entityId}
|
||||||
|
onClose={onClose}
|
||||||
|
onNoteAdded={onNoteAdded}
|
||||||
|
/>
|
||||||
|
</Dialog.Body>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ export function AdminView({
|
||||||
const navigationItems = [
|
const navigationItems = [
|
||||||
{
|
{
|
||||||
id: 'stats' as AdminSection,
|
id: 'stats' as AdminSection,
|
||||||
label: translate('::App.Videoroom.Dashboard'),
|
label: translate('::App.Forum.Dashboard'),
|
||||||
icon: FaChartBar,
|
icon: FaChartBar,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -167,11 +167,11 @@ export function CategoryManagement({
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
icon={<FaPlus className="w-4 h-4" />}
|
|
||||||
onClick={() => setShowCreateForm(true)}
|
onClick={() => setShowCreateForm(true)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
|
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
|
<FaPlus className="w-4 h-4" />
|
||||||
<span>{translate('::App.Forum.CategoryManagement.AddCategory')}</span>
|
<span>{translate('::App.Forum.CategoryManagement.AddCategory')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -369,56 +369,56 @@ export function CategoryManagement({
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size='xs'
|
||||||
icon={
|
|
||||||
category.isActive ? (
|
|
||||||
<FaEye className="w-4 h-4" />
|
|
||||||
) : (
|
|
||||||
<FaEyeSlash className="w-4 h-4" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={() => handleToggleActive(category)}
|
onClick={() => handleToggleActive(category)}
|
||||||
className={`p-2 rounded-lg transition-colors ${
|
className={`p-1 rounded-lg transition-colors ${
|
||||||
category.isActive
|
category.isActive
|
||||||
? 'text-green-600 hover:bg-green-100'
|
? 'text-green-600 hover:bg-green-100'
|
||||||
: 'text-red-600 hover:bg-red-100'
|
: 'text-red-600 hover:bg-red-100'
|
||||||
}`}
|
}`}
|
||||||
title={category.isActive ? 'Hide Category' : 'Show Category'}
|
title={category.isActive ? 'Hide Category' : 'Show Category'}
|
||||||
></Button>
|
>
|
||||||
|
{category.isActive ? (
|
||||||
|
<FaEye className="w-3 h-3" />
|
||||||
|
) : (
|
||||||
|
<FaEyeSlash className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size='xs'
|
||||||
onClick={() => handleToggleLocked(category)}
|
onClick={() => handleToggleLocked(category)}
|
||||||
className={`p-2 rounded-lg transition-colors ${
|
className={`p-1 rounded-lg transition-colors ${
|
||||||
category.isLocked
|
category.isLocked
|
||||||
? 'text-yellow-600 hover:bg-yellow-100'
|
? 'text-yellow-600 hover:bg-yellow-100'
|
||||||
: 'text-green-600 hover:bg-green-100'
|
: 'text-green-600 hover:bg-green-100'
|
||||||
}`}
|
}`}
|
||||||
title={category.isLocked ? 'Unlock Category' : 'Lock Category'}
|
title={category.isLocked ? 'Unlock Category' : 'Lock Category'}
|
||||||
icon={
|
>
|
||||||
category.isLocked ? (
|
{category.isLocked ? (
|
||||||
<FaLock className="w-4 h-4" />
|
<FaLock className="w-3 h-3" />
|
||||||
) : (
|
) : (
|
||||||
<FaUnlock className="w-4 h-4" />
|
<FaUnlock className="w-3 h-3" />
|
||||||
)
|
)}
|
||||||
}
|
</Button>
|
||||||
></Button>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size='xs'
|
||||||
onClick={() => handleEdit(category)}
|
onClick={() => handleEdit(category)}
|
||||||
className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
|
className="p-1 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
|
||||||
title={translate('::App.Forum.CategoryManagement.EditCategory')}
|
title={translate('::App.Forum.CategoryManagement.EditCategory')}
|
||||||
icon={<FaEdit className="w-4 h-4" />}
|
>
|
||||||
></Button>
|
<FaEdit className="w-3 h-3" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size='xs'
|
||||||
onClick={() => confirmDeleteCategory(category)}
|
onClick={() => confirmDeleteCategory(category)}
|
||||||
className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
|
className="p-1 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
|
||||||
title={translate('::App.Forum.CategoryManagement.DeleteCategory')}
|
title={translate('::App.Forum.CategoryManagement.DeleteCategory')}
|
||||||
icon={<FaTrash className="w-4 h-4" />}
|
>
|
||||||
></Button>
|
<FaTrash className="w-3 h-3" />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -165,12 +165,12 @@ export function PostManagement({
|
||||||
{translate('::App.Forum.PostManagement.Title')}
|
{translate('::App.Forum.PostManagement.Title')}
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
<Button
|
||||||
icon={<FaPlus className="w-4 h-4" />}
|
|
||||||
variant="solid"
|
variant="solid"
|
||||||
onClick={() => setShowCreateForm(true)}
|
onClick={() => setShowCreateForm(true)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
|
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
|
<FaPlus className="w-4 h-4" />
|
||||||
<span>{translate('::App.Forum.PostManagement.AddPost')}</span>
|
<span>{translate('::App.Forum.PostManagement.AddPost')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -399,15 +399,9 @@ export function PostManagement({
|
||||||
|
|
||||||
<div className="flex items-center space-x-2 ml-4">
|
<div className="flex items-center space-x-2 ml-4">
|
||||||
<Button
|
<Button
|
||||||
icon={
|
size='xs'
|
||||||
post.isAcceptedAnswer ? (
|
|
||||||
<FaCheckCircle className="w-4 h-4" />
|
|
||||||
) : (
|
|
||||||
<FaCircle className="w-4 h-4" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={() => handleToggleAcceptedAnswer(post)}
|
onClick={() => handleToggleAcceptedAnswer(post)}
|
||||||
className={`p-2 rounded-lg transition-colors ${
|
className={`p-1 rounded-lg transition-colors ${
|
||||||
post.isAcceptedAnswer
|
post.isAcceptedAnswer
|
||||||
? 'text-emerald-600 hover:bg-emerald-100'
|
? 'text-emerald-600 hover:bg-emerald-100'
|
||||||
: 'text-gray-400 hover:bg-gray-100'
|
: 'text-gray-400 hover:bg-gray-100'
|
||||||
|
|
@ -417,24 +411,30 @@ export function PostManagement({
|
||||||
? 'Remove Accepted Answer'
|
? 'Remove Accepted Answer'
|
||||||
: 'Mark as Accepted Answer'
|
: 'Mark as Accepted Answer'
|
||||||
}
|
}
|
||||||
></Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
icon={<FaEdit className="w-4 h-4" />}
|
|
||||||
onClick={() => handleEdit(post)}
|
|
||||||
className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
|
|
||||||
title={translate('::App.Forum.PostManagement.EditPost')}
|
|
||||||
>
|
>
|
||||||
|
{post.isAcceptedAnswer ? (
|
||||||
|
<FaCheckCircle className="w-3 h-3" />
|
||||||
|
) : (
|
||||||
|
<FaCircle className="w-3 h-3" />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
icon={<FaTrashAlt className="w-4 h-4" />}
|
size='xs'
|
||||||
|
onClick={() => handleEdit(post)}
|
||||||
|
className="p-1 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
|
||||||
|
title={translate('::App.Forum.PostManagement.EditPost')}
|
||||||
|
>
|
||||||
|
<FaEdit className="w-3 h-3" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size='xs'
|
||||||
onClick={() => confirmDeletePost(post)}
|
onClick={() => confirmDeletePost(post)}
|
||||||
className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
|
className="p-1 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
|
||||||
title={translate('::App.Forum.PostManagement.DeletePost')}
|
title={translate('::App.Forum.PostManagement.DeletePost')}
|
||||||
>
|
>
|
||||||
|
<FaTrashAlt className="w-3 h-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -208,11 +208,11 @@ export function TopicManagement({
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
icon={<FaPlus className="w-4 h-4" />}
|
|
||||||
onClick={() => setShowCreateForm(true)}
|
onClick={() => setShowCreateForm(true)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
|
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
|
<FaPlus className="w-4 h-4" />
|
||||||
<span>{translate('::App.Forum.TopicManagement.AddTopic')}</span>
|
<span>{translate('::App.Forum.TopicManagement.AddTopic')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -400,69 +400,73 @@ export function TopicManagement({
|
||||||
|
|
||||||
<div className="flex items-center space-x-2 ml-4">
|
<div className="flex items-center space-x-2 ml-4">
|
||||||
<Button
|
<Button
|
||||||
icon={
|
size='xs'
|
||||||
topic.isPinned ? (
|
|
||||||
<FaThumbtack className="w-4 h-4" />
|
|
||||||
) : (
|
|
||||||
<FaTree className="w-4 h-4" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={() => handlePin(topic)}
|
onClick={() => handlePin(topic)}
|
||||||
className={`p-2 rounded-lg transition-colors ${
|
className={`p-1 rounded-lg transition-colors ${
|
||||||
topic.isPinned
|
topic.isPinned
|
||||||
? 'text-orange-600 hover:bg-orange-100'
|
? 'text-orange-600 hover:bg-orange-100'
|
||||||
: 'text-gray-400 hover:bg-gray-100'
|
: 'text-gray-400 hover:bg-gray-100'
|
||||||
}`}
|
}`}
|
||||||
title={topic.isPinned ? 'Unpin Topic' : 'Pin Topic'}
|
title={topic.isPinned ? 'Unpin Topic' : 'Pin Topic'}
|
||||||
></Button>
|
>
|
||||||
|
{topic.isPinned ? (
|
||||||
|
<FaThumbtack className="w-3 h-3" />
|
||||||
|
) : (
|
||||||
|
<FaTree className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
icon={
|
size='xs'
|
||||||
topic.isLocked ? (
|
|
||||||
<FaLock className="w-4 h-4" />
|
|
||||||
) : (
|
|
||||||
<FaUnlock className="w-4 h-4" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={() => handleLock(topic)}
|
onClick={() => handleLock(topic)}
|
||||||
className={`p-2 rounded-lg transition-colors ${
|
className={`p-1 rounded-lg transition-colors ${
|
||||||
topic.isLocked
|
topic.isLocked
|
||||||
? 'text-yellow-600 hover:bg-yellow-100'
|
? 'text-yellow-600 hover:bg-yellow-100'
|
||||||
: 'text-green-600 hover:bg-green-100'
|
: 'text-green-600 hover:bg-green-100'
|
||||||
}`}
|
}`}
|
||||||
title={topic.isLocked ? 'Unlock Topic' : 'Lock Topic'}
|
title={topic.isLocked ? 'Unlock Topic' : 'Lock Topic'}
|
||||||
></Button>
|
>
|
||||||
|
{topic.isLocked ? (
|
||||||
|
<FaLock className="w-3 h-3" />
|
||||||
|
) : (
|
||||||
|
<FaUnlock className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
icon={
|
size='xs'
|
||||||
topic.isSolved ? (
|
|
||||||
<FaCheckCircle className="w-4 h-4" />
|
|
||||||
) : (
|
|
||||||
<FaCircle className="w-4 h-4" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={() => handleSolved(topic)}
|
onClick={() => handleSolved(topic)}
|
||||||
className={`p-2 rounded-lg transition-colors ${
|
className={`p-1 rounded-lg transition-colors ${
|
||||||
topic.isSolved
|
topic.isSolved
|
||||||
? 'text-emerald-600 hover:bg-emerald-100'
|
? 'text-emerald-600 hover:bg-emerald-100'
|
||||||
: 'text-gray-400 hover:bg-gray-100'
|
: 'text-gray-400 hover:bg-gray-100'
|
||||||
}`}
|
}`}
|
||||||
title={topic.isSolved ? 'Mark as Unsolved' : 'Mark as Solved'}
|
title={topic.isSolved ? 'Mark as Unsolved' : 'Mark as Solved'}
|
||||||
></Button>
|
>
|
||||||
|
{topic.isSolved ? (
|
||||||
|
<FaCheckCircle className="w-3 h-3" />
|
||||||
|
) : (
|
||||||
|
<FaCircle className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
icon={<FaEdit className="w-4 h-4" />}
|
size='xs'
|
||||||
onClick={() => handleEdit(topic)}
|
onClick={() => handleEdit(topic)}
|
||||||
className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
|
className="p-1 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
|
||||||
title={translate('::App.Forum.TopicManagement.EditTopic')}
|
title={translate('::App.Forum.TopicManagement.EditTopic')}
|
||||||
></Button>
|
>
|
||||||
|
<FaEdit className="w-3 h-3" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
icon={<FaTrashAlt className="w-4 h-4" />}
|
size='xs'
|
||||||
onClick={() => confirmDeleteTopic(topic)}
|
onClick={() => confirmDeleteTopic(topic)}
|
||||||
className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
|
className="p-1 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
|
||||||
title={translate('::App.Forum.TopicManagement.DeleteTopic')}
|
title={translate('::App.Forum.TopicManagement.DeleteTopic')}
|
||||||
></Button>
|
>
|
||||||
|
<FaTrashAlt className="w-3 h-3" />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1476,7 +1476,9 @@ const Grid = (props: GridProps) => {
|
||||||
onClose={() => filterData.setIsImportModalOpen(false)}
|
onClose={() => filterData.setIsImportModalOpen(false)}
|
||||||
onRequestClose={() => filterData.setIsImportModalOpen(false)}
|
onRequestClose={() => filterData.setIsImportModalOpen(false)}
|
||||||
>
|
>
|
||||||
<ImportDashboard gridDto={gridDto} />
|
<Dialog.Body className="flex flex-col">
|
||||||
|
<ImportDashboard gridDto={gridDto} />
|
||||||
|
</Dialog.Body>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -404,11 +404,17 @@ const SchedulerView = (props: SchedulerViewProps) => {
|
||||||
canUpdate: listFormField?.canUpdate ?? false,
|
canUpdate: listFormField?.canUpdate ?? false,
|
||||||
canCreate: listFormField?.canCreate ?? false,
|
canCreate: listFormField?.canCreate ?? false,
|
||||||
canExport: listFormField?.canExport ?? false,
|
canExport: listFormField?.canExport ?? false,
|
||||||
|
allowEditing: listFormField?.allowEditing ?? true,
|
||||||
|
allowAdding: listFormField?.allowAdding ?? true,
|
||||||
dataField: i.dataField,
|
dataField: i.dataField,
|
||||||
name: i.dataField,
|
name: i.dataField,
|
||||||
editorType2: i.editorType2,
|
editorType2: i.editorType2,
|
||||||
editorType:
|
editorType:
|
||||||
i.editorType2 == PlatformEditorTypes.dxGridBox ? 'dxDropDownBox' : i.editorType2,
|
i.editorType2 == PlatformEditorTypes.dxGridBox
|
||||||
|
? 'dxDropDownBox'
|
||||||
|
: i.editorType2 == PlatformEditorTypes.dxImageUpload
|
||||||
|
? undefined
|
||||||
|
: i.editorType2,
|
||||||
colSpan: i.colSpan,
|
colSpan: i.colSpan,
|
||||||
isRequired: i.isRequired,
|
isRequired: i.isRequired,
|
||||||
editorOptions,
|
editorOptions,
|
||||||
|
|
@ -419,7 +425,10 @@ const SchedulerView = (props: SchedulerViewProps) => {
|
||||||
item.label = { text: captionize(i.dataField.split(':')[1]) }
|
item.label = { text: captionize(i.dataField.split(':')[1]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((currentMode == 'edit' && !item.canUpdate) || (currentMode == 'new' && !item.canCreate)) {
|
if (
|
||||||
|
(currentMode == 'edit' && !item.canUpdate) ||
|
||||||
|
(currentMode == 'new' && !item.canCreate)
|
||||||
|
) {
|
||||||
item.editorOptions = {
|
item.editorOptions = {
|
||||||
...item.editorOptions,
|
...item.editorOptions,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
|
|
@ -492,18 +501,22 @@ const SchedulerView = (props: SchedulerViewProps) => {
|
||||||
// Tabbed varsa belirli class'lara sahip elementi bul ve flex-direction'ı değiştir
|
// Tabbed varsa belirli class'lara sahip elementi bul ve flex-direction'ı değiştir
|
||||||
if (hasTabbedItems) {
|
if (hasTabbedItems) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const targetElement = document.querySelector('.dx-item-content.dx-box-item-content.dx-box-flex.dx-box.dx-widget.dx-collection')
|
const targetElement = document.querySelector(
|
||||||
|
'.dx-item-content.dx-box-item-content.dx-box-flex.dx-box.dx-widget.dx-collection',
|
||||||
|
)
|
||||||
if (targetElement) {
|
if (targetElement) {
|
||||||
const htmlElement = targetElement as HTMLElement
|
const htmlElement = targetElement as HTMLElement
|
||||||
|
|
||||||
// Mevcut style'ı al ve flex-direction: row'u column'a çevir
|
// Mevcut style'ı al ve flex-direction: row'u column'a çevir
|
||||||
const currentStyle = htmlElement.getAttribute('style') || ''
|
const currentStyle = htmlElement.getAttribute('style') || ''
|
||||||
const updatedStyle = currentStyle.replace(/flex-direction:\s*row/gi, 'flex-direction: column')
|
const updatedStyle = currentStyle.replace(
|
||||||
|
/flex-direction:\s*row/gi,
|
||||||
|
'flex-direction: column',
|
||||||
|
)
|
||||||
htmlElement.setAttribute('style', updatedStyle)
|
htmlElement.setAttribute('style', updatedStyle)
|
||||||
}
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
[gridDto, translate, isPopupFullScreen, listFormCode],
|
[gridDto, translate, isPopupFullScreen, listFormCode],
|
||||||
)
|
)
|
||||||
|
|
@ -540,7 +553,7 @@ const SchedulerView = (props: SchedulerViewProps) => {
|
||||||
key={`Scheduler-${listFormCode}-${schedulerDataSource ? 'loaded' : 'loading'}`}
|
key={`Scheduler-${listFormCode}-${schedulerDataSource ? 'loaded' : 'loading'}`}
|
||||||
id={'Scheduler-' + listFormCode}
|
id={'Scheduler-' + listFormCode}
|
||||||
dataSource={schedulerDataSource}
|
dataSource={schedulerDataSource}
|
||||||
dateSerializationFormat='yyyy-MM-ddTHH:mm:ss'
|
dateSerializationFormat="yyyy-MM-ddTHH:mm:ss"
|
||||||
textExpr={gridDto.gridOptions.schedulerOptionDto?.textExpr || 'text'}
|
textExpr={gridDto.gridOptions.schedulerOptionDto?.textExpr || 'text'}
|
||||||
startDateExpr={gridDto.gridOptions.schedulerOptionDto?.startDateExpr || 'startDate'}
|
startDateExpr={gridDto.gridOptions.schedulerOptionDto?.startDateExpr || 'startDate'}
|
||||||
endDateExpr={gridDto.gridOptions.schedulerOptionDto?.endDateExpr || 'endDate'}
|
endDateExpr={gridDto.gridOptions.schedulerOptionDto?.endDateExpr || 'endDate'}
|
||||||
|
|
|
||||||
|
|
@ -1068,13 +1068,17 @@ const Tree = (props: TreeProps) => {
|
||||||
canUpdate: listFormField?.canUpdate ?? false,
|
canUpdate: listFormField?.canUpdate ?? false,
|
||||||
canCreate: listFormField?.canCreate ?? false,
|
canCreate: listFormField?.canCreate ?? false,
|
||||||
canExport: listFormField?.canExport ?? false,
|
canExport: listFormField?.canExport ?? false,
|
||||||
|
allowEditing: listFormField?.allowEditing ?? true,
|
||||||
|
allowAdding: listFormField?.allowAdding ?? true,
|
||||||
dataField: i.dataField,
|
dataField: i.dataField,
|
||||||
name: i.dataField,
|
name: i.dataField,
|
||||||
editorType2: i.editorType2,
|
editorType2: i.editorType2,
|
||||||
editorType:
|
editorType:
|
||||||
i.editorType2 == PlatformEditorTypes.dxGridBox
|
i.editorType2 == PlatformEditorTypes.dxGridBox
|
||||||
? 'dxDropDownBox'
|
? 'dxDropDownBox'
|
||||||
: i.editorType2,
|
: i.editorType2 == PlatformEditorTypes.dxImageUpload
|
||||||
|
? undefined
|
||||||
|
: i.editorType2,
|
||||||
colSpan: i.colSpan,
|
colSpan: i.colSpan,
|
||||||
isRequired: i.isRequired,
|
isRequired: i.isRequired,
|
||||||
editorOptions,
|
editorOptions,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue