Our pure JavaScript Scheduler component


Post by mateRO »

Hello

we are having some difficulties with SchedulerPro in our project

  1. We are setting events property on the Scheduler but nothing appears. When moving the timeline using shiftNext(), the events appear.

  2. After selecting an event, we are getting this error message in the console:

    Uncaught TypeError: Cannot read properties of null (reading 'outgoingDeps')
        at EventSelection.js:587:1
        at Array.forEach (<anonymous>)
        at SchedulerPro.highlightLinkedEvents (EventSelection.js:580:1)
        at SchedulerPro.onSelectedCollectionChange (EventSelection.js:451:1)
        at EventSelection.js:166:1
        at ProjectModel.deferUntilRepopulationIfNeeded (SchedulerBasicProjectMixin.js:519:1)
        at SchedulerPro.change (EventSelection.js:166:1)
        at Collection.trigger (Events.js:1212:1)
        at Collection.splice (Collection.js:625:1)
        at set selectedAssignments [as selectedAssignments] (EventSelection.js:219:1)

We are using typescript and this is the method on how we set events:
data.mwos is saved data from the server.

const extractEvents = (data: CoreType): EventType[] => {
    return data.mwos.map(mwo => {
        let ed: Date | null
        let cls = ''
        let iconCls = ''

    const fwoIndex = data.fwos.findIndex(value => value.id === mwo.fwoId)


    if (mwo.startPlanned !== null && mwo.endDate !== null) {
        ed = new Date(mwo.startPlanned?.getTime())
        ed.setSeconds(ed.getSeconds() + mwo.duration)
        mwo.endDate = ed
    }

    if (mwo.endDate !== null && mwo.endDate > mwo.dueDate) {
        cls += eventErrorColor
    } else if (fwoIndex !== -1) {
        if (data.fwos[fwoIndex].color === null) {
            data.fwos[fwoIndex].color = 'eventColor' + getCurrentColor()
        }

        cls += data.fwos[fwoIndex].color
    }

    if (mwo.startPlannedLocked) {
        iconCls = 'b-fa b-fa-lock'
    }

    return {
        id: mwo.id,
        name: mwo.description,
        startDate: mwo.startPlanned,
        endDate: mwo.endDate,
        image: false,
        resourceId: mwo.wksId,
        iconCls: iconCls,
        duration: mwo.duration,
        durationUnit: 'second',
        resizable: false,
        cls: cls
    }
})
}
Last edited by mateRO on Wed Nov 23, 2022 8:45 am, edited 1 time in total.

Post by marcio »

Hey mateRO,

Could you please provide a sample of how did you set up the Scheduler and the events data?? Without that is difficult to say what could be causing the events to not be appearing...

Also, that error doesn't seem to be related (at first sight) to the way that you're setting the events, perhaps with that sample project, we could check what is going on there. You can base in one of our demos, add your configuration and data with the behavior that you're describing, and from there we could assist you better.

Best regards,
Márcio


Post by mateRO »

Hi

this is the full code of our Timeline.tsx file:

import React, {Fragment, useCallback, useEffect, useRef, useState} from 'react'
import {
    dailyPreset,
    getScheduler,
    gridConfig,
    histogramConfig,
    monthlyPreset, project,
    schedulerProConfig,
    weeklyPreset
} from './config'
import {BryntumGrid, BryntumResourceHistogram, BryntumSchedulerPro, BryntumSplitter} from '@bryntum/schedulerpro-react'
import {
    autoPlanStateEnum,
    autoPlanStateType,
    CoreType,
    setPlanStateType,
} from '../../../Plansphere'
import {
    CalendarModel,
    DateHelper,
    ResourceHistogram,
    SchedulerPro,
    TimeRangesConfig,
    Grid,
    Store,
    EventStore,
    EventModel,
    Model,
    DomClassList,
    ScrollManager,
    WidgetHelper,
    ResourceModel, SchedulerEventModel
} from '@bryntum/schedulerpro'
import './Timeline.scss'
import {Drag} from './Drag'
import {
    Button,
    Checkbox,
    Dialog,
    DialogActions,
    DialogContent,
    DialogTitle,
    FormControlLabel,
    TextField,
    Grid as MuiGrid,
} from '@mui/material'
import EditPanel from '../../widgets/EditPanel/EditPanel'
import {MwoType} from '../../../models/mwo'
import {ErrorBaseEnum, ErrorType} from '../../../models/error'
import {FwoType} from '../../../models/fwo'
import {eventErrorColor, getCurrentColor} from '../../config/colors'

type TimelineProps = CoreType & {
    setMwos: React.Dispatch<React.SetStateAction<MwoType[]>>,
    setFwos: React.Dispatch<React.SetStateAction<FwoType[]>>,
    planState: autoPlanStateType,
    setPlanState: setPlanStateType,
}

type IdEvent = string
type IdResource = string
type IdDependency = string
type IdAssignment = string
type IdCalendar = string

type DependencyType = {
    id: IdDependency,
    from: IdEvent,
    to: IdEvent,
    lag: number,
    lagUnit: string
}

type ResourceType = {
    id: IdResource
    name: string,
    //iconCls: string,
    calendar: CalendarModel,
}
type AssignmentType = {
    id: IdAssignment,
    resourceId: IdResource,
    eventId: IdEvent
}

type EventType = {
    id: IdEvent
    name: string
    startDate: Date | null
    endDate: Date | null
    iconCls: string
    //eventColor: string
    resizable: boolean
    cls: string | null
    //dueDate: Date
    resourceId: string
}

type intervalType = {
    name: string
    cls?: string
    endDate?: Date
    iconCls?: string
    isWorking: boolean
    recurrentEndDate?: string
    recurrentStartDate?: string
    startDate?: Date
}

type CalendarType = {
    id: IdCalendar
    name: string
    unspecifiedTimeIsWorking: boolean
    cls?: string
    intervals: intervalType[]
}

//SchedulerEventModel
const extractEvents = (data: CoreType): EventType[] => {
    console.log('extractEvents: ' + data.mwos.length)
    return data.mwos.map(mwo => {
        let ed: Date | null
        let cls = ''
        let iconCls = ''

    const fwoIndex = data.fwos.findIndex(value => value.id === mwo.fwoId)


    if (mwo.startPlanned !== null && mwo.endDate !== null) {
        ed = new Date(mwo.startPlanned?.getTime())
        //const ed2 = DateHelper.add(ed, mwo.duration, 'seconds')
        ed.setSeconds(ed.getSeconds() + mwo.duration)
        mwo.endDate = ed
    }

    if (mwo.endDate !== null && mwo.endDate > mwo.dueDate) {
        cls += eventErrorColor
    } else if (fwoIndex !== -1) {
        if (data.fwos[fwoIndex].color === null) {
            data.fwos[fwoIndex].color = 'eventColor' + getCurrentColor()
        }

        cls += data.fwos[fwoIndex].color
    }

    if (mwo.startPlannedLocked) {
        iconCls = 'b-fa b-fa-lock'
    }

    return {
        id: mwo.id,
        name: mwo.description,
        startDate: mwo.startPlanned,
        endDate: mwo.endDate,
        image: false,
        resourceId: mwo.wksId,
        iconCls: iconCls,
        duration: mwo.duration,
        durationUnit: 'second',
        resizable: false,
        cls: cls
    }
})
}

const extractData = (data: CoreType): EventType[] => {
    return data.mwos.map(mwo => {
        let ed: Date | null

    if (mwo.startPlanned !== null && mwo.endDate !== null) {
        ed = new Date(mwo.startPlanned?.getTime())
        //const ed2 = DateHelper.add(ed, mwo.duration, 'seconds')
        ed.setSeconds(ed.getSeconds() + mwo.duration)
        mwo.endDate = ed
    }

    return {
        id: mwo.id,
        name: mwo.description,
        startDate: mwo.startPlanned,
        endDate: mwo.endDate,
        image: false,
        resourceId: mwo.wksId,
        iconCls: 'b-fa b-fa-snowplow',
        eventColor: 'orange',
        duration: mwo.duration,
        durationUnit: 'second',
        resizable: false,
        dueDate: mwo.dueDate,
        cls: null,
    }
})
}


const extractResources = (data: CoreType): ResourceType[] => {
    return data.wks.map(wks => {
        return {
            id: wks.id,
            name: wks.description,
            calendar: wks.calendar,
            image: false,
            //iconCls: 'b-fa b-fa-snowplow',
        }
    })
}

const extractDependencies = (data: CoreType): DependencyType[] => {
    let id = 0
    const dependencies: DependencyType[] = []

for (const mwo of data.mwos) {
    for (const d of mwo.predecessors) {
        id++
        dependencies.push({
            id: 'D' + id.toString(),
            from: d,
            to: mwo.id,
            lag: 0,
            lagUnit: 's',
        })
    }
}
return dependencies
}

const extractCalendars = (data: CoreType): CalendarType[] => {
    return data.calendars
}

const timeRangesSchedule: Partial<TimeRangesConfig> = {
    // This displays the red line indicating the current time
    showCurrentTimeLine: true,
    //currentDateFormat: 'D.M',
}

const timeRangesHistogram: Partial<TimeRangesConfig> = {
    // For histogram we don't want the time so we just insert empty format.
    showCurrentTimeLine: true,
    currentDateFormat: '',
}

const visibleDate = {
    date: new Date(),
    block: 'center'
}

enum View {
    daily,
    weekly,
    monthly,
}

const ViewLength = {
    [View.daily]: 1,
    [View.weekly]: 7,
    [View.monthly]: 31
}


const Timeline = (props: TimelineProps) => {
    const [filterDialog, setFilterDialog] = useState(false)
    const [createBookMarkDialog, setCreateBookMarkDialog] = useState(false)
    const [hideHistogram, setHideHistogram] = useState(false)
    const [editPanel, setEditPanel] = useState(false)
    const [editPanelData, setEditPanelData] = useState(null)

const histogramRef = useRef<BryntumResourceHistogram>(null)
const schedulerRef = useRef<BryntumSchedulerPro>(null)
const gridRef = useRef<BryntumGrid>(null)

const histogramInstance = () => histogramRef.current?.instance as ResourceHistogram
const gridInstance = () => gridRef.current?.instance as Grid
const schedulerInstance = () => schedulerRef.current?.instance as SchedulerPro

// Toggle MWO list visibility
const onToggleOrderList = useCallback(() => {
    if (gridRef !== null && gridRef.current !== null) {
        gridInstance().hidden = !gridInstance().hidden
        const element = document.getElementById('schedule_container')!
        if (!gridInstance().hidden) {
            element.style.width = '80%'
        }
    }

    bryntumIconsClassGrid()
}, [gridRef, gridRef.current, histogramRef, histogramRef.current, schedulerRef, schedulerRef.current])

useEffect(() => {
    histogramInstance().addPartner(schedulerInstance())
}, [schedulerRef, schedulerRef.current, gridRef, gridRef.current, histogramRef, histogramRef.current])


useEffect(() => {
    const element = document.getElementById('schedule_container')!
    element.style.width = '80%'

}, [props.mwos])

const addDraggedElement = (obj: SchedulerEventModel) => {
    const index = props.mwos.findIndex(el => el.id === obj.id)
    console.log(index)
    props.mwos[index].startPlanned = obj.startDate as Date


    const ed = new Date((obj.startDate as Date).getTime())
    ed.setSeconds(ed.getSeconds() + props.mwos[index].duration)
    props.mwos[index].endDate = ed
    obj.endDate = ed
    obj.name = props.mwos[index].description
    obj.resourceId = props.mwos[index].wksId
    obj.duration = props.mwos[index].duration
    obj.durationUnit = 'second'

    const event = schedulerInstance().eventStore.add(obj)
}

useEffect(() => {
    // // Setup dragging
    new Drag({
        grid: gridRef?.current?.instance,
        schedule: schedulerRef?.current?.instance,
        constrain: false,
        outerElement: gridRef?.current?.instance.element,
        scheduleInstance: schedulerInstance(),
        histogramInstance: histogramInstance(),
        gridInstance: gridInstance(),
        addDraggedElement: addDraggedElement
    })
}, [props.calendars, props.fwos, props.stocks, props.calendars, schedulerRef, schedulerRef.current, gridRef, gridRef.current, histogramRef, histogramRef.current])

// Runs only once. Dependencies are not going to change
useEffect(() => {
    const startDate = new Date()
    const endDate = new Date()

    startDate.setMonth(startDate.getMonth() - 6)
    endDate.setMonth(endDate.getMonth() + 6)

    if (schedulerRef !== null && schedulerRef?.current !== null) {
        const scheduler = schedulerRef.current.instance
        scheduler.on({
            toggleOrderList: onToggleOrderList,
        })

        schedulerInstance().setTimeSpan(startDate, endDate)
        schedulerInstance().scrollToNow({block: 'center'})
    }
}, [schedulerRef, schedulerRef.current, histogramRef, histogramRef.current, props.mwos, onToggleOrderList])

const onDataChange = (event: any) => {
    if (event.action === 'update') {
        const index = props.mwos.findIndex(el => el.id === event.record.originalData.id)
        if (event.changes.startDate !== undefined) {

            props.mwos[index].startPlanned = event.changes.startDate.value
            let ed = event.changes.startDate.value
            if (ed !== null && ed !== undefined) {
                ed = new Date(ed.getSeconds())
                ed.setSeconds(props.mwos[index].duration)
                props.mwos[index].endDate = ed
            }

            if (event.changes.endDate.value > props.mwos[index].dueDate) {
                if (!props.mwos[index].errors.some((el: ErrorType) => el.base === ErrorBaseEnum.overDue)) {
                    props.mwos[index].errors.push(
                        {
                            description: 'Over Due',
                            base: ErrorBaseEnum.overDue,
                        }
                    )
                }

                const taskClassList = new DomClassList(event.record.cls)
                taskClassList.add('overDue')
            } else {
                const baseIndex = props.mwos[index].errors.findIndex(el => el.base === ErrorBaseEnum.overDue)
                props.mwos[index].errors.splice(baseIndex, 1)

                const taskClassList = new DomClassList(event.record.cls)
                taskClassList.remove('overDue')
                event.record.cls = taskClassList.value
            }
        }
        if (event.changes.duration !== undefined) {
            props.mwos[index].duration = event.changes.duration.value
            event.record.duration = event.changes.duration.value
        }
        if (event.changes.endDate !== undefined) {
            event.record.endDate = event.changes.endDate.value
        }
        if (event.changes.draggable !== undefined) {
            event.record.draggable = event.changes.draggable
        }
    }
}

const setNewActivePreset = (event: any) => {
    const elements = document.getElementsByClassName('bryntumButtonGroup')
    Array.from(elements).forEach(el => el.classList.remove('bryntumButtonGroupActive'))

    event.currentTarget.classList.add('bryntumButtonGroupActive')
}

const clearSearch = () => {
    const gridStore = gridInstance().store as Store
    const schedulerStore = schedulerInstance().eventStore as EventStore

    gridStore.clearFilters()
    schedulerStore.forEach((task: SchedulerEventModel) => {
        const taskClassList = new DomClassList(task.cls)

        taskClassList.remove('searchedWidget')

        task.cls = taskClassList.value
    })
}

const searchInputChange = (value: string) => {
    if (value === undefined || value === '') {
        clearSearch()
        return
    }

    const gridStore = gridInstance().store as Store
    const schedulerStore = schedulerInstance().eventStore as EventStore


    value = value.toLowerCase()
    //Search for grid
    gridStore.clearFilters()
    gridStore.filter((el: Model) => {
        return el.id.toString().toLowerCase().includes(value) || el.getData('name').toLowerCase().includes(value)
    })

    //Search for scheduler
    schedulerStore.forEach((task: SchedulerEventModel) => {
        const taskClassList = new DomClassList(task.cls)

        taskClassList.remove('searchedWidget')

        if ((task.id.toString().toLowerCase().includes(value) || task.name.toLowerCase().includes(value))) {
            taskClassList.add('searchedWidget')
            schedulerInstance().scrollToDate(task.startDate as Date, {block: 'center'})
        }

        task.cls = taskClassList.value
    })
}

const onBeforeTaskEdit = (event: any): boolean => {
    setEditPanelData(event.taskRecord)
    setEditPanel(!editPanel)

    return false
}

// Don't allow dragging events that are locked
const beforeEventDrag = ({eventRecord}: any): boolean => {
    return !props.mwos.find(value => value.id === eventRecord.id)?.startPlannedLocked
}

const bryntumIconsClass = (condition: boolean): string => {
    return condition ? 'bryntumIconButtonBlue' : 'bryntumIconButtonGray'
}

const bryntumIconsClassGrid = () => {
    const element = document.getElementsByClassName('bGridToggle')
    element[0].classList.remove('bryntumIconButtonGray')
    element[0].classList.remove('bryntumIconButtonBlue')


    if (gridRef === null || gridRef.current === null) {
        return
    }

    element[0].classList.add(!gridInstance().hidden ? 'bryntumIconButtonBlue' : 'bryntumIconButtonGray')

}

return (
    <div id={'scheduler_container'}
        style={{flex: 'auto', flexWrap: 'nowrap', display: 'flex', flexDirection: 'row'}} className={'scheduler'}>
        <Dialog open={filterDialog}>
            <DialogTitle>
                Workstations filter
            </DialogTitle>
            <DialogContent>
                <MuiGrid container justifyContent={'flex-start'} alignItems={'center'}>
                    {
                        props.mwos.map(mwo => (
                            <MuiGrid key={mwo.id}>
                                <FormControlLabel value={mwo.id} control={<Checkbox checked={true}></Checkbox>}
                                    label={mwo.id}></FormControlLabel>
                            </MuiGrid>
                        ))
                    }
                </MuiGrid>
            </DialogContent>
            <DialogActions>
                <Button id={'filterDialogSave'} onClick={() => {
                    setFilterDialog(!filterDialog)
                }}>
                    Save
                </Button>
                <Button id={'filterDialogCreateBookmark'} onClick={() => {
                    //setFilterDialog(!filterDialog)
                    setCreateBookMarkDialog(!createBookMarkDialog)
                }}>
                    Create bookmark
                </Button>
                <Button id={'filterDialogCancel'} onClick={() => {
                    setFilterDialog(!filterDialog)
                }}>
                    Cancel
                </Button>
            </DialogActions>
        </Dialog>
        <Dialog open={createBookMarkDialog}>
            <DialogContent>
                <TextField id={'bookMarkName'} label={'New bookmark name'} variant={'filled'}></TextField>
            </DialogContent>
            <DialogActions>
                <Button id={'filterDialogSave'} onClick={() => {
                    setFilterDialog(!filterDialog)
                    setCreateBookMarkDialog(!createBookMarkDialog)
                }}>
                    Create
                </Button>
                <Button id={'filterDialogCancel'} onClick={() => {
                    setCreateBookMarkDialog(!createBookMarkDialog)
                }}>
                    Cancel
                </Button>
            </DialogActions>
        </Dialog>
        <Fragment>
            <div id={'schedule_container'}>
                <BryntumSchedulerPro
                    {...schedulerProConfig}
                    onBeforeTaskEdit={onBeforeTaskEdit}
                    tbar={[
                        {
                            icon: 'b-icon b-fa-caret-left',
                            cls: {'bryntumIconButtonLeft': true},
                            onAction: ({source}: any) => {
                                schedulerInstance().shiftPrevious()
                            }
                        },
                        {
                            icon: 'b-icon b-fa-caret-right',
                            cls: {'bryntumIconButtonRight': true},
                            onAction: ({source}: any) => {
                                schedulerInstance().shiftNext()
                            }
                        },
                        {
                            text: 'Now',
                            cls: {'bryntumButton': true},
                            onAction: ({source}: any) => {
                                schedulerInstance().scrollToNow({block: 'center'})
                            }
                        },
                        {
                            type: 'buttongroup',
                            toggleGroup: true,
                            cls: {'bryntumButtonGroup': true},
                            items: [
                                {
                                    //id: 'buttonGroupDay',
                                    text: 'Day',
                                    value: 1,
                                    cls: 'bryntumButtonGroup bryntumButtonGroupDay',
                                    preset: dailyPreset,
                                },

                                {
                                    //id: 'buttonGroupDayWeek',
                                    text: 'Week',
                                    cls: 'bryntumButtonGroup bryntumButtonGroupWeek bryntumButtonGroupActive',
                                    value: 7,
                                    preset: weeklyPreset,
                                },
                                {
                                    //id: 'buttonGroupMonth',
                                    text: 'Month',
                                    cls: 'bryntumButtonGroup bryntumButtonGroupMonth',
                                    value: 31,
                                    preset: monthlyPreset,
                                },
                            ],
                            onClick: ({source, event}: any) => {
                                setNewActivePreset(event)
                                const
                                    scheduler = getScheduler(source),
                                    value = source.value,
                                    startDate = DateHelper.add(DateHelper.clearTime(scheduler.startDate), scheduler.extraData.startHour, 'h'),
                                    endDate = DateHelper.add(startDate, value - 1, 'd')

                                endDate.setHours(scheduler.extraData.endHour)

                                schedulerInstance().viewPreset = source.preset
                            }
                        },
                        '->',
                        {
                            type: 'textfield',
                            cls: 'searchText',
                            clearable: true,
                            placeholder: 'Search mwos..',
                            maxLength: 150,
                            triggers: {
                                plug: {
                                    cls: 'b-fa b-fa-search textFieldIcon'
                                }
                            },
                            onClear: ({source}: any) => {
                                clearSearch()
                            },
                            onInput: ({value}: any) => {
                                searchInputChange(value)
                            }
                        },
                        {
                            icon: 'b-icon b-icon-filter',
                            cls: bryntumIconsClass(filterDialog),
                            onAction: ({source}: any) => {
                                setFilterDialog(!filterDialog)
                            }
                        },
                        {
                            icon: 'b-icon b-fa-chart-column',
                            cls: bryntumIconsClass(!hideHistogram),
                            onAction: ({source}: any) => {
                                setHideHistogram(!hideHistogram)
                            }
                        },
                        {
                            icon: 'b-icon b-icon-menu-vertical',
                            cls: 'bGridToggle bryntumIconButtonBlue',
                            onAction: ({source}: any) => {
                                //getScheduler(source).trigger('toggleOrderList')
                                schedulerInstance().trigger('toggleOrderList')
                            }
                        }
                    ]}

                    ref={schedulerRef}
                    events={extractEvents(props)}
                    resources={extractResources(props)}
                    //assignments={extractAssignments(props)}
                    dependencies={extractDependencies(props)}
                    calendars={extractCalendars(props)}
                    timeRangesFeature={timeRangesSchedule}

                    visibleDate={visibleDate}
                    scrollable={true}
                    onDataChange={onDataChange}
                    onBeforeEventDrag={beforeEventDrag}
                />
                <BryntumSplitter/>
                <BryntumResourceHistogram
                    {...histogramConfig}
                    hidden={hideHistogram}
                    ref={histogramRef}
                    timeRangesFeature={timeRangesHistogram}
                />

            </div>
            <BryntumSplitter/>
            {!editPanel ? <BryntumGrid
                {...gridConfig}
                ref={gridRef}
                data={extractData(props)}
            /> : null}
            {editPanel ?
                <EditPanel fwos={props.fwos} setEditPanel={setEditPanel} eventRecord={editPanelData}
                    resources={extractResources(props)}
                    mwos={props.mwos}></EditPanel> : null}

        </Fragment>
    </div>
)
}

export default Timeline
export {View, ViewLength}
export type {CalendarType, TimelineProps, IdCalendar}

This is our config file:

import {BryntumGridProps, BryntumResourceHistogramProps, BryntumSchedulerProProps} from '@bryntum/schedulerpro-react'
import {Column, DateHelper, ProjectModel, ResourceModel, ResourceModelConfig, WidgetHelper} from '@bryntum/schedulerpro'
import EditPanel from '../../widgets/EditPanel/EditPanel'

const dailyPreset = {
    base: 'hourAndDay',
    tickWidth: 45,
    timeResolution: {              // Dates will be snapped to this resolution
        unit: 'minute',       // Valid values are 'millisecond', 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'.
        increment: 1
    },
    headers: [
        {
            unit: 'day',
            align: 'center',
            dateFormat: 'dddd DD.MM'
        },
        {
            unit: 'h',
            align: 'center',
            dateFormat: 'HH'
        }
    ]
}

const weeklyPreset = {
    base: 'dayAndWeek',
    // id: 'myPreset',              // Unique id value provided to recognize your view preset. Not required, but having it you can simply set new view preset by id: scheduler.viewPreset = 'myPreset'

// name: 'My view preset',        // A human-readable name provided to be used in GUI, e.i. preset picker, etc.

// tickWidth  : 24,                // Time column width in horizontal mode
// tickHeight : 50,                // Time column height in vertical mode
displayDateFormat: 'DD HH:mm',    // Controls how dates will be displayed in tooltips etc

shiftIncrement: 1,             // Controls how much time to skip when calling shiftNext and shiftPrevious.
shiftUnit: 'day',         // Valid values are 'millisecond', 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'.
//defaultSpan: 7,            // By default, if no end date is supplied to a view it will show 12 hours

timeResolution: {              // Dates will be snapped to this resolution
    unit: 'minute',       // Valid values are 'millisecond', 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'.
    increment: 10
},

headers: [                     // This defines your header rows from top to bottom
    {                           // For each row you can define 'unit', 'increment', 'dateFormat', 'renderer', 'align', and 'thisObj'
        unit: 'month',
        increment: 1,
        dateFormat: 'MMMM'
    },
    {
        unit: 'day',
        increment: 1,
        dateFormat: 'ddd DD.MM'
    },
],

// columnLinesFor : 1              // Defines header level column lines will be drawn for. Defaults to the last level.
}

const monthlyPreset = {
    id: 'monthlyView',
    tickWidth: 35,
    //rowHeight         : 32,
    displayDateFormat: 'DD.MM',
    shiftIncrement: 1,
    shiftUnit: 'day',
    timeResolution: {
        unit: 'day',
        increment: 1
    },
    //defaultSpan: 31,
    mainHeaderLevel: 1,
    headers: [
        {
            unit: 'month',
            increment: 1,
            dateFormat: 'MMMM YYYY'
        },
        {
            unit: 'day',
            increment: 1,
            dateFormat: 'DD'
        }
    ]
}

// Upper-level SchedulerPro getter
const getScheduler = (child: any) => {
    return child.up('schedulerpro')
}

class MyResource extends ResourceModel {
    //https://forum.bryntum.com/viewtopic.php?t=18441&start=10
    constructor(config?: Partial<ResourceModelConfig>) {
        super(config)
        Object.defineProperty(this, 'initials', {
            get() {
                return 'ABC'
            }
        })
    }
}

const project = new ProjectModel({
    resourceModelClass: MyResource,
})


const schedulerProConfig: BryntumSchedulerProProps = {
    viewPreset: weeklyPreset,
    allowOverlap: false,
    rowHeight: 80,
    flex: '1 1 60%',
    enableUndoRedoKeys: true,
    stripeFeature: true,
    timeRangesFeature: true,
    columns: [
        {
            type: 'resourceInfo',
            text: 'Name',
            field: 'name',
            showEventCount: true,
            width: 200,
            editor: null,
        }
    ],
    project: project,

calendarHighlightFeature: true,
eventTooltipFeature: {
    hoverDelay: 300,
    textContent: true,
    template: (event: any) => {
        return `<h4>ID: ${event.eventRecord.id}</h4>`
    },
},
features: {
    tree: true,
    //To customize arrow between mwos on timeline
    //https://www.bryntum.com/docs/gantt/api/Scheduler/feature/Dependencies
    dependencies: true,
    //https://www.bryntum.com/docs/gantt/api/Scheduler/feature/DependencyEdit
    dependencyEdit: false,
    eventDrag: {
        constrainDragToResource: true,
    },
    eventDragCreate: false,
    eventDragSelect: true,
    eventResize: false,
    cellMenu: true,

    timeAxisHeaderMenu: {
        disabled: true,
    },
    //For right click on events
    //https://www.bryntum.com/docs/gantt/api/Scheduler/feature/EventMenu
    eventMenu: {
        disabled: true
    },
    //Right click on empty space in timeline
    scheduleMenu: {
        disabled: true
    },


    nonWorkingTime: true,
    resourceNonWorkingTime: {
        maxTimeAxisUnit: 'week',
    },
},


extraData: {
    startHour: 0,
    endHour: 24
},

zoomKeepsOriginalTimespan: true,
zoomOnTimeAxisDoubleClick: false,
zoomOnMouseWheel: false,
createEventOnDblClick: false,
enableDeleteKey: false,
enableEventAnimations: false,
multiEventSelect: false,
highlightPredecessors: true,
highlightSuccessors: true,
}

const histogramConfig: BryntumResourceHistogramProps = {
    project: project,
    hideHeaders: true,
    viewPreset: weeklyPreset,
    rowHeight: 60,
    flex: '1 1 40%',
    showBarTip: true,
    showBarText: false,
    columns: [
        {
            type: 'resourceInfo',
            text: 'Name',
            field: 'name',
            editor: false,
            //enableCellContextMenu: false,
            showEventCount: true,
            width: 200
        },
        {
            type: 'scale',
            hidden: true,
        }
    ],

features: {
    nonWorkingTime: true,
    resourceNonWorkingTime: {
        maxTimeAxisUnit: 'week',
    },
},

calendarHighlightFeature: true,

eventTooltipFeature: {
    hoverDelay: 300,
    textContent: true,
    template: (event: any) => {
        return `<h4>ID: ${event.eventRecord.id}</h4>`
    },
},
}

const gridConfig: BryntumGridProps = {
    flex: 1,
    rowHeight: 60,
    readOnly: false,
    minWidth: 250,
    maxWidth: 431,
    selectionMode: {
        row: true
    },
    draggable: true,

stripeFeature: true,
sortFeature: 'id',
cellTooltipFeature: {
    hoverDelay: 300,
    textContent: true,
    tooltipRenderer: ({record: order}: any) =>
        order.isScheduled ?
            `<h4>Type: ${order.templateName}</h4>This order is scheduled to finish by <b>${DateHelper.format(order.finishDate, 'MMM d HH:mm')}</b>`
            : `<h4>Type: ${order.templateName}</h4>Try dragging this order onto the schedule`
},

// Grid top toolbar
tbar: [
    {
        text: 'Add order',
        cls: {'tbarBryntumButton': true},
        //icon: 'b-icon b-fa-plus',
        toggleable: true,
        // onAction   : onAddClick
    },
    '->',
    {
        text: 'Hide scheduled',
        toggleable: true,
        cls: {'tbarBryntumButton': true},
        onToggle: ({source, pressed}: any) => {
            const store = source.up('grid').store
            if (pressed) {
                store.filter({
                    property: 'startDate',
                    operator: '=',
                    value: null
                })
            } else {
                store.clearFilters()
            }
        }
    }
],

columns: [{
    type: 'template',
    text: 'Order List',
    flex: 1,
    maxWidth: 100,
    field: 'name',
    editor: false,
    htmlEncode: false,
    cellCls: 'order-cell',
    template: ({record: order}: any) =>
        `<div>${'#' + order.id} </div>
        <div class="customer">Descr: ${order.name}</div>`
}, {
    text: 'Start',
    type: 'date',
    flex: 1,
    align: 'right',
    format: 'MMM D HH:mm',
    field: 'startDate',
}, {
    text: 'Finish',
    type: 'date',
    flex: 1,
    align: 'right',
    format: 'MMM D HH:mm',
    editor: false,
    field: 'endDate'
},
{
    text: 'Due date',
    type: 'date',
    flex: 1,
    align: 'right',
    format: 'MMM D HH:mm',
    editor: false,
    field: 'dueDate'
},

],

disableGridRowModelWarning: true
}

export {
    schedulerProConfig,
    gridConfig,
    dailyPreset,
    weeklyPreset,
    monthlyPreset,
    getScheduler,
    histogramConfig,
    project
}

Post by alex.l »

Nothing obvious from the files attached, unfortunately.
Please zip and attach a runnable app here, and provide steps to reproduce the issue. It will help us a lot to investigate it faster.

All the best,
Alex


Post by kovacd »

Hello,

mateRO was a member in our group an was moved to another department. Now i am working on the project so i will continue with this topic.

I've attached a minimal test case. The src/README.md file contains all of our problems and how to reproduce them.

Attachments
test-case-scheduler.zip
(401.07 KiB) Downloaded 36 times

Post by kovacd »

Hey,

any updates on this topic?


Post by alex.l »

Sorry for long answer. Your application is very hard to read and understand. We are working hard to find all required parts to see what's going on.

All the best,
Alex


Post by kovacd »

Thank you for the reply, if there is anything we can help you with understanding the code or anything else, let us know and we can schedule a meeting.

Best regards.


Post by alex.l »

Thanks for waiting!

I found 2 problems:

  1. resourceId notation for SchedulerPro is not supported. You need to fill assignmentStore instead. I see in the code you just need to uncomment that and remove resourceId from events data.
  2. SchedulerPro shows current date period, but your event scheduled on Dec 2023. try to change startDate to new Date() for test. You'll see event on the timeline.
Attachments
Screenshot 2022-12-13 at 11.22.00.png
Screenshot 2022-12-13 at 11.22.00.png (281.84 KiB) Viewed 319 times

All the best,
Alex


Post by kovacd »

Hey,

thank you for the reply, will make the changes.

Hope you will manage to resolve other issues as well.


Post Reply