Gantt düzenlemesi 2

This commit is contained in:
Sedat ÖZTÜRK 2025-12-02 12:28:54 +03:00
parent 7cf448ec13
commit 5a4244dee3
8 changed files with 169 additions and 141 deletions

View file

@ -2124,9 +2124,10 @@ public class ListFormSeeder_Project : IDataSeedContributor, ITransientDependency
new() { new() {
Order=1, ColCount=2, ColSpan=1, ItemType="group", Items =[ Order=1, ColCount=2, ColSpan=1, ItemType="group", Items =[
new EditingFormItemDto { Order = 1, DataField="Name", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox }, new EditingFormItemDto { Order = 1, DataField="Name", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
new EditingFormItemDto { Order = 2, DataField="Description", ColSpan = 2, EditorType2 = EditorTypes.dxTextArea }, new EditingFormItemDto { Order = 2, DataField="Progress", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox },
new EditingFormItemDto { Order = 3, DataField="StartDate", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxDateBox, EditorOptions = EditorOptionValues.DateTimeFormat }, new EditingFormItemDto { Order = 3, DataField="StartDate", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxDateBox, EditorOptions = EditorOptionValues.DateFormat },
new EditingFormItemDto { Order = 4, DataField="EndDate", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxDateBox, EditorOptions = EditorOptionValues.DateTimeFormat }, new EditingFormItemDto { Order = 4, DataField="EndDate", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxDateBox, EditorOptions = EditorOptionValues.DateFormat },
new EditingFormItemDto { Order = 5, DataField="Description", ColSpan = 2, EditorType2 = EditorTypes.dxTextArea },
]} ]}
}), }),
}, autoSave: true }, autoSave: true
@ -2209,9 +2210,10 @@ public class ListFormSeeder_Project : IDataSeedContributor, ITransientDependency
new() { new() {
ListFormCode = listForm.ListFormCode, ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En, CultureName = LanguageCodes.En,
SourceDbType = DbType.DateTime, SourceDbType = DbType.Date,
FieldName = "StartDate", FieldName = "StartDate",
Width = 150, Alignment = "center",
Width = 100,
ListOrderNo = 5, ListOrderNo = 5,
Visible = true, Visible = true,
IsActive = true, IsActive = true,
@ -2225,9 +2227,10 @@ public class ListFormSeeder_Project : IDataSeedContributor, ITransientDependency
new() { new() {
ListFormCode = listForm.ListFormCode, ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En, CultureName = LanguageCodes.En,
SourceDbType = DbType.DateTime, SourceDbType = DbType.Date,
FieldName = "EndDate", FieldName = "EndDate",
Width = 150, Alignment = "center",
Width = 100,
ListOrderNo = 6, ListOrderNo = 6,
Visible = true, Visible = true,
IsActive = true, IsActive = true,
@ -2243,7 +2246,9 @@ public class ListFormSeeder_Project : IDataSeedContributor, ITransientDependency
CultureName = LanguageCodes.En, CultureName = LanguageCodes.En,
SourceDbType = DbType.Decimal, SourceDbType = DbType.Decimal,
FieldName = "Progress", FieldName = "Progress",
Width = 100, Format = "fixedPoint",
Alignment = "right",
Width = 70,
ListOrderNo = 7, ListOrderNo = 7,
Visible = true, Visible = true,
IsActive = true, IsActive = true,

View file

@ -90,7 +90,16 @@ function ChartTabCommonSettings(props: FormEditProps) {
} }
errorMessage={errors.commonDto?.containerBackgroundColor} errorMessage={errors.commonDto?.containerBackgroundColor}
> >
<Field name="commonDto.containerBackgroundColor" component={Input} /> <Field name="commonDto.containerBackgroundColor">
{({ field, form }: FieldProps) => (
<Input
{...field}
type="color"
className='!h-10 !p-0 !m-0 !border-0'
onChange={(e) => form.setFieldValue(field.name, e.target.value)}
/>
)}
</Field>
</FormItem> </FormItem>
<FormItem <FormItem

View file

@ -283,20 +283,6 @@ function FormTabDetails(
)} )}
<div className="flex gap-2"> <div className="flex gap-2">
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout')}
invalid={errors.layoutDto?.gantt && touched.layoutDto?.gantt}
errorMessage={errors.layoutDto?.gantt}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.gantt"
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout')}
component={Checkbox}
/>
</FormItem>
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GridLayout')} label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GridLayout')}
invalid={errors.layoutDto?.grid && touched.layoutDto?.grid} invalid={errors.layoutDto?.grid && touched.layoutDto?.grid}
@ -366,6 +352,20 @@ function FormTabDetails(
component={Checkbox} component={Checkbox}
/> />
</FormItem> </FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout')}
invalid={errors.layoutDto?.gantt && touched.layoutDto?.gantt}
errorMessage={errors.layoutDto?.gantt}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.gantt"
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout')}
component={Checkbox}
/>
</FormItem>
</div> </div>
<FormItem <FormItem

View file

@ -1,28 +1,34 @@
import Container from '@/components/shared/Container' import Container from '@/components/shared/Container'
import { Dialog, Notification, toast } from '@/components/ui'
import { DX_CLASSNAMES } from '@/constants/app.constant' import { DX_CLASSNAMES } from '@/constants/app.constant'
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 useResponsive from '@/utils/hooks/useResponsive'
import Gantt, { import Gantt, {
Column, Column,
ContextMenu,
Editing, Editing,
FilterRow,
GanttRef, GanttRef,
HeaderFilter,
Item, Item,
Sorting,
Tasks, Tasks,
Toolbar, Toolbar,
Validation, Validation,
} from 'devextreme-react/gantt' } from 'devextreme-react/gantt'
import DataSource from 'devextreme/data/data_source'
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { getList } from '@/services/form.service' import { getList } from '@/services/form.service'
import { useListFormCustomDataSource } from './useListFormCustomDataSource' import { useListFormCustomDataSource } from './useListFormCustomDataSource'
import { useToolbar } from './useToolbar'
import { useFilters } from './useFilters'
import { addCss, addJs } from './Utils' import { addCss, addJs } from './Utils'
import { layoutTypes } from '../admin/listForm/edit/types' import { layoutTypes } from '../admin/listForm/edit/types'
import WidgetGroup from '@/components/ui/Widget/WidgetGroup' import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { usePWA } from '@/utils/hooks/usePWA'
import { 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'
interface GanttViewProps { interface GanttViewProps {
listFormCode: string listFormCode: string
@ -36,15 +42,18 @@ interface GanttViewProps {
const GanttView = (props: GanttViewProps) => { const GanttView = (props: GanttViewProps) => {
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
const { translate } = useLocalization() const { translate } = useLocalization()
const { smaller } = useResponsive() const isPwaMode = usePWA()
const ganttRef = useRef<GanttRef>() const gridRef = useRef<GanttRef>()
const refListFormCode = useRef('') const refListFormCode = useRef('')
const widgetGroupRef = useRef<HTMLDivElement>(null) const widgetGroupRef = useRef<HTMLDivElement>(null)
const [ganttDataSource, setGanttDataSource] = useState<DataSource<any, any>>() const [ganttDataSource, setGanttDataSource] = useState<CustomStore<any, any>>()
const [columnData, setColumnData] = useState<GridColumnData[]>()
const [gridDto, setGridDto] = useState<GridDto>() const [gridDto, setGridDto] = useState<GridDto>()
const [widgetGroupHeight, setWidgetGroupHeight] = useState(0) const [widgetGroupHeight, setWidgetGroupHeight] = useState(0)
const [scaleType, setScaleType] = useState<GanttScaleType>('weeks')
const layout = layoutTypes.gantt || 'gantt'
useEffect(() => { useEffect(() => {
const initializeGantt = async () => { const initializeGantt = async () => {
@ -59,44 +68,9 @@ const GanttView = (props: GanttViewProps) => {
} }
}, [listFormCode, extGridDto]) }, [listFormCode, extGridDto])
const layout = layoutTypes.gantt || 'gantt'
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
gridDto,
listFormCode,
getSelectedRowKeys: () => Promise.resolve([]),
getSelectedRowsData: () => [],
refreshData,
getFilter: () => undefined,
layout,
})
const { filterToolbarData, ...filterData } = useFilters({
gridDto,
gridRef: ganttRef as any,
listFormCode,
})
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef: ganttRef as any })
function refreshData() {
ganttRef.current?.instance()?.refresh()
}
function onTaskInserted() {
props.refreshData?.()
}
function onTaskUpdated() {
props.refreshData?.()
}
function onTaskDeleted() {
props.refreshData?.()
}
useEffect(() => { useEffect(() => {
if (ganttRef?.current) { if (gridRef?.current) {
const instance = ganttRef?.current?.instance() const instance = gridRef?.current?.instance()
if (instance) { if (instance) {
instance.option('dataSource', undefined) instance.option('dataSource', undefined)
} }
@ -107,6 +81,14 @@ const GanttView = (props: GanttViewProps) => {
} }
}, [listFormCode]) }, [listFormCode])
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
const { getBandedColumns } = useListFormColumns({
gridDto,
listFormCode,
isSubForm,
gridRef,
})
useEffect(() => { useEffect(() => {
if (!gridDto) { if (!gridDto) {
return return
@ -129,7 +111,10 @@ const GanttView = (props: GanttViewProps) => {
useEffect(() => { useEffect(() => {
if (!gridDto) return if (!gridDto) return
const customStore = createSelectDataSource( const cols = getBandedColumns()
setColumnData(cols)
const dataSource = createSelectDataSource(
gridDto.gridOptions, gridDto.gridOptions,
listFormCode, listFormCode,
searchParams, searchParams,
@ -137,12 +122,6 @@ const GanttView = (props: GanttViewProps) => {
undefined, undefined,
) )
// Gantt için DataSource wrapper'ı oluştur
const dataSource = new DataSource({
store: customStore,
reshapeOnPush: true,
})
setGanttDataSource(dataSource) setGanttDataSource(dataSource)
}, [gridDto, searchParams, createSelectDataSource]) }, [gridDto, searchParams, createSelectDataSource])
@ -171,26 +150,35 @@ const GanttView = (props: GanttViewProps) => {
} }
}, [gridDto?.widgets]) }, [gridDto?.widgets])
// Gantt için sütunları oluştur const settingButtonClick = useCallback(() => {
const getGanttColumns = useCallback(() => { window.open(
if (!gridDto?.columnFormats) return [] ROUTES_ENUM.protected.saas.listFormManagement.edit.replace(':listFormCode', listFormCode),
isPwaMode ? '_self' : '_blank',
)
}, [])
return gridDto.columnFormats const getSettingButtonOptions = useCallback(
.filter((col) => col.canRead && col.isActive && col.visible) () => ({
.map((col) => { icon: 'preferences',
const column: any = { stylingMode: 'icon',
dataField: col.fieldName, onClick: () => {
caption: col.captionName ? translate('::' + col.captionName) : col.fieldName, settingButtonClick()
width: col.width > 0 ? col.width : undefined, },
alignment: col.alignment, }),
format: col.format, [settingButtonClick],
} )
return column const getRefreshButtonOptions = useCallback(
}) () => ({
}, [gridDto, translate]) icon: 'refresh',
text: translate('::ListForms.ListForm.Refresh'),
const ganttColumns = getGanttColumns() stylingMode: 'icon',
onClick: () => {
gridRef.current?.instance()?.refresh()
},
}),
[settingButtonClick],
)
return ( return (
<> <>
@ -208,17 +196,25 @@ const GanttView = (props: GanttViewProps) => {
<link rel="stylesheet" href="/css/gantt/dx-gantt.min.css" /> <link rel="stylesheet" href="/css/gantt/dx-gantt.min.css" />
</Helmet> </Helmet>
)} )}
{!gridDto && <div className="p-4">Loading gantt configuration...</div>} {!gridDto && (
{gridDto && !ganttDataSource && <div className="p-4">Loading data source...</div>} <div className="p-4">
{gridDto && ganttDataSource && ( <Loading loading>Loading gantt configuration...</Loading>
</div>
)}
{gridDto && !ganttDataSource && (
<div className="p-4">
<Loading loading>Loading data source...</Loading>
</div>
)}
{gridDto && columnData && ganttDataSource && (
<> <>
<div className="p-1"> <div className="p-1">
<Gantt <Gantt
ref={ganttRef as any} ref={gridRef as any}
key={`Gantt-${listFormCode}-${ganttDataSource ? 'loaded' : 'loading'}`} key={`Gantt-${listFormCode}-${ganttDataSource ? 'loaded' : 'loading'}`}
id={'Gantt-' + listFormCode} id={'Gantt-' + listFormCode}
taskListWidth={500} taskListWidth={500}
scaleType="weeks" scaleType={scaleType}
rootValue={ rootValue={
gridDto.gridOptions.treeOptionDto?.rootValue === '' || gridDto.gridOptions.treeOptionDto?.rootValue === '' ||
gridDto.gridOptions.treeOptionDto?.rootValue === undefined gridDto.gridOptions.treeOptionDto?.rootValue === undefined
@ -230,13 +226,8 @@ const GanttView = (props: GanttViewProps) => {
? gridDto.gridOptions.height ? gridDto.gridOptions.height
: gridDto.gridOptions.fullHeight : gridDto.gridOptions.fullHeight
? `calc(100vh - ${170 + widgetGroupHeight}px)` ? `calc(100vh - ${170 + widgetGroupHeight}px)`
: 700 : undefined
} }
showResources={true}
showDependencies={true}
onTaskInserted={onTaskInserted}
onTaskUpdated={onTaskUpdated}
onTaskDeleted={onTaskDeleted}
> >
<Tasks <Tasks
dataSource={ganttDataSource} dataSource={ganttDataSource}
@ -249,52 +240,59 @@ const GanttView = (props: GanttViewProps) => {
/> />
<Toolbar> <Toolbar>
<Item name="undo" /> {/* <Item name="undo" /> */}
<Item name="redo" /> {/* <Item name="redo" /> */}
<Item name="separator" /> {/* <Item name="separator" /> */}
<Item name="collapseAll" /> <Item name="collapseAll" />
<Item name="expandAll" /> <Item name="expandAll" />
<Item name="separator" /> <Item name="separator" />
<Item name="addTask" /> {/* <Item name="addTask" /> */}
<Item name="deleteTask" /> {/* <Item name="deleteTask" /> */}
<Item name="separator" /> {/* <Item name="separator" /> */}
<Item name="zoomIn" /> <Item name="zoomIn" />
<Item name="zoomOut" /> <Item name="zoomOut" />
<Item name="separator" />
<Item
location="after"
widget="dxSelectBox"
options={{
width: 150,
items: [
{ value: 'auto', text: translate('::Auto') },
{ value: 'minutes', text: translate('::Minutes') },
{ value: 'hours', text: translate('::Hours') },
{ value: 'days', text: translate('::Days') },
{ value: 'weeks', text: translate('::Weeks') },
{ value: 'months', text: translate('::Months') },
{ value: 'quarters', text: translate('::Quarters') },
{ value: 'years', text: translate('::Years') },
],
displayExpr: 'text',
valueExpr: 'value',
value: scaleType,
onValueChanged: (e: any) => setScaleType(e.value),
}}
/>
<Item location="after" widget="dxButton" options={getRefreshButtonOptions()} />
<Item location="after" widget="dxButton" options={getSettingButtonOptions()} />
</Toolbar> </Toolbar>
<Editing <Editing enabled={false} />
enabled={gridDto.gridOptions.editingOptionDto?.allowUpdating} <FilterRow visible={gridDto.gridOptions.filterRowDto?.visible}></FilterRow>
allowTaskAdding={gridDto.gridOptions.editingOptionDto?.allowAdding} <HeaderFilter visible={gridDto.gridOptions.headerFilterDto.visible}></HeaderFilter>
allowTaskUpdating={gridDto.gridOptions.editingOptionDto?.allowUpdating} <Sorting mode={gridDto.gridOptions?.sortMode}></Sorting>
allowTaskDeleting={gridDto.gridOptions.editingOptionDto?.allowDeleting}
allowDependencyAdding={false}
allowDependencyDeleting={false}
allowResourceAdding={false}
allowResourceDeleting={false}
/>
<Validation autoUpdateParentTasks={true} /> <Validation autoUpdateParentTasks={true} />
<ContextMenu enabled={false} />
{ganttColumns.map((col: any) => ( {columnData
<Column .filter((col) => col.type != 'buttons')
key={col.dataField} .map((col: any) => (
dataField={col.dataField} <Column key={col.dataField} {...col} />
caption={col.caption} ))}
width={col.width}
/>
))}
</Gantt> </Gantt>
</div> </div>
</> </>
)} )}
<Dialog
isOpen={toolbarModalData?.open || false}
onClose={() => setToolbarModalData(undefined)}
onRequestClose={() => setToolbarModalData(undefined)}
>
{toolbarModalData?.content}
</Dialog>
</Container> </Container>
</> </>
) )

View file

@ -70,6 +70,7 @@ import { getList } from '@/services/form.service'
import { layoutTypes } from '../admin/listForm/edit/types' import { layoutTypes } from '../admin/listForm/edit/types'
import { useListFormCustomDataSource } from './useListFormCustomDataSource' import { useListFormCustomDataSource } from './useListFormCustomDataSource'
import { useListFormColumns } from './useListFormColumns' import { useListFormColumns } from './useListFormColumns'
import { Loading } from '@/components/shared'
interface GridProps { interface GridProps {
listFormCode: string listFormCode: string
@ -811,10 +812,20 @@ const Grid = (props: GridProps) => {
defaultTitle="Erp Platform" defaultTitle="Erp Platform"
></Helmet> ></Helmet>
)} )}
{!gridDto && <div className="p-4">Loading grid configuration...</div>} {!gridDto && (
{gridDto && !columnData && <div className="p-4">Loading columns...</div>} <div className="p-4">
<Loading loading>Loading grid configuration...</Loading>
</div>
)}
{gridDto && !columnData && (
<div className="p-4">
<Loading loading>Loading columns...</Loading>
</div>
)}
{gridDto && columnData && !gridDataSource && ( {gridDto && columnData && !gridDataSource && (
<div className="p-4">Loading data source...</div> <div className="p-4">
<Loading loading>Loading data source...</Loading>
</div>
)} )}
{gridDto && {gridDto &&
columnData && columnData &&

View file

@ -96,7 +96,8 @@ const List = () => {
<div className="flex gap-1"> <div className="flex gap-1">
{gridDto?.gridOptions?.layoutDto.gantt && {gridDto?.gridOptions?.layoutDto.gantt &&
gridDto?.gridOptions?.treeOptionDto?.parentIdExpr && ( gridDto?.gridOptions?.treeOptionDto?.parentIdExpr &&
gridDto?.gridOptions?.treeOptionDto?.titleExpr && (
<Button <Button
size="xs" size="xs"
variant={viewMode === 'gantt' ? 'solid' : 'default'} variant={viewMode === 'gantt' ? 'solid' : 'default'}

View file

@ -18,6 +18,7 @@ import { setGridPanelColor } from './Utils'
import { usePermission } from '@/utils/hooks/usePermission' import { usePermission } from '@/utils/hooks/usePermission'
import { usePWA } from '@/utils/hooks/usePWA' import { usePWA } from '@/utils/hooks/usePWA'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { GanttRef } from 'devextreme-react/cjs/gantt'
export interface ISelectBoxData { export interface ISelectBoxData {
value?: string value?: string
@ -144,6 +145,7 @@ const useFilters = ({
| MutableRefObject<DataGridRef<any, any> | undefined> | MutableRefObject<DataGridRef<any, any> | undefined>
| MutableRefObject<PivotGridRef | undefined> | MutableRefObject<PivotGridRef | undefined>
| MutableRefObject<TreeListRef<any, any> | undefined> | MutableRefObject<TreeListRef<any, any> | undefined>
| MutableRefObject<GanttRef | undefined>
listFormCode: string listFormCode: string
}): { }): {
filterToolbarData: ToolbarItem[] filterToolbarData: ToolbarItem[]

View file

@ -9,6 +9,7 @@ import { GridColumnData } from './GridColumnData'
import { dynamicFetch } from '@/services/form.service' import { dynamicFetch } from '@/services/form.service'
import { MULTIVALUE_DELIMITER } from '@/constants/app.constant' import { MULTIVALUE_DELIMITER } from '@/constants/app.constant'
import { TreeListRef } from 'devextreme-react/cjs/tree-list' import { TreeListRef } from 'devextreme-react/cjs/tree-list'
import { GanttRef } from 'devextreme-react/cjs/gantt'
const filteredGridPanelColor = 'rgba(10, 200, 10, 0.5)' // kullanici tanimli filtre ile filtrelenmis gridin paneline ait renk const filteredGridPanelColor = 'rgba(10, 200, 10, 0.5)' // kullanici tanimli filtre ile filtrelenmis gridin paneline ait renk
@ -19,6 +20,7 @@ const useListFormCustomDataSource = ({
| MutableRefObject<DataGridRef<any, any> | undefined> | MutableRefObject<DataGridRef<any, any> | undefined>
| MutableRefObject<PivotGridRef | undefined> | MutableRefObject<PivotGridRef | undefined>
| MutableRefObject<TreeListRef<any, any> | undefined> | MutableRefObject<TreeListRef<any, any> | undefined>
| MutableRefObject<GanttRef | undefined>
}) => { }) => {
const createSelectDataSource = useCallback( const createSelectDataSource = useCallback(
( (