import Container from '@/components/shared/Container' import { DX_CLASSNAMES } from '@/constants/app.constant' import { GridDto, ListFormCustomizationTypeEnum } from '@/proxy/form/models' import { useLocalization } from '@/utils/hooks/useLocalization' import Gantt, { Column, ContextMenu, Editing, FilterRow, type GanttRef, HeaderFilter, Item, Sorting, Tasks, Toolbar, Validation, } from 'devextreme-react/gantt' import { useCallback, useEffect, useRef, useState } from 'react' import PageTitle from '@/components/shared/PageTitle' import { useStylesheet } from '@/utils/hooks/useStylesheet' import { useListFormCustomDataSource } from './useListFormCustomDataSource' 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 { 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 { useListFormCustomSources, useListFormGridDto, useWidgetGroupHeight, } from './shared/hooks' import GridFilterDialogs from './GridFilterDialogs' type GanttInstance = ReturnType type GanttTreeListInstance = { element: () => HTMLElement getVisibleColumns: () => Array> state: { (): Record (value: Record | null): void } } const getGanttTreeList = (gantt: GanttInstance) => { const element = (gantt.element() as HTMLElement).querySelector('.dx-treelist') return element ? (TreeList.getInstance(element as HTMLElement) as unknown as GanttTreeListInstance) : undefined } interface GanttViewProps { listFormCode: string searchParams?: URLSearchParams isSubForm?: boolean level?: number refreshData?: () => Promise gridDto?: GridDto } const GanttView = (props: GanttViewProps) => { const { listFormCode, searchParams, isSubForm, gridDto: extGridDto } = props const { translate } = useLocalization() const isPwaMode = usePWA() // Eskiden Helmet'in çocuğuydu; koşul aynen korunuyor. useStylesheet(isSubForm ? null : '/css/gantt/dx-gantt.min.css', 'dx-gantt-css') const gridRef = useRef(undefined) const widgetGroupRef = useRef(null) const { checkPermission } = usePermission() const [ganttDataSource, setGanttDataSource] = useState>() const [columnData, setColumnData] = useState() const gridDto = useListFormGridDto(listFormCode, extGridDto) const [scaleType, setScaleType] = useState('weeks') const [taskListWidth, setTaskListWidth] = useState(500) const layout = layoutTypes.gantt || 'gantt' const pendingStateRef = useRef | undefined>(undefined) const loadedStateKeyRef = useRef('') const widgetGroupHeight = useWidgetGroupHeight(widgetGroupRef, gridDto?.widgets) useListFormCustomSources(gridDto) 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(() => { setScaleType(gridDto?.gridOptions.ganttOptionDto?.scaleType || 'weeks') }, [gridDto]) // listFormCode değişiminde eski veri kaynağını temizle. useEffect(() => { gridRef.current?.instance()?.option('dataSource', undefined) }, [listFormCode]) const { createSelectDataSource } = useListFormCustomDataSource({ gridRef }) const { getBandedColumns } = useListFormColumns({ gridDto, listFormCode, isSubForm, gridRef, }) useEffect(() => { if (!gridDto) return const cols = getBandedColumns() setColumnData(cols) const dataSource = createSelectDataSource( gridDto.gridOptions, listFormCode, searchParams, layout, undefined, ) setGanttDataSource(dataSource) }, [createSelectDataSource, getBandedColumns, gridDto, layout, listFormCode, searchParams]) const settingButtonClick = useCallback(() => { window.open( ROUTES_ENUM.protected.saas.listFormManagement.edit.replace(':listFormCode', listFormCode), isPwaMode ? '_self' : '_blank', ) }, [isPwaMode, listFormCode]) const getSettingButtonOptions = useCallback( () => ({ icon: 'preferences', text: translate('::ListForms.ListForm.Manage'), hint: translate('::ListForms.ListForm.Manage'), stylingMode: 'icon', onClick: () => { settingButtonClick() }, }), [settingButtonClick, translate], ) const getRefreshButtonOptions = useCallback( () => ({ icon: 'refresh', text: translate('::ListForms.ListForm.Refresh'), stylingMode: 'icon', onClick: () => { gridRef.current?.instance()?.refresh() }, }), [translate], ) 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') ?? 500, ), } }, []) 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' : ''}>
{!isSubForm && } {!gridDto && (
Loading gantt configuration...
)} {gridDto && !ganttDataSource && (
Loading data source...
)} {gridDto && columnData && ganttDataSource && ( <>
0 ? gridDto.gridOptions.height : gridDto.gridOptions.fullHeight ? `calc(100vh - ${170 + widgetGroupHeight}px)` : undefined } onContentReady={handleGanttContentReady} > {gridDto.gridOptions.ganttOptionDto?.allowTaskAdding && ( )} {gridDto.gridOptions.ganttOptionDto?.allowTaskDeleting && ( )} setScaleType(e.value), }} /> {ganttFilterToolbarData.map((item) => ( ))} {gridDto.gridOptions.stateStoringDto?.enabled && ( )} {gridDto.gridOptions.stateStoringDto?.enabled && ( )} {checkPermission('App.Listforms.Listform.Update') && ( )} {columnData .filter((col) => col.type != 'buttons') .map((col: any) => ( ))}
)}
) } export default GanttView