From 9988cd11b0d27875b759b2e5914e883b351ef5ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:57:49 +0300 Subject: [PATCH] =?UTF-8?q?GanttView=20d=C3=BCzenlemeleri?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GridOptionsDto/GanttOptionDto.cs | 9 + .../Seeds/LanguagesData.json | 12 + ui/src/index.css | 3 +- ui/src/proxy/form/models.ts | 2 + .../admin/listForm/edit/FormTabGantt.tsx | 133 +++++++---- ui/src/views/admin/listForm/wizard/Wizard.tsx | 2 + .../admin/listForm/wizard/WizardStep9.tsx | 2 + .../developerKit/SqlTableDesignerDialog.tsx | 18 +- ui/src/views/list/GanttView.tsx | 217 +++++++++++++++++- ui/src/views/list/useListFormStateStoring.tsx | 2 +- 10 files changed, 335 insertions(+), 65 deletions(-) diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GanttOptionDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GanttOptionDto.cs index 01612c09..5bfd5cc7 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GanttOptionDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GanttOptionDto.cs @@ -76,6 +76,10 @@ public class GanttOptionDto /// public bool AllowTaskUpdating { get; set; } = false; /// + /// Task kaynaklarının güncellenmesine izin verilsin mi + /// + public bool AllowTaskResourceUpdating { get; set; } = false; + /// /// Task silmeye izin verilsin mi /// public bool AllowTaskDeleting { get; set; } = false; @@ -94,6 +98,11 @@ public class GanttOptionDto /// public bool AllowResourceAdding { get; set; } = false; + /// + /// Kaynak güncellemeye izin verilsin mi + /// + public bool AllowResourceUpdating { get; set; } = false; + /// /// Kaynaklar için kullanılan field adı (örn: "resources") /// diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 82f1292d..08d5e1a0 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -5562,6 +5562,12 @@ "en": "Allow Task Updating", "tr": "Görev Güncelleme İzni" }, + { + "resourceName": "Platform", + "key": "ListForms.ListFormEdit.AllowTaskResourceUpdating", + "en": "Allow Task Resource Updating", + "tr": "Görev Kaynaklarını Güncelleme İzni" + }, { "resourceName": "Platform", "key": "ListForms.ListFormEdit.AllowDependencyAdding", @@ -5580,6 +5586,12 @@ "en": "Allow Resource Adding", "tr": "Kaynak Ekleme İzni" }, + { + "resourceName": "Platform", + "key": "ListForms.ListFormEdit.AllowResourceUpdating", + "en": "Allow Resource Updating", + "tr": "Kaynak Güncelleme İzni" + }, { "resourceName": "Platform", "key": "ListForms.ListFormEdit.AllowResourceDeleting", diff --git a/ui/src/index.css b/ui/src/index.css index e6372fec..debe8c5f 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -289,7 +289,8 @@ div.dialog-after-open > div.dialog-content.maximized { } .dx-datagrid-header-panel > .dx-toolbar .dx-toolbar-after .dx-button-text, -.dx-treelist-header-panel > .dx-toolbar .dx-toolbar-after .dx-button-text { +.dx-treelist-header-panel > .dx-toolbar .dx-toolbar-after .dx-button-text, +.dx-gantt-toolbar-wrapper .dx-button-text { text-transform: none !important; } diff --git a/ui/src/proxy/form/models.ts b/ui/src/proxy/form/models.ts index 78283768..b0db7af8 100644 --- a/ui/src/proxy/form/models.ts +++ b/ui/src/proxy/form/models.ts @@ -420,10 +420,12 @@ export interface GanttOptionDto { allowEditing?: boolean allowTaskAdding?: boolean allowTaskUpdating?: boolean + allowTaskResourceUpdating?: boolean allowTaskDeleting?: boolean allowDependencyAdding?: boolean allowDependencyDeleting?: boolean allowResourceAdding?: boolean + allowResourceUpdating?: boolean allowResourceDeleting?: boolean } diff --git a/ui/src/views/admin/listForm/edit/FormTabGantt.tsx b/ui/src/views/admin/listForm/edit/FormTabGantt.tsx index 9b56031e..2ac75a2f 100644 --- a/ui/src/views/admin/listForm/edit/FormTabGantt.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabGantt.tsx @@ -361,21 +361,6 @@ function FormTabGantt(props: FormEditProps) { /> - - - - - - - - -
- - - -
+
+ + + + + + + + + + + + + + + + + + + +
- diff --git a/ui/src/views/admin/listForm/wizard/Wizard.tsx b/ui/src/views/admin/listForm/wizard/Wizard.tsx index 9fbb945d..9a1e4348 100644 --- a/ui/src/views/admin/listForm/wizard/Wizard.tsx +++ b/ui/src/views/admin/listForm/wizard/Wizard.tsx @@ -93,10 +93,12 @@ const initialValues: ListFormWizardDto = { allowEditing: false, allowTaskAdding: false, allowTaskUpdating: false, + allowTaskResourceUpdating: false, allowTaskDeleting: false, allowDependencyAdding: false, allowDependencyDeleting: false, allowResourceAdding: false, + allowResourceUpdating: false, allowResourceDeleting: false, }, schedulerOptionDto: { diff --git a/ui/src/views/admin/listForm/wizard/WizardStep9.tsx b/ui/src/views/admin/listForm/wizard/WizardStep9.tsx index c93efe90..ec6d7241 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStep9.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStep9.tsx @@ -41,7 +41,9 @@ const WizardStep9 = ({ selectCommandColumns, translate, onBack, onNext }: Wizard ['allowDependencyAdding', '::ListForms.ListFormEdit.AllowDependencyAdding'], ['allowDependencyDeleting', '::ListForms.ListFormEdit.AllowDependencyDeleting'], ['allowResourceAdding', '::ListForms.ListFormEdit.AllowResourceAdding'], + ['allowResourceUpdating', '::ListForms.ListFormEdit.AllowResourceUpdating'], ['allowResourceDeleting', '::ListForms.ListFormEdit.AllowResourceDeleting'], + ['allowTaskResourceUpdating', '::ListForms.ListFormEdit.AllowTaskResourceUpdating'], ] as const return ( diff --git a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx index a04a1c75..de86a530 100644 --- a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx +++ b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx @@ -382,15 +382,6 @@ const GANTT_COLUMNS: ColumnDefinition[] = [ defaultValue: 'NEWID()', description: 'Gantt task primary key', }, - { - id: '__GanttParentId', - columnName: 'ParentId', - dataType: 'uniqueidentifier', - maxLength: '', - isNullable: true, - defaultValue: '', - description: 'Parent Gantt task ID', - }, { id: '__GanttTitle', columnName: 'Title', @@ -400,6 +391,15 @@ const GANTT_COLUMNS: ColumnDefinition[] = [ defaultValue: '', description: 'Gantt task title', }, + { + id: '__GanttParentId', + columnName: 'ParentId', + dataType: 'uniqueidentifier', + maxLength: '', + isNullable: true, + defaultValue: '', + description: 'Parent Gantt task ID', + }, { id: '__GanttStartDate', columnName: 'StartDate', diff --git a/ui/src/views/list/GanttView.tsx b/ui/src/views/list/GanttView.tsx index 80401186..47ba13b5 100644 --- a/ui/src/views/list/GanttView.tsx +++ b/ui/src/views/list/GanttView.tsx @@ -1,6 +1,6 @@ import Container from '@/components/shared/Container' import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant' -import { GridDto } from '@/proxy/form/models' +import { GridDto, ListFormCustomizationTypeEnum } from '@/proxy/form/models' import { useLocalization } from '@/utils/hooks/useLocalization' import Gantt, { Column, @@ -24,12 +24,24 @@ import { layoutTypes } from '../admin/listForm/edit/types' import WidgetGroup from '@/components/ui/Widget/WidgetGroup' import { ROUTES_ENUM } from '@/routes/route.constant' import { usePWA } from '@/utils/hooks/usePWA' -import type { GanttScaleType } from 'devextreme/ui/gantt' +import type { ContentReadyEvent, GanttScaleType } from 'devextreme/ui/gantt' import { useListFormColumns } from './useListFormColumns' import CustomStore from 'devextreme/data/custom_store' import { GridColumnData } from './GridColumnData' import { Loading } from '@/components/shared' import { usePermission } from '@/utils/hooks/usePermission' +import { useListFormStateStoring } from './useListFormStateStoring' +import TreeList from 'devextreme/ui/tree_list' +import { postListFormCustomization } from '@/services/list-form-customization.service' +import { useFilters } from './useFilters' +import GridFilterDialogs from './GridFilterDialogs' + +type GanttInstance = ReturnType + +const getGanttTreeList = (gantt: GanttInstance) => { + const element = (gantt.element() as HTMLElement).querySelector('.dx-treelist') + return element ? TreeList.getInstance(element as HTMLElement) : undefined +} interface GanttViewProps { listFormCode: string @@ -55,7 +67,30 @@ const GanttView = (props: GanttViewProps) => { const [gridDto, setGridDto] = useState() const [widgetGroupHeight, setWidgetGroupHeight] = useState(0) const [scaleType, setScaleType] = useState('weeks') + const [taskListWidth, setTaskListWidth] = useState(500) const layout = layoutTypes.gantt || 'gantt' + const pendingStateRef = useRef>() + const loadedStateKeyRef = useRef('') + + const { customSaveState, customLoadState, storageKey } = useListFormStateStoring({ + listFormCode, + storageKey: gridDto?.gridOptions.stateStoringDto?.storageKey, + filterPrefix: 'gantt', + }) + const ganttFilterRef = useRef({ + instance: () => { + const gantt = gridRef.current?.instance() + return gantt ? getGanttTreeList(gantt) : undefined + }, + }) + const { filterToolbarData, ...filterData } = useFilters({ + gridDto, + gridRef: ganttFilterRef, + listFormCode, + }) + const ganttFilterToolbarData = filterToolbarData.filter((item) => + ['saveActiveFilter', 'deleteFilter', 'clearFilter'].includes(item.name ?? ''), + ) useEffect(() => { const initializeGantt = async () => { @@ -92,7 +127,6 @@ const GanttView = (props: GanttViewProps) => { isSubForm, gridRef, }) - useEffect(() => { if (!gridDto) { return @@ -164,12 +198,14 @@ const GanttView = (props: GanttViewProps) => { const getSettingButtonOptions = useCallback( () => ({ icon: 'preferences', + text: translate('::ListForms.ListForm.Manage'), + hint: translate('::ListForms.ListForm.Manage'), stylingMode: 'icon', onClick: () => { settingButtonClick() }, }), - [settingButtonClick], + [settingButtonClick, translate], ) const getRefreshButtonOptions = useCallback( @@ -184,6 +220,124 @@ const GanttView = (props: GanttViewProps) => { [settingButtonClick], ) + const getCurrentState = useCallback((gantt: GanttInstance) => { + const treeList = getGanttTreeList(gantt) + const state = (treeList?.state() ?? {}) as Record + const visibleColumns = (treeList?.getVisibleColumns() ?? []) as Array> + const headerCells = Array.from( + (treeList?.element() as HTMLElement | undefined)?.querySelectorAll('.dx-header-row > td') ?? + [], + ) + const widths = new Map( + visibleColumns.map((column, index) => [ + column.dataField, + headerCells[index]?.getBoundingClientRect().width, + ]), + ) + const taskListElement = (gantt.element() as HTMLElement).querySelector( + '.dx-gantt-treelist-wrapper', + ) + + return { + ...state, + columns: state.columns?.map((column: Record) => { + const width = widths.get(column.dataField) + return width ? { ...column, width: Math.round(width) } : column + }), + scaleType: gantt.option('scaleType'), + showDependencies: gantt.option('showDependencies'), + showResources: gantt.option('showResources'), + taskListWidth: Math.round( + taskListElement?.getBoundingClientRect().width ?? gantt.option('taskListWidth'), + ), + } + }, []) + + const applyState = useCallback((gantt: GanttInstance, state: Record) => { + const treeList = getGanttTreeList(gantt) + if (!treeList) return + + const { + scaleType: savedScaleType, + showDependencies, + showResources, + taskListWidth: savedTaskListWidth, + treeListState, + ...treeState + } = state + + treeList.state(treeListState ?? treeState) + if (savedScaleType) setScaleType(savedScaleType) + if (savedTaskListWidth) setTaskListWidth(savedTaskListWidth) + gantt.option({ + ...(showDependencies !== undefined ? { showDependencies } : {}), + ...(showResources !== undefined ? { showResources } : {}), + ...(savedTaskListWidth ? { taskListWidth: savedTaskListWidth } : {}), + }) + }, []) + + const handleGanttContentReady = useCallback( + (event: ContentReadyEvent) => { + if (!pendingStateRef.current) return + + const state = pendingStateRef.current + pendingStateRef.current = undefined + requestAnimationFrame(() => requestAnimationFrame(() => applyState(event.component, state))) + }, + [applyState], + ) + + const saveGanttState = useCallback(() => { + const gantt = gridRef.current?.instance() + if (gantt) customSaveState(getCurrentState(gantt)).catch(() => undefined) + }, [customSaveState, getCurrentState]) + + const resetGanttState = useCallback(async () => { + const gantt = gridRef.current?.instance() + if (!gantt || !storageKey) return + + await postListFormCustomization({ + listFormCode, + customizationType: ListFormCustomizationTypeEnum.GridState, + filterName: `gantt-${storageKey}`, + customizationData: '', + }) + getGanttTreeList(gantt)?.state(null) + setTaskListWidth(500) + }, [listFormCode, storageKey]) + + useEffect(() => { + if ( + !gridDto?.gridOptions.stateStoringDto?.enabled || + !ganttDataSource || + !columnData || + !storageKey + ) + return + + const key = `${listFormCode}-${storageKey}` + if (loadedStateKeyRef.current === key) return + loadedStateKeyRef.current = key + + customLoadState() + .then((state) => { + pendingStateRef.current = state ?? undefined + const gantt = gridRef.current?.instance() + if (gantt && state) handleGanttContentReady({ component: gantt } as ContentReadyEvent) + }) + .catch(() => { + loadedStateKeyRef.current = '' + }) + }, [ + columnData, + customLoadState, + ganttDataSource, + gridDto, + handleGanttContentReady, + listFormCode, + storageKey, + ]) + return ( <>
0 ? 'mt-2' : ''}> @@ -217,7 +371,7 @@ const GanttView = (props: GanttViewProps) => { ref={gridRef as any} key={`Gantt-${listFormCode}-${ganttDataSource ? 'loaded' : 'loading'}`} id={'Gantt-' + listFormCode} - taskListWidth={500} + taskListWidth={taskListWidth} scaleType={scaleType} rootValue={ gridDto.gridOptions.ganttOptionDto?.rootValue === '' || @@ -232,6 +386,7 @@ const GanttView = (props: GanttViewProps) => { ? `calc(100vh - ${170 + widgetGroupHeight}px)` : undefined } + onContentReady={handleGanttContentReady} > { showText="always" options={getRefreshButtonOptions()} /> + {ganttFilterToolbarData.map((item) => ( + + ))} + {gridDto.gridOptions.stateStoringDto?.enabled && ( + + )} + {gridDto.gridOptions.stateStoringDto?.enabled && ( + + )} {checkPermission('App.Listforms.Listform.Update') && ( { /> )} - + - + @@ -324,6 +522,11 @@ const GanttView = (props: GanttViewProps) => { ))} +
)} diff --git a/ui/src/views/list/useListFormStateStoring.tsx b/ui/src/views/list/useListFormStateStoring.tsx index ad110f56..6a379ffe 100644 --- a/ui/src/views/list/useListFormStateStoring.tsx +++ b/ui/src/views/list/useListFormStateStoring.tsx @@ -20,7 +20,7 @@ type PendingSave = { type UseListFormStateStoringParams = { listFormCode: string storageKey?: string | null - filterPrefix: 'list' | 'tree' | 'pivot' | 'todo' + filterPrefix: 'list' | 'tree' | 'pivot' | 'todo' | 'gantt' skipSaveRef?: MutableRefObject showSaveToast?: boolean saveDebounceMs?: number