diff --git a/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs b/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs index 3836d8e..f0f7e9a 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs @@ -220,7 +220,7 @@ public static class LookupQueryValues $"\"Name\" AS \"Name\" " + $"FROM \"{TableNameResolver.GetFullTableName(nameof(TableNameEnum.JobPosition))}\" " + $"WHERE " + - $"(\"DepartmentId\" = @param0 OR @param0 IS NULL) " + + $"(\"DepartmentId\" = @param0 OR \"ParentId\" IS NULL OR @param0 IS NULL) " + $"AND \"IsDeleted\" = 'false' " + $"ORDER BY \"Name\";"; diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs index f48f88a..26ef1d8 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs @@ -4026,7 +4026,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency SearchPanelJson = DefaultSearchPanelJson, GroupPanelJson = DefaultGroupPanelJson, SelectionJson = DefaultSelectionSingleJson, - ColumnOptionJson = DefaultColumnOptionJson(false), + ColumnOptionJson = DefaultColumnOptionJson(), PermissionJson = DefaultPermissionJson(listFormName), PagerOptionJson = DefaultPagerOptionJson, EditingOptionJson = DefaultEditingOptionJson(listFormName, 950, 650, true, true, true, true, false), diff --git a/ui/src/assets/styles/components/_dialog.css b/ui/src/assets/styles/components/_dialog.css index a57e9b2..446dd73 100644 --- a/ui/src/assets/styles/components/_dialog.css +++ b/ui/src/assets/styles/components/_dialog.css @@ -47,12 +47,30 @@ } .dialog-content { - @apply p-6 rounded-lg shadow-xl sm:my-16 relative h-full bg-white dark:bg-gray-800; + max-height: calc(100vh - 2rem); + @apply p-6 rounded-lg shadow-xl my-4 relative bg-white dark:bg-gray-800 flex flex-col overflow-hidden; +} + +@screen sm { + .dialog-content { + max-height: calc(100vh - 8rem); + @apply my-16; + } +} + +.dialog-header, +.dialog-footer { + @apply flex-shrink-0; +} + +.dialog-body { + @apply flex-1 min-h-0 overflow-y-auto; } .dialog-content.maximized { border-radius: 0 !important; height: 100vh !important; + max-height: 100vh !important; margin: 0 !important; overflow: hidden; display: flex; diff --git a/ui/src/components/importManager/FileUploadArea.tsx b/ui/src/components/importManager/FileUploadArea.tsx index bf5e61e..113db3d 100644 --- a/ui/src/components/importManager/FileUploadArea.tsx +++ b/ui/src/components/importManager/FileUploadArea.tsx @@ -99,7 +99,9 @@ export const FileUploadArea: React.FC = ({ {!selectedFile ? (
= ({
-
+
{dragActive ? translate('::App.Listforms.ImportManager.DropHere') : translate('::App.Listforms.ImportManager.UploadYourFile')}
-

+

{translate('::App.Listforms.ImportManager.DragOrClick')}

-
+
{translate('::App.Listforms.ImportManager.SupportedFormats')}{' '} {acceptedFormats.join(', ')} • Max size: {maxSize}MB
) : ( -
+
-
{selectedFile.name}
-
{formatFileSize(selectedFile.size)}
+
+ {selectedFile.name} +
+
+ {formatFileSize(selectedFile.size)} +
@@ -154,9 +160,9 @@ export const FileUploadArea: React.FC = ({ )} {error && ( -
+
- {error} + {error}
)} diff --git a/ui/src/components/importManager/ImportDashboard.tsx b/ui/src/components/importManager/ImportDashboard.tsx index 4b0c9f9..3fdec01 100644 --- a/ui/src/components/importManager/ImportDashboard.tsx +++ b/ui/src/components/importManager/ImportDashboard.tsx @@ -195,17 +195,17 @@ export const ImportDashboard: React.FC = ({ gridDto }) => switch (status) { case 'uploaded': case 'executed': - return 'bg-green-50 text-green-700 border-green-200' + return 'bg-green-50 text-green-700 border-green-200 dark:bg-green-950/30 dark:text-green-300 dark:border-green-900/60' case 'executed_with_errors': - return 'bg-orange-50 text-orange-700 border-orange-200' + return 'bg-orange-50 text-orange-700 border-orange-200 dark:bg-orange-950/30 dark:text-orange-300 dark:border-orange-900/60' case 'failed': case 'execute_failed': - return 'bg-red-50 text-red-700 border-red-200' + return 'bg-red-50 text-red-700 border-red-200 dark:bg-red-950/30 dark:text-red-300 dark:border-red-900/60' case 'processing': case 'validating': - return 'bg-blue-50 text-blue-700 border-blue-200' + return 'bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-950/30 dark:text-blue-300 dark:border-blue-900/60' default: - return 'bg-yellow-50 text-yellow-700 border-yellow-200' + return 'bg-yellow-50 text-yellow-700 border-yellow-200 dark:bg-yellow-950/30 dark:text-yellow-300 dark:border-yellow-900/60' } } @@ -269,17 +269,17 @@ export const ImportDashboard: React.FC = ({ gridDto }) => const getExecuteStatusColor = (status: string) => { switch (status) { case 'completed': - return 'text-green-600' + return 'text-green-600 dark:text-green-300' case 'completed_with_errors': - return 'text-orange-600' + return 'text-orange-600 dark:text-orange-300' case 'processing': - return 'text-blue-600' + return 'text-blue-600 dark:text-blue-300' case 'validating': - return 'text-yellow-600' + return 'text-yellow-600 dark:text-yellow-300' case 'failed': - return 'text-red-600' + return 'text-red-600 dark:text-red-300' default: - return 'text-slate-600' + return 'text-slate-600 dark:text-slate-400' } } @@ -331,7 +331,7 @@ export const ImportDashboard: React.FC = ({ gridDto }) => return (
{/* Navigation Tabs */} -
+
{['import', 'preview', 'history'].map((tab) => ( @@ -382,10 +382,10 @@ export const ImportDashboard: React.FC = ({ gridDto }) => @@ -394,38 +394,38 @@ export const ImportDashboard: React.FC = ({ gridDto }) =>
- + - - - - - + {editableColumns.map((column: any) => ( - - + - -
+ {translate('::App.Listform.ListformField.Column')} + {translate('::ListForms.ListFormEdit.Type')} + {translate('::App.Required')} + {translate('::Abp.Mailing.Default')}
+
{column.fieldName} + {column.dataType} @@ -435,16 +435,16 @@ export const ImportDashboard: React.FC = ({ gridDto }) => {column.validationRuleDto.some( (rule: any) => rule.type === 'required', ) ? ( - + {translate('::App.Listforms.ImportManager.Yes')} ) : ( - + {translate('::App.Listforms.ImportManager.No')} )} + {typeof column.defaultValue === 'object' ? JSON.stringify(column.defaultValue) : column.defaultValue} @@ -458,7 +458,7 @@ export const ImportDashboard: React.FC = ({ gridDto }) => {generating && (
- + {translate('::App.Listforms.ImportManager.GeneratingTemplate')}
@@ -468,8 +468,8 @@ export const ImportDashboard: React.FC = ({ gridDto }) => {/* File Upload - 1/3 width on large screens, full width on mobile */}
-
-

+
+

{translate('::App.Listforms.ImportManager.UploadData')}

@@ -506,9 +506,9 @@ export const ImportDashboard: React.FC = ({ gridDto }) => )}
) : ( -
-
- +
+
+
{translate('::App.Listforms.ImportManager.NoDataToPreview')}
@@ -520,22 +520,22 @@ export const ImportDashboard: React.FC = ({ gridDto }) => )} {activeTab === 'history' && ( -
-
-

+
+
+

{translate('::App.Listforms.ImportManager.ImportHistory')}

-
+
{importHistory.map((session) => (
@@ -543,13 +543,15 @@ export const ImportDashboard: React.FC = ({ gridDto }) => {getStatusIcon(session.status)}
-
{session.blobName}
-
+
+ {session.blobName} +
+
{new Date(session.creationTime).toLocaleString()}
{currentSession?.id === session.id && ( - + {translate('::App.Status.Active')} )} @@ -558,7 +560,7 @@ export const ImportDashboard: React.FC = ({ gridDto }) =>
-
+
{session.totalRows} {translate('::App.Listforms.ImportManager.TotalRows')}
@@ -576,8 +578,8 @@ export const ImportDashboard: React.FC = ({ gridDto }) => onClick={() => toggleSessionExecutes(session.id)} className={`p-2 rounded-lg transition-colors ${ expandedSessions.has(session.id) - ? 'text-red-500 bg-red-50 hover:text-red-600 hover:bg-red-100' - : 'text-slate-400 hover:text-slate-600 hover:bg-slate-100' + ? 'text-red-500 bg-red-50 hover:text-red-600 hover:bg-red-100 dark:bg-red-950/30 dark:text-red-300 dark:hover:bg-red-950/40' + : 'text-slate-400 hover:text-slate-600 hover:bg-slate-100 dark:text-slate-500 dark:hover:text-slate-300 dark:hover:bg-slate-800' }`} title={translate('::App.Listforms.ImportManager.ViewExecutionDetails')} > @@ -607,7 +609,7 @@ export const ImportDashboard: React.FC = ({ gridDto }) => } } }} - className="p-2 rounded-lg transition-colors text-slate-400 hover:text-blue-500 hover:bg-blue-50" + className="p-2 rounded-lg transition-colors text-slate-400 hover:text-blue-500 hover:bg-blue-50 dark:text-slate-500 dark:hover:text-blue-300 dark:hover:bg-blue-950/30" title={translate('::App.Listforms.ImportManager.RefreshExecutionDetails')} > @@ -623,8 +625,8 @@ export const ImportDashboard: React.FC = ({ gridDto }) => 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' + ? 'text-slate-300 dark:text-slate-600 cursor-not-allowed' + : 'text-slate-400 hover:text-red-500 hover:bg-red-50 dark:text-slate-500 dark:hover:text-red-300 dark:hover:bg-red-950/30' }`} title={ currentSession?.id === session.id @@ -645,10 +647,10 @@ export const ImportDashboard: React.FC = ({ gridDto }) => session.status === 'executed_with_errors' || session.status === 'failed' || session.status === 'execute_failed' - ? 'bg-red-50 text-red-700 border border-red-200' + ? 'bg-red-50 text-red-700 border border-red-200 dark:bg-red-950/30 dark:text-red-300 dark:border-red-900/60' : session.status === 'uploaded' - ? 'bg-blue-50 text-blue-700 border border-blue-200' - : 'bg-green-50 text-green-700 border border-green-200' + ? 'bg-blue-50 text-blue-700 border border-blue-200 dark:bg-blue-950/30 dark:text-blue-300 dark:border-blue-900/60' + : 'bg-green-50 text-green-700 border border-green-200 dark:bg-green-950/30 dark:text-green-300 dark:border-green-900/60' }`} > @@ -668,10 +670,10 @@ export const ImportDashboard: React.FC = ({ gridDto }) => {/* Execute Details Section */} {expandedSessions.has(session.id) && ( -
+
{loadingExecutes.has(session.id) ? ( -
+
{translate('::App.Listforms.ImportManager.LoadingExecutionDetails')} @@ -681,38 +683,38 @@ export const ImportDashboard: React.FC = ({ gridDto }) => sessionExecutes[session.id].length > 0 ? (
{sessionExecutes[session.id].map((execute) => ( -
+
{/* Sol: Tarih */}
-
+
{new Date(execute.creationTime).toLocaleString()}
{/* Orta: Executed, Valid, Errors */} -
+
-
+
{execute.execRows}
-
+
{translate('::App.Listforms.ImportManager.Executed')}
-
+
{execute.validRows}
-
+
{translate('::App.Listforms.ImportManager.Valid')}
-
+
{execute.errorRows}
-
+
{translate('::App.Listforms.ImportManager.Errors')}
@@ -737,7 +739,7 @@ export const ImportDashboard: React.FC = ({ gridDto }) =>
{expandedErrors.has(execute.id) && ( -
+
{parseErrors(execute.errorsJson).length > 0 ? ( - + - - - + {parseErrors(execute.errorsJson).map((err, idx) => ( - - + - @@ -778,7 +783,7 @@ export const ImportDashboard: React.FC = ({ gridDto }) =>
+ Satır + Hata Mesajı
+
{err.row} + {err.message}
) : ( -

+

Hata detayı mevcut değil.

)} @@ -790,7 +795,7 @@ export const ImportDashboard: React.FC = ({ gridDto }) => ))}
) : ( -
+
{translate('::App.Listforms.ImportManager.NoExecutionRecords')}
)} @@ -801,8 +806,8 @@ export const ImportDashboard: React.FC = ({ gridDto }) => ))} {importHistory.length === 0 && ( -
- +
+
{translate('::App.Listforms.ImportManager.NoImportHistory')}
diff --git a/ui/src/components/importManager/ImportPreview.tsx b/ui/src/components/importManager/ImportPreview.tsx index c8f5046..de4db27 100644 --- a/ui/src/components/importManager/ImportPreview.tsx +++ b/ui/src/components/importManager/ImportPreview.tsx @@ -100,13 +100,13 @@ export const ImportPreview: React.FC = ({ const getStatusColor = (status: string) => { switch (status) { case 'uploaded': - return 'text-green-600 bg-green-50 border-green-200' + return 'text-green-600 bg-green-50 border-green-200 dark:text-green-300 dark:bg-green-950/30 dark:border-green-900/60' case 'failed': - return 'text-red-600 bg-red-50 border-red-200' + return 'text-red-600 bg-red-50 border-red-200 dark:text-red-300 dark:bg-red-950/30 dark:border-red-900/60' case 'validating': - return 'text-yellow-600 bg-yellow-50 border-yellow-200' + return 'text-yellow-600 bg-yellow-50 border-yellow-200 dark:text-yellow-300 dark:bg-yellow-950/30 dark:border-yellow-900/60' default: - return 'text-blue-600 bg-blue-50 border-blue-200' + return 'text-blue-600 bg-blue-50 border-blue-200 dark:text-blue-300 dark:bg-blue-950/30 dark:border-blue-900/60' } } @@ -145,14 +145,14 @@ export const ImportPreview: React.FC = ({ } return ( -
+
{/* Header */} -
+
{/* Başlık kısmı - Üstte mobile, solda desktop */}
-

+

{translate('::App.Listforms.ImportManager.ImportPreviewTitle')}

@@ -160,9 +160,9 @@ export const ImportPreview: React.FC = ({ {/* İstatistik kartları - Mobile'da alt alta, desktop'ta yan yana */}
-
+
{previewData?.rows?.length || session.totalRows || 0}{' '} - + {translate('::App.Listforms.ImportManager.TotalRows')}
@@ -184,16 +184,16 @@ export const ImportPreview: React.FC = ({ {/* Preview Data */} {previewData && previewData.headers && previewData.headers.length > 0 ? ( -
-

+
+

{translate('::App.Listforms.ImportManager.DataPreviewTitle')}

-
+
- + - ))} - + {previewData.rows.map((row: any[], rowIndex: number) => ( @@ -242,25 +242,25 @@ export const ImportPreview: React.FC = ({ ) : previewData && previewData.headers && previewData.headers.length === 0 ? ( -
+
-

+

{translate('::App.Listforms.ImportManager.NoDataFoundTitle')}

-

+

{translate('::App.Listforms.ImportManager.NoDataFoundDescription')}

) : ( -
+
-

+

{translate('::App.Listforms.ImportManager.LoadingPreviewTitle')}

-

+

{translate('::App.Listforms.ImportManager.LoadingPreviewDescription')}

@@ -271,8 +271,8 @@ export const ImportPreview: React.FC = ({
{/* Success Message */} {showSuccessMessage && lastExecutionResult && ( -
-
+
+
{translate('::App.Listforms.ImportManager.ImportProgress.Status.Uploaded')}{' '} @@ -286,7 +286,7 @@ export const ImportPreview: React.FC = ({
{selectedRows.length === 0 && (previewData?.rows?.length || 0) > 0 && ( -
+
{translate('::App.Listforms.ImportManager.SelectRowsWarning')} @@ -295,7 +295,7 @@ export const ImportPreview: React.FC = ({ )} {selectedRows.length > 0 && ( -
+
{selectedRows.length} {translate('::App.Listforms.ImportManager.RowsSelected')} @@ -304,7 +304,7 @@ export const ImportPreview: React.FC = ({ )} {(previewData?.rows?.length || 0) === 0 && ( -
+
{translate('::App.Listforms.ImportManager.NoRowsAvailable')} @@ -314,7 +314,7 @@ export const ImportPreview: React.FC = ({
- diff --git a/ui/src/components/importManager/ImportProgress.tsx b/ui/src/components/importManager/ImportProgress.tsx index 5f1f3ae..9cd9aae 100644 --- a/ui/src/components/importManager/ImportProgress.tsx +++ b/ui/src/components/importManager/ImportProgress.tsx @@ -59,15 +59,17 @@ export const ImportProgress: React.FC = ({ session }) => { } return ( -
+
{/* Status Icon */}
{getStatusIcon()}
{/* Status Message */}
-

{getStatusMessage()}

-

+

+ {getStatusMessage()} +

+

{translate('::App.Listforms.Status.Processing')}{' '} {session.blobName}

@@ -75,11 +77,11 @@ export const ImportProgress: React.FC = ({ session }) => { {/* Progress Bar */}
-
+
{translate('::App.Listforms.ImportManager.ImportProgress.ProgressLabel')} {Math.round(getProgressPercentage())}%
-
+
{ title="Navigation" isOpen={isOpen} bodyClass={classNames(navColor(), 'p-0')} - width={330} + width={320} placement={direction === DIR_RTL ? 'right' : 'left'} onClose={onDrawerClose} onRequestClose={onDrawerClose} diff --git a/ui/src/components/ui/Dialog/Dialog.tsx b/ui/src/components/ui/Dialog/Dialog.tsx index 68cb731..fffd505 100644 --- a/ui/src/components/ui/Dialog/Dialog.tsx +++ b/ui/src/components/ui/Dialog/Dialog.tsx @@ -17,6 +17,19 @@ const DialogContext = createContext({ isMaximized: false }) export const useDialogContext = () => useContext(DialogContext) +const DialogHeader = ({ + children, + className, +}: { + children?: ReactNode + className?: string +}) => ( +
+ {children} +
+) +DialogHeader.displayName = 'Dialog.Header' + const DialogBody = ({ children, className, @@ -24,14 +37,8 @@ const DialogBody = ({ children?: ReactNode className?: string }) => { - const { isMaximized } = useContext(DialogContext) return ( -
+
{children}
) @@ -45,9 +52,8 @@ const DialogFooter = ({ children?: ReactNode className?: string }) => { - const { isMaximized } = useContext(DialogContext) return ( -
+
{children}
) @@ -231,13 +237,7 @@ const Dialog = (props: DialogProps) => { {closable && !showWindowControls && renderCloseButton} {closable && showWindowControls && renderWindowControls} - {isMaximized ? ( -
- {children} -
- ) : ( - children - )} + {children}
@@ -247,11 +247,13 @@ const Dialog = (props: DialogProps) => { Dialog.displayName = 'Dialog' type DialogType = typeof Dialog & { + Header: typeof DialogHeader Body: typeof DialogBody Footer: typeof DialogFooter } const DialogWithSubComponents = Dialog as DialogType +DialogWithSubComponents.Header = DialogHeader DialogWithSubComponents.Body = DialogBody DialogWithSubComponents.Footer = DialogFooter diff --git a/ui/src/views/admin/listForm/wizard/WizardStep2.tsx b/ui/src/views/admin/listForm/wizard/WizardStep2.tsx index 689a337..f7f8350 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStep2.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStep2.tsx @@ -5,7 +5,7 @@ import { SelectBoxOption } from '@/types/shared' import { Field, FieldProps, FormikErrors, FormikTouched, useFormikContext } from 'formik' import { useState } from 'react' import CreatableSelect from 'react-select/creatable' -import { FaArrowLeft, FaArrowRight, FaPlus } from 'react-icons/fa' +import { FaArrowLeft, FaArrowRight, FaPlus, FaTable } from 'react-icons/fa' import { dbSourceTypeOptions, listFormDefaultLayoutOptions, selectCommandTypeOptions, sqlDataTypeToDbType } from '../edit/options' import { ListFormWizardDto } from '@/proxy/admin/wizard/models' import SqlTableDesignerDialog from '@/views/developerKit/SqlTableDesignerDialog' @@ -68,8 +68,35 @@ const WizardStep2 = ({ onNext, }: WizardStep2Props) => { const [showTableDesignerDialog, setShowTableDesignerDialog] = useState(false) + const [designTableData, setDesignTableData] = useState<{ + schemaName: string + tableName: string + } | null>(null) const formik = useFormikContext() + const selectedTable = values.selectCommand + ? dbObjects?.tables.find( + (table) => + table.tableName === values.selectCommand || + table.fullName === values.selectCommand || + `${table.schemaName}.${table.tableName}` === values.selectCommand, + ) + : undefined + + const handleNewTable = () => { + setDesignTableData(null) + setShowTableDesignerDialog(true) + } + + const handleDesignTable = () => { + if (!selectedTable) return + setDesignTableData({ + schemaName: selectedTable.schemaName, + tableName: selectedTable.tableName, + }) + setShowTableDesignerDialog(true) + } + const handleTableDeployed = async (table: { schemaName: string; tableName: string }) => { await onDbObjectsRefresh(values.dataSourceCode) formik.setFieldValue('selectCommand', table.tableName) @@ -283,10 +310,19 @@ const WizardStep2 = ({ variant="solid" icon={} disabled={!values.dataSourceCode} - onClick={() => setShowTableDesignerDialog(true)} + onClick={handleNewTable} > New Table +
) }} @@ -832,9 +868,12 @@ const WizardStep2 = ({ setShowTableDesignerDialog(false)} + onClose={() => { + setShowTableDesignerDialog(false) + setDesignTableData(null) + }} dataSource={values.dataSourceCode} - initialTableData={null} + initialTableData={designTableData} onDeployed={handleTableDeployed} />
diff --git a/ui/src/views/admin/listForm/wizard/WizardStep6.tsx b/ui/src/views/admin/listForm/wizard/WizardStep6.tsx index 6c6f4c5..75bb89f 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStep6.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStep6.tsx @@ -196,34 +196,56 @@ function WizardStep6({ const resetDemo = () => { const startId = uniqueCriteriaId([]) - const approvalId = uniqueCriteriaId([], [startId]) - const endId = uniqueCriteriaId([], [startId, approvalId]) + const approval1Id = uniqueCriteriaId([], [startId]) + const approval2Id = uniqueCriteriaId([], [startId, approval1Id]) + const informId = uniqueCriteriaId([], [startId, approval1Id, approval2Id]) + const endId = uniqueCriteriaId([], [startId, approval1Id, approval2Id, informId]) updateCriteria([ { ...normalizeCriteria(emptyCriteria('Start', listFormCode)), id: startId, nodeId: startId, - title: uniqueCriteriaTitle('Start', []), - nextOnStart: approvalId, - positionX: 72, - positionY: 160, + title: 'İş Akışı Başlat1', + nextOnStart: approval1Id, + positionX: 34, + positionY: 28, } as WorkflowCriteriaDto, { ...normalizeCriteria(emptyCriteria('Approval', listFormCode)), - id: approvalId, - nodeId: approvalId, - title: uniqueCriteriaTitle('Approval', []), - nextOnApprove: endId, - positionX: 360, - positionY: 160, + id: approval1Id, + nodeId: approval1Id, + title: 'Onay1', + nextOnApprove: approval2Id, + nextOnReject: informId, + positionX: 323, + positionY: 28, + } as WorkflowCriteriaDto, + { + ...normalizeCriteria(emptyCriteria('Approval', listFormCode)), + id: approval2Id, + nodeId: approval2Id, + title: 'Onay2', + nextOnApprove: informId, + nextOnReject: informId, + positionX: 586, + positionY: 28, + } as WorkflowCriteriaDto, + { + ...normalizeCriteria(emptyCriteria('Inform', listFormCode)), + id: informId, + nodeId: informId, + title: 'Bilgilendirme1', + nextOnStart: endId, + positionX: 458, + positionY: 336, } as WorkflowCriteriaDto, { ...normalizeCriteria(emptyCriteria('End', listFormCode)), id: endId, nodeId: endId, - title: uniqueCriteriaTitle('End', []), - positionX: 650, - positionY: 160, + title: 'İş Akışı Bitir1', + positionX: 792, + positionY: 336, } as WorkflowCriteriaDto, ]) } diff --git a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx index fadaa42..d383d31 100644 --- a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx +++ b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx @@ -2,7 +2,6 @@ import classNames from 'classnames' import { createPortal } from 'react-dom' import { Button, Dialog, Notification, toast, Checkbox } from '@/components/ui' -import { useDialogContext } from '@/components/ui/Dialog/Dialog' import { FaPlus, FaTrash, @@ -835,9 +834,8 @@ const STEPS = ['Sütun Tasarımı', 'Entity Ayarları', 'Index / Key', 'İlişki type Step = 0 | 1 | 2 | 3 | 4 function StepContentWrapper({ children }: { children: React.ReactNode }) { - const { isMaximized } = useDialogContext() return ( -
+
{children}
) @@ -1810,7 +1808,7 @@ const SqlTableDesignerDialog = ({
)} -
+
{columns.map((col, idx) => { const isDuplicate = col.columnName.trim() !== '' && @@ -2741,7 +2739,7 @@ const SqlTableDesignerDialog = ({ return ( - + {/* Header */}
@@ -2761,7 +2759,9 @@ const SqlTableDesignerDialog = ({ {/* Steps */}
{renderStepIndicator()}
+ + {/* Content */} {step === 0 && renderColumnDesigner()} diff --git a/ui/src/views/form/FormDevExpress.tsx b/ui/src/views/form/FormDevExpress.tsx index bd8ae48..fbe12b4 100644 --- a/ui/src/views/form/FormDevExpress.tsx +++ b/ui/src/views/form/FormDevExpress.tsx @@ -140,7 +140,9 @@ const getValueByField = (data: Record = {}, field?: string) => { } const shouldRunEditorScriptOnContentReady = (script?: string) => - Boolean(script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly'))) + Boolean( + script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly')), + ) const FormDevExpress = (props: { listFormCode: string @@ -158,9 +160,15 @@ const FormDevExpress = (props: { const formItemsRef = useRef(formItems) const formInstanceRef = useRef() const lastContentReadyScriptKeyRef = useRef() + const didAutoFocusRef = useRef(false) const [runtimeReadOnlyFields, setRuntimeReadOnlyFields] = useState>({}) const runtimeReadOnlyFieldsRef = useRef>({}) + const isTouchLikeDevice = () => + typeof window !== 'undefined' && + (window.matchMedia?.('(pointer: coarse)').matches || + window.matchMedia?.('(hover: none)').matches) + useEffect(() => { formDataRef.current = formData }, [formData]) @@ -173,6 +181,10 @@ const FormDevExpress = (props: { runtimeReadOnlyFieldsRef.current = runtimeReadOnlyFields }, [runtimeReadOnlyFields]) + useEffect(() => { + didAutoFocusRef.current = false + }, [listFormCode, mode]) + const setRuntimeEditorReadOnly = (field: string, readOnly: boolean) => { const resolvedField = findFormFieldKey(formItemsRef.current, field) const key = String(resolvedField || field || '').toLowerCase() @@ -202,11 +214,7 @@ const FormDevExpress = (props: { setTimeout(() => setFormEditorReadOnly(formInstanceRef.current ?? form, field, readOnly), 0) } - const runEditorScript = ( - formItem: SimpleItemWithColData, - eventValue: any, - component?: any, - ) => { + const runEditorScript = (formItem: SimpleItemWithColData, eventValue: any, component?: any) => { if (!formItem?.editorScript) { return } @@ -250,7 +258,9 @@ const FormDevExpress = (props: { const prevOnValueChanged = formItem.editorOptions?.onValueChanged return { - ...(index !== undefined && mode !== 'view' ? { autoFocus: index === 1 } : {}), + ...(index !== undefined && mode !== 'view' && !isTouchLikeDevice() + ? { autoFocus: index === 1 } + : {}), ...(formItem.editorType === 'dxDateBox' ? { useMaskBehavior: true, @@ -466,7 +476,6 @@ const FormDevExpress = (props: { setTimeout(() => { updateCascadeDisabledStates() }, 0) - }} onContentReady={(e) => { formInstanceRef.current = e.component @@ -478,7 +487,8 @@ const FormDevExpress = (props: { const groupItems = e.component.option('items') as any[] const firstItem = groupItems?.[0]?.items?.[0] - if (firstItem?.dataField) { + if (!didAutoFocusRef.current && firstItem?.dataField && !isTouchLikeDevice()) { + didAutoFocusRef.current = true const editor = e.component.getEditor(firstItem.dataField) mode !== 'view' && editor?.focus() } @@ -492,98 +502,100 @@ const FormDevExpress = (props: { colSpan={formGroupItem.colSpan} caption={formGroupItem.caption} > - {(formGroupItem.items as SimpleItemWithColData[])?.filter((formItem) => { - if (mode === 'edit') return formItem.allowEditing !== false - if (mode === 'new') return formItem.allowAdding !== false - return true - }).map((formItem, i) => { - return formItem.editorType2 === PlatformEditorTypes.dxTagBox ? ( - ( - { - const newData = { ...formDataRef.current, [formItem.dataField!]: e } - formDataRef.current = newData - setFormData(newData) - runEditorScript(formItem, e, formInstanceRef.current) - }} - editorOptions={getEditorOptions(formItem)} - > - )} - label={{ - text: translate('::' + formItem.colData?.captionName), - className: 'font-semibold', - }} - > - ) : formItem.editorType2 === PlatformEditorTypes.dxGridBox ? ( - ( - { - const newData = { ...formDataRef.current, [formItem.dataField!]: e } - formDataRef.current = newData - setFormData(newData) - runEditorScript(formItem, e, formInstanceRef.current) - }} - editorOptions={getEditorOptions(formItem)} - > - )} - label={{ - text: translate('::' + formItem.colData?.captionName), - className: 'font-semibold', - }} - > - ) : formItem.editorType2 === PlatformEditorTypes.dxImageUpload ? ( - ( - { - const newData = { ...formDataRef.current, [formItem.dataField!]: val } - formDataRef.current = newData - setFormData(newData) - runEditorScript(formItem, val, formInstanceRef.current) - }} - editorOptions={getEditorOptions(formItem)} - /> - )} - label={{ - text: translate('::' + formItem.colData?.captionName), - className: 'font-semibold', - }} - > - ) : ( - - ) - })} + {(formGroupItem.items as SimpleItemWithColData[]) + ?.filter((formItem) => { + if (mode === 'edit') return formItem.allowEditing !== false + if (mode === 'new') return formItem.allowAdding !== false + return true + }) + .map((formItem, i) => { + return formItem.editorType2 === PlatformEditorTypes.dxTagBox ? ( + ( + { + const newData = { ...formDataRef.current, [formItem.dataField!]: e } + formDataRef.current = newData + setFormData(newData) + runEditorScript(formItem, e, formInstanceRef.current) + }} + editorOptions={getEditorOptions(formItem)} + > + )} + label={{ + text: translate('::' + formItem.colData?.captionName), + className: 'font-semibold', + }} + > + ) : formItem.editorType2 === PlatformEditorTypes.dxGridBox ? ( + ( + { + const newData = { ...formDataRef.current, [formItem.dataField!]: e } + formDataRef.current = newData + setFormData(newData) + runEditorScript(formItem, e, formInstanceRef.current) + }} + editorOptions={getEditorOptions(formItem)} + > + )} + label={{ + text: translate('::' + formItem.colData?.captionName), + className: 'font-semibold', + }} + > + ) : formItem.editorType2 === PlatformEditorTypes.dxImageUpload ? ( + ( + { + const newData = { ...formDataRef.current, [formItem.dataField!]: val } + formDataRef.current = newData + setFormData(newData) + runEditorScript(formItem, val, formInstanceRef.current) + }} + editorOptions={getEditorOptions(formItem)} + /> + )} + label={{ + text: translate('::' + formItem.colData?.captionName), + className: 'font-semibold', + }} + > + ) : ( + + ) + })} ) })} diff --git a/ui/src/views/form/useFormData.tsx b/ui/src/views/form/useFormData.tsx index abae5e1..c187cb4 100644 --- a/ui/src/views/form/useFormData.tsx +++ b/ui/src/views/form/useFormData.tsx @@ -19,6 +19,13 @@ import { layoutTypes } from '../admin/listForm/edit/types' import { useListFormCustomDataSource } from '../list/useListFormCustomDataSource' import { useListFormColumns } from '../list/useListFormColumns' +const flattenFormItems = (items: any[] = []): SimpleItemWithColData[] => + items.flatMap((item) => [ + ...(item?.dataField ? [item] : []), + ...flattenFormItems(item?.items || []), + ...(item?.tabs || []).flatMap((tab: any) => flattenFormItems(tab?.items || [])), + ]) + const useGridData = (props: { mode: RowMode listFormCode: string @@ -41,6 +48,7 @@ const useGridData = (props: { const [permissionResults, setPermissionResults] = useState() const refForm = useRef(null) + const previousFormDataRef = useRef() const [searchParams] = useSearchParams() const navigate = useNavigate() const { translate } = useLocalization() @@ -306,18 +314,36 @@ const useGridData = (props: { setGridReady(true) }, [gridDto]) - // formData değiştiğinde sadece lookup datasource'ları güncelle + // formData değiştiğinde sadece etkilenen cascading lookup datasource'ları güncelle useEffect(() => { if (!gridDto || !formItems.length) { + previousFormDataRef.current = formData return } - // View mode'da formData olsa da olmasa da cascading alanlar için dataSource oluşturulmalı - const updatedItems = formItems.map((groupItem) => ({ - ...groupItem, - items: (groupItem.items as SimpleItemWithColData[])?.map((item) => { + const previousFormData = previousFormDataRef.current + const changedFields = previousFormData + ? Object.keys({ ...(previousFormData || {}), ...(formData || {}) }).filter( + (field) => !Object.is(previousFormData?.[field], formData?.[field]), + ) + : [] + + const shouldRefreshLookup = (item: SimpleItemWithColData) => { + const cascadeParentFields = item.colData?.lookupDto?.cascadeParentFields + ?.split(',') + .map((field: string) => field.trim()) + .filter(Boolean) + + return ( + !previousFormData || + cascadeParentFields?.some((field: string) => changedFields.includes(field)) + ) + } + + const updateItems = (items: any[] = []) => + items.map((item) => { const colData = gridDto.columnFormats.find((x) => x.fieldName === item.dataField) - if (colData?.lookupDto?.dataSourceType) { + if (colData?.lookupDto?.dataSourceType && shouldRefreshLookup(item)) { const currentDataSource = item.editorOptions?.dataSource const keepCustomDataSource = currentDataSource !== undefined && typeof currentDataSource?.load !== 'function' @@ -330,16 +356,55 @@ const useGridData = (props: { dataSource: keepCustomDataSource ? currentDataSource : getLookupDataSource(colData?.editorOptions, colData, formData || null), - valueExpr: item.editorOptions?.valueExpr ?? colData?.lookupDto?.valueExpr?.toLowerCase(), + valueExpr: + item.editorOptions?.valueExpr ?? colData?.lookupDto?.valueExpr?.toLowerCase(), displayExpr: item.editorOptions?.displayExpr ?? colData?.lookupDto?.displayExpr?.toLowerCase(), }, } } + + if (item?.items?.length) { + return { + ...item, + items: updateItems(item.items), + } + } + + if (item?.tabs?.length) { + return { + ...item, + tabs: item.tabs.map((tab: any) => ({ + ...tab, + items: updateItems(tab.items), + })), + } + } + return item - }), + }) + + const hasAffectedLookup = + !previousFormData || + formItems + .flatMap((group) => flattenFormItems([group])) + .some((item) => item.colData?.lookupDto?.dataSourceType && shouldRefreshLookup(item)) + + if (!hasAffectedLookup) { + previousFormDataRef.current = formData + return + } + + const updatedItems = formItems.map((groupItem) => ({ + ...groupItem, + items: updateItems(groupItem.items as any[]), + tabs: (groupItem as any).tabs?.map((tab: any) => ({ + ...tab, + items: updateItems(tab.items), + })), })) + previousFormDataRef.current = formData setFormItems(updatedItems) }, [formData, gridDto]) diff --git a/ui/src/views/list/Grid.tsx b/ui/src/views/list/Grid.tsx index 614d38a..bb0f1c3 100644 --- a/ui/src/views/list/Grid.tsx +++ b/ui/src/views/list/Grid.tsx @@ -239,19 +239,27 @@ const getValueByField = (data: Record = {}, field?: string) => { } const shouldRunEditorScriptOnContentReady = (script?: string) => - Boolean(script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly'))) + Boolean( + script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly')), + ) + +const isTouchLikeDevice = () => + typeof window !== 'undefined' && + (window.matchMedia?.('(pointer: coarse)').matches || window.matchMedia?.('(hover: none)').matches) const Grid = (props: GridProps) => { const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props const { translate } = useLocalization() const { smaller } = useResponsive() const currentUser = useStoreState((state) => state.auth.user) + const useMobileEditPopup = smaller.md || isTouchLikeDevice() const gridRef = useRef() const refListFormCode = useRef('') const widgetGroupRef = useRef(null) const editingFormDataRef = useRef>({}) const editingFormInstanceRef = useRef() + const lastEditingContentReadyScriptKeyRef = useRef() // Edit popup state kaydetmeyi engellemek için flag const isEditingRef = useRef(false) @@ -344,6 +352,29 @@ const Grid = (props: GridProps) => { const { createSelectDataSource } = useListFormCustomDataSource({ gridRef }) + const applyMobileEditorFocusGuard = useCallback( + (editorOptions: EditorOptionsWithButtons, dataField?: string) => { + if (!useMobileEditPopup || !dataField) { + return editorOptions + } + + const previousOnFocusIn = editorOptions.onFocusIn + + return { + ...editorOptions, + autoFocus: false, + focusStateEnabled: false, + selectTextOnFocus: false, + onFocusIn: (e: any) => { + if (typeof previousOnFocusIn === 'function') { + previousOnFocusIn(e) + } + }, + } + }, + [useMobileEditPopup], + ) + const openNotePanel = useCallback( (rowData: Record) => { const keyFieldName = gridDto?.gridOptions.keyFieldName @@ -728,6 +759,10 @@ const Grid = (props: GridProps) => { const onEditorPreparing = useCallback( (editor: DataGridTypes.EditorPreparingEvent) => { if (editor.parentType === 'dataRow' && editor.dataField && gridDto) { + if (isTouchLikeDevice()) { + editor.editorOptions = applyMobileEditorFocusGuard(editor.editorOptions, editor.dataField) + } + const formItem = gridDto.gridOptions.editingFormDto .flatMap((group) => flattenEditingFormItems([group])) .find((i) => i.dataField === editor.dataField) @@ -888,7 +923,13 @@ const Grid = (props: GridProps) => { } } }, - [gridDto, cascadeFieldsMap], + [ + gridDto, + cascadeFieldsMap, + parentToChildrenMap, + mode, + applyMobileEditorFocusGuard, + ], ) const customLoadState = useCallback(() => { @@ -1181,20 +1222,20 @@ const Grid = (props: GridProps) => { if (listFormField?.sourceDbType === DbTypeEnum.Date) { Object.assign(defaultEditorOptions, { - type: 'date', - dateSerializationFormat: 'yyyy-MM-dd', - displayFormat: 'shortDate', - }) + type: 'date', + dateSerializationFormat: 'yyyy-MM-dd', + displayFormat: 'shortDate', + }) } else if ( listFormField?.sourceDbType === DbTypeEnum.DateTime || listFormField?.sourceDbType === DbTypeEnum.DateTime2 || listFormField?.sourceDbType === DbTypeEnum.DateTimeOffset ) { Object.assign(defaultEditorOptions, { - type: 'datetime', - dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ss', - displayFormat: 'shortDateShortTime', - }) + type: 'datetime', + dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ss', + displayFormat: 'shortDateShortTime', + }) } // Her item'a placeholder olarak captionName ekle @@ -1217,6 +1258,8 @@ const Grid = (props: GridProps) => { ...forcedEditorOptions, } + editorOptions = applyMobileEditorFocusGuard(editorOptions, i.dataField) + if (editorOptions?.buttons) { editorOptions.buttons = (editorOptions?.buttons || []).map((btn: any) => { if (btn?.options?.onClick && typeof btn.options.onClick === 'string') { @@ -1261,7 +1304,7 @@ const Grid = (props: GridProps) => { return item }, - [gridDto, mode, searchParams, extraFilters], + [gridDto, mode, searchParams, extraFilters, applyMobileEditorFocusGuard], ) // WidgetGroup yüksekliğini hesapla @@ -1514,7 +1557,7 @@ const Grid = (props: GridProps) => { /> { showTitle: gridDto.gridOptions.editingOptionDto?.popup?.showTitle, hideOnOutsideClick: gridDto.gridOptions.editingOptionDto?.popup?.hideOnOutsideClick, - width: gridDto.gridOptions.editingOptionDto?.popup?.width, - height: gridDto.gridOptions.editingOptionDto?.popup?.height, - resizeEnabled: gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled, - fullScreen: isPopupFullScreen, + width: useMobileEditPopup + ? '100%' + : gridDto.gridOptions.editingOptionDto?.popup?.width, + height: useMobileEditPopup + ? '100dvh' + : gridDto.gridOptions.editingOptionDto?.popup?.height, + resizeEnabled: + !useMobileEditPopup && + gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled, + fullScreen: useMobileEditPopup || isPopupFullScreen, + dragEnabled: !useMobileEditPopup, + focusStateEnabled: !useMobileEditPopup, + restorePosition: !useMobileEditPopup, toolbarItems: [ { widget: 'dxButton', @@ -1581,6 +1633,7 @@ const Grid = (props: GridProps) => { }} form={{ colCount: 1, + focusStateEnabled: !useMobileEditPopup, onContentReady: (e) => { editingFormInstanceRef.current = e.component @@ -1601,15 +1654,27 @@ const Grid = (props: GridProps) => { } const runReadOnlyScripts = () => { - const editorValues = gridDto.gridOptions.editingFormDto - .flatMap((group) => flattenEditingFormItems([group])) - .reduce>((values, formItem) => { + const formItems = gridDto.gridOptions.editingFormDto.flatMap((group) => + flattenEditingFormItems([group]), + ) + const scriptItems = formItems.filter((formItem) => + shouldRunEditorScriptOnContentReady(formItem.editorScript), + ) + + if (!scriptItems.length) { + return + } + + const editorValues = formItems.reduce>( + (values, formItem) => { const editorInstance = form?.getEditor?.(formItem.dataField) if (editorInstance?.option) { values[formItem.dataField] = editorInstance.option('value') } return values - }, {}) + }, + {}, + ) const formData = { ...editingFormDataRef.current, ...(form?.option?.('formData') || {}), @@ -1617,38 +1682,47 @@ const Grid = (props: GridProps) => { } editingFormDataRef.current = { ...formData } - gridDto.gridOptions.editingFormDto - .flatMap((group) => flattenEditingFormItems([group])) - .filter((formItem) => - shouldRunEditorScriptOnContentReady(formItem.editorScript), - ) - .forEach((formItem) => { - try { - const editorInstance = form?.getEditor?.(formItem.dataField) - const editorValue = - editorInstance?.option?.('value') ?? - getValueByField(formData, formItem.dataField) - const editor = { - dataField: formItem.dataField, - component: grid, - } - const e = { - component: form, - dataField: formItem.dataField, - value: editorValue, - } + const scriptKey = `${mode}|${String(rowKey)}|${scriptItems + .map((formItem) => formItem.dataField) + .join('|')}|${JSON.stringify(formData)}` - executeEditorScript(formItem.editorScript!, { - formData, - e, - editor, - runtimeSetEditorReadOnly, - setFormData, - }) - } catch (err) { - console.error('Script exec error on contentReady', formItem.dataField, err) + if (lastEditingContentReadyScriptKeyRef.current === scriptKey) { + return + } + + lastEditingContentReadyScriptKeyRef.current = scriptKey + + scriptItems.forEach((formItem) => { + try { + const editorInstance = form?.getEditor?.(formItem.dataField) + const editorValue = + editorInstance?.option?.('value') ?? + getValueByField(formData, formItem.dataField) + const editor = { + dataField: formItem.dataField, + component: grid, } - }) + const e = { + component: form, + dataField: formItem.dataField, + value: editorValue, + } + + executeEditorScript(formItem.editorScript!, { + formData, + e, + editor, + runtimeSetEditorReadOnly, + setFormData, + }) + } catch (err) { + console.error( + 'Script exec error on contentReady', + formItem.dataField, + err, + ) + } + }) } runReadOnlyScripts() @@ -1704,7 +1778,6 @@ const Grid = (props: GridProps) => { console.error('Script exec error', err) } } - } }, items: diff --git a/ui/src/views/list/Tree.tsx b/ui/src/views/list/Tree.tsx index ef4ffa8..fdd1fd4 100644 --- a/ui/src/views/list/Tree.tsx +++ b/ui/src/views/list/Tree.tsx @@ -227,19 +227,27 @@ const getValueByField = (data: Record = {}, field?: string) => { } const shouldRunEditorScriptOnContentReady = (script?: string) => - Boolean(script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly'))) + Boolean( + script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly')), + ) + +const isTouchLikeDevice = () => + typeof window !== 'undefined' && + (window.matchMedia?.('(pointer: coarse)').matches || window.matchMedia?.('(hover: none)').matches) const Tree = (props: TreeProps) => { const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props const { translate } = useLocalization() const { smaller } = useResponsive() const currentUser = useStoreState((state) => state.auth.user) + const useMobileEditPopup = smaller.md || isTouchLikeDevice() const gridRef = useRef() const refListFormCode = useRef('') const widgetGroupRef = useRef(null) const editingFormDataRef = useRef>({}) const editingFormInstanceRef = useRef() + const lastEditingContentReadyScriptKeyRef = useRef() // Edit popup state kaydetmeyi engellemek için flag const isEditingRef = useRef(false) @@ -349,6 +357,29 @@ const Tree = (props: TreeProps) => { const { createSelectDataSource } = useListFormCustomDataSource({ gridRef }) + const applyMobileEditorFocusGuard = useCallback( + (editorOptions: EditorOptionsWithButtons, dataField?: string) => { + if (!useMobileEditPopup || !dataField) { + return editorOptions + } + + const previousOnFocusIn = editorOptions.onFocusIn + + return { + ...editorOptions, + autoFocus: false, + focusStateEnabled: false, + selectTextOnFocus: false, + onFocusIn: (e: any) => { + if (typeof previousOnFocusIn === 'function') { + previousOnFocusIn(e) + } + }, + } + }, + [useMobileEditPopup], + ) + const openNotePanel = useCallback( (rowData: Record) => { const keyFieldName = gridDto?.gridOptions.keyFieldName @@ -677,6 +708,10 @@ const Tree = (props: TreeProps) => { function onEditorPreparing(editor: TreeListTypes.EditorPreparingEvent) { if (editor.parentType === 'dataRow' && editor.dataField && gridDto) { + if (isTouchLikeDevice()) { + editor.editorOptions = applyMobileEditorFocusGuard(editor.editorOptions, editor.dataField) + } + const formItem = gridDto.gridOptions.editingFormDto .flatMap((group) => flattenEditingFormItems([group])) .find((i) => i.dataField === editor.dataField) @@ -1180,7 +1215,7 @@ const Tree = (props: TreeProps) => { { showTitle: gridDto.gridOptions.editingOptionDto?.popup?.showTitle, hideOnOutsideClick: gridDto.gridOptions.editingOptionDto?.popup?.hideOnOutsideClick, - width: gridDto.gridOptions.editingOptionDto?.popup?.width, - height: gridDto.gridOptions.editingOptionDto?.popup?.height, - resizeEnabled: gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled, - fullScreen: isPopupFullScreen, + width: useMobileEditPopup + ? '100%' + : gridDto.gridOptions.editingOptionDto?.popup?.width, + height: useMobileEditPopup + ? '100dvh' + : gridDto.gridOptions.editingOptionDto?.popup?.height, + resizeEnabled: + !useMobileEditPopup && + gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled, + fullScreen: useMobileEditPopup || isPopupFullScreen, + dragEnabled: !useMobileEditPopup, + focusStateEnabled: !useMobileEditPopup, + restorePosition: !useMobileEditPopup, toolbarItems: [ { widget: 'dxButton', @@ -1243,6 +1287,7 @@ const Tree = (props: TreeProps) => { }} form={{ colCount: 1, + focusStateEnabled: !useMobileEditPopup, onContentReady: (e) => { editingFormInstanceRef.current = e.component @@ -1263,15 +1308,27 @@ const Tree = (props: TreeProps) => { } const runReadOnlyScripts = () => { - const editorValues = gridDto.gridOptions.editingFormDto - .flatMap((group) => flattenEditingFormItems([group])) - .reduce>((values, formItem) => { + const formItems = gridDto.gridOptions.editingFormDto.flatMap((group) => + flattenEditingFormItems([group]), + ) + const scriptItems = formItems.filter((formItem) => + shouldRunEditorScriptOnContentReady(formItem.editorScript), + ) + + if (!scriptItems.length) { + return + } + + const editorValues = formItems.reduce>( + (values, formItem) => { const editorInstance = form?.getEditor?.(formItem.dataField) if (editorInstance?.option) { values[formItem.dataField] = editorInstance.option('value') } return values - }, {}) + }, + {}, + ) const formData = { ...editingFormDataRef.current, ...(form?.option?.('formData') || {}), @@ -1279,38 +1336,47 @@ const Tree = (props: TreeProps) => { } editingFormDataRef.current = { ...formData } - gridDto.gridOptions.editingFormDto - .flatMap((group) => flattenEditingFormItems([group])) - .filter((formItem) => - shouldRunEditorScriptOnContentReady(formItem.editorScript), - ) - .forEach((formItem) => { - try { - const editorInstance = form?.getEditor?.(formItem.dataField) - const editorValue = - editorInstance?.option?.('value') ?? - getValueByField(formData, formItem.dataField) - const editor = { - dataField: formItem.dataField, - component: grid, - } - const e = { - component: form, - dataField: formItem.dataField, - value: editorValue, - } + const scriptKey = `${mode}|${String(rowKey)}|${scriptItems + .map((formItem) => formItem.dataField) + .join('|')}|${JSON.stringify(formData)}` - executeEditorScript(formItem.editorScript!, { - formData, - e, - editor, - runtimeSetEditorReadOnly, - setFormData, - }) - } catch (err) { - console.error('Script exec error on contentReady', formItem.dataField, err) + if (lastEditingContentReadyScriptKeyRef.current === scriptKey) { + return + } + + lastEditingContentReadyScriptKeyRef.current = scriptKey + + scriptItems.forEach((formItem) => { + try { + const editorInstance = form?.getEditor?.(formItem.dataField) + const editorValue = + editorInstance?.option?.('value') ?? + getValueByField(formData, formItem.dataField) + const editor = { + dataField: formItem.dataField, + component: grid, } - }) + const e = { + component: form, + dataField: formItem.dataField, + value: editorValue, + } + + executeEditorScript(formItem.editorScript!, { + formData, + e, + editor, + runtimeSetEditorReadOnly, + setFormData, + }) + } catch (err) { + console.error( + 'Script exec error on contentReady', + formItem.dataField, + err, + ) + } + }) } runReadOnlyScripts() @@ -1366,7 +1432,6 @@ const Tree = (props: TreeProps) => { console.error('Script exec error', err) } } - } }, items: @@ -1388,7 +1453,9 @@ const Tree = (props: TreeProps) => { let parsedEditorOptions: EditorOptionsWithButtons = {} const forcedEditorOptions: EditorOptionsWithButtons = {} try { - parsedEditorOptions = i.editorOptions ? JSON.parse(i.editorOptions) : {} + parsedEditorOptions = i.editorOptions + ? JSON.parse(i.editorOptions) + : {} const rawFilter = searchParams?.get('filter') if (rawFilter) { @@ -1420,20 +1487,20 @@ const Tree = (props: TreeProps) => { if (listFormField?.sourceDbType === DbTypeEnum.Date) { Object.assign(defaultEditorOptions, { - type: 'date', - dateSerializationFormat: 'yyyy-MM-dd', - displayFormat: 'shortDate', - }) + type: 'date', + dateSerializationFormat: 'yyyy-MM-dd', + displayFormat: 'shortDate', + }) } else if ( listFormField?.sourceDbType === DbTypeEnum.DateTime || listFormField?.sourceDbType === DbTypeEnum.DateTime2 || listFormField?.sourceDbType === DbTypeEnum.DateTimeOffset ) { Object.assign(defaultEditorOptions, { - type: 'datetime', - dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ss', - displayFormat: 'shortDateShortTime', - }) + type: 'datetime', + dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ss', + displayFormat: 'shortDateShortTime', + }) } editorOptions = { @@ -1442,6 +1509,8 @@ const Tree = (props: TreeProps) => { ...forcedEditorOptions, } + editorOptions = applyMobileEditorFocusGuard(editorOptions, i.dataField) + if (editorOptions?.buttons) { editorOptions.buttons = (editorOptions?.buttons || []).map( (btn: any) => {
+ = ({ {previewData.headers.map((header: string, index: number) => ( {header}
@@ -230,7 +230,7 @@ export const ImportPreview: React.FC = ({ {row.map((cell, cellIndex) => ( {cell?.toString() || '-'}