Hi, I'd like to get some assistance if possible due to some issues I need to solve.
1 - I am successfully able to export my scheduler to excel, but after it happens, I get an error when opening the Excel file. Excel then offers to repair the file which successfully does and I can enter the file. Is there anything wrong on my code that may be causing this error in excel?
2 - I need to filter all rows that won't have a specific value in them. How can I do that?. At the moment I've been able to successfully alter some values per each row/column (i. e. I am converting statusId to its correct assigned label name) but don't know how to avoid an entire row to be exported when a specific cell does not have a value for that row.
3 - I'm trying to use the "width" prop but it doesn't seem to alter/work the column width at all.
I'm pasting my component and also captures of the error I get from Excel as well as the log file.
Thanks and I look forward to hearing from you,
import React, { useCallback, useRef, useEffect, useState, lazy } from 'react';
import { useDispatch } from 'react-redux'
import zipcelx from 'zipcelx';
import Moment from 'moment'
import { extendMoment } from 'moment-range'
import { BryntumScheduler } from '@bryntum/schedulerpro-react'
import { Menu, ColumnStore, WidgetColumn } from '@bryntum/schedulerpro/schedulerpro.umd'
import '../../styles/styles.scss'
import {
getResourcesSchedulerSearch,
setEmptyGetResourcesSchedulerSearch,
setFormFilters
} from '../../redux/actions'
import {
errorAlert,
getMessageTranslation,
convertToSelectableValuesTranslated,
getTranslation,
decimalDaysToDateValues
} from '../../utils'
import {
buildFiltersResourcesSchedulerSearchBusiness,
initModelResourcesSchedulerSearchBusiness,
getViewPresetsTaskSchedulerBusiness,
getViewStartDateTaskSchedulerBusiness,
getViewEndDateTaskSchedulerBusiness,
getTimeSpanFromViewPresetTaskSchedulerBusiness,
getLocaleTaskSchedulerBusiness,
mapToSchedulerResourcesTasks,
personalTasksRemovalTool,
getScheduleTaskDurationBusiness
} from '../../business'
class ExtendedWidgetColumn extends WidgetColumn {
static get type() {
return 'extendedwidget'
}
onBeforeWidgetSetValue(widget, renderData) {
if (widget.type === 'button') {
widget.hidden = renderData.record.get('resourceTypeCode') !== 'Resources Groups'
}
}
}
ColumnStore.registerColumnType(ExtendedWidgetColumn);
const TaskSchedulerSearchResourcesPopup = lazy(() => import('../TaskSchedulerSearchResourcesPopup'))
const TaskSchedulerSearchProjectIdPopup = lazy(() => import('../TaskSchedulerSearchProjectIdPopup'))
const Popup = lazy(() => import('../../components/Popup'))
const SchedulerPro = ({
user,
fieldsPage,
translation,
language,
scenario,
localization,
userConfiguration,
companies,
clientsClassesScenaries,
onTranslation,
messages,
usersCostCenters,
schedulerResourcesResults,
schedulerResources,
schedulersStatus,
setSelectedResources,
schedulerSearchResults,
bookingTasksSearchResults,
handleTaskEditionClick,
handleBookingEditionClick,
refreshSelectedResources,
setRefreshSelectedResources,
personalTasksSearchResults,
personalTasksFromScheduler,
setPersonalTasksFromScheduler,
setOriginalSelectedResource
}) => {
const scheduler = useRef()
const dispatch = useDispatch()
const [openResourcesSchedulerSearchPopup, setOpenResourcesSchedulerSearchPopup] = useState(false)
const [openResourcesProjectIdSearchPopup, setOpenResourcesProjectIdSearchPopup] = useState(false)
const [cecos, setCecos] = useState([])
const [projectId, setProjectId] = useState(null)
const [searchModel, setSearchModel] = useState(initModelResourcesSchedulerSearchBusiness())
const presets = getViewPresetsTaskSchedulerBusiness()
const moment = extendMoment(Moment)
const commonEvents = schedulerSearchResults.map(data => mapToSchedulerResourcesTasks(data))
const personalEvents = personalTasksSearchResults.map(pt => mapToSchedulerResourcesTasks(pt, schedulerSearchResults))
const bookingEvents = bookingTasksSearchResults.map(data => mapToSchedulerResourcesTasks(data))
const events = [...commonEvents, ...personalEvents, ...bookingEvents]
useEffect(() => {
if (events.length > 0) {
if (projectId !== null) {
scheduler.current.instance.events = scheduler.current.instance.events.filter(se => se.data.projectId === parseInt(projectId))
}
}
}, [events])
useEffect(() => {
initializeSelectors()
handleSearch({ ...searchModel, scenaryId: scenario.id, userId: user.id })
}, [])
const initializeSelectors = useCallback(() => {
dispatch(setEmptyGetResourcesSchedulerSearch())
}, [fieldsPage])
useEffect(() => {
if (language.isoCode === 'es-ES' && !scheduler.current.instance.localeManager.locales["es-ES"]) {
scheduler.current.instance.localeManager.registerLocale('es-ES', {
desc: 'Spanish',
locale: {
...translation('bryntum_scheduler_locale_config'),
...getLocaleTaskSchedulerBusiness(language, fieldsPage, scenario)
}
});
}
if (language.isoCode === 'en-GB' && !scheduler.current.instance.localeManager.locales["en-GB"]) {
scheduler.current.instance.localeManager.registerLocale('en-GB', {
desc: 'English',
locale: {
...translation('bryntum_scheduler_locale_config'),
...getLocaleTaskSchedulerBusiness(language, fieldsPage, scenario)
}
});
}
scheduler.current.instance.localeManager.applyLocale(language.isoCode);
}, [language])
useEffect(() => {
if (refreshSelectedResources) {
setRefreshSelectedResources(false)
scheduler.current.instance.deselectAll()
}
}, [refreshSelectedResources])
useEffect(() => {
if (personalTasksFromScheduler.length === 0) {
const newPersonalTasks = scheduler.current.instance.events.filter(se => se.data.type !== null)
setPersonalTasksFromScheduler(newPersonalTasks)
}
}, [personalTasksFromScheduler, (scheduler.current !== undefined && scheduler.current.instance.events.length > 0)])
const handleSearch = (filters) => {
setSearchModel(filters)
dispatch(setEmptyGetResourcesSchedulerSearch())
const businessResult = buildFiltersResourcesSchedulerSearchBusiness(filters)
if (!businessResult.hasErrors) {
dispatch(setFormFilters(businessResult.content))
dispatch(getResourcesSchedulerSearch(businessResult.content))
} else {
errorAlert(getMessageTranslation(language.id, messages, 'code', businessResult.resultOperations[0].message))
}
}
const handleOnPopupSearch = (qualificationName, costCenterIds) => {
setOpenResourcesSchedulerSearchPopup(false)
handleSearch({ ...searchModel, qualificationName: qualificationName, costCenterIds: costCenterIds })
}
const handleOnPopupSearchByProjectId = (projectId) => {
setOpenResourcesProjectIdSearchPopup(false)
setProjectId(projectId)
scheduler.current.instance.events = scheduler.current.instance.events.filter(se => se.data.projectId === parseInt(projectId))
}
const handleClearProjectId = () => {
setProjectId(null)
scheduler.current.instance.events = events
}
const handleOnOpenResourcesSchedulerSearchPopup = () => {
setOpenResourcesSchedulerSearchPopup(true)
}
const handleOnOpenProjectIdSchedulerSearchPopup = () => {
setOpenResourcesProjectIdSearchPopup(true)
}
const handleClearClick = () => {
setCecos([])
setSelectedResources([])
handleSearch({ ...initModelResourcesSchedulerSearchBusiness(), scenaryId: scenario.id, userId: user.id })
}
const selectionChangeHandler = ({ selection }) => {
if (selection[0] !== undefined && selection[0].data) {
setSelectedResources(selection.map(s => s.data))
setOriginalSelectedResource(selection.map(s => s.data))
} else if (selection[0] === undefined) {
setSelectedResources([])
setOriginalSelectedResource([])
} else {
return
}
}
const getSchedulerStatusValue = (data) => {
return !!data?.eventRecord?.statusId && schedulersStatus?.length > 0
? convertToSelectableValuesTranslated(schedulersStatus, language.id).filter(schedulerStatus => schedulerStatus.value === data.eventRecord.statusId)[0]?.label
: ''
}
const handlePresetView = (e, base) => {
const schedulerInst = scheduler.current.instance
const timeSpanBusinessResult = getTimeSpanFromViewPresetTaskSchedulerBusiness(schedulerInst.startDate, base)
schedulerInst.viewPreset = presets.find(p => p.base === timeSpanBusinessResult.viewPresetName)
schedulerInst.setTimeSpan(timeSpanBusinessResult.startDate, timeSpanBusinessResult.endDate)
}
const handleShiftPrevious = () => {
scheduler.current.instance.shiftPrevious()
}
const handleShiftNext = () => {
scheduler.current.instance.shiftNext()
}
const handleExcelExporter = () => {
scheduler.current.instance.features.excelExporter.export({
filename: 'Mi Exports de Excel',
exporterConfig: {
columns: [
{ text: `${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_name", scenario.id)}`, field: 'name' },
{ text: `${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_resourceType", scenario.id)}`, field: 'resourceType' },
],
eventColumns: [
{
text: `${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_projectId", scenario.id)}`,
field: 'projectId',
width: 400
},
{ text: `${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_taskId", scenario.id)}`, field: 'name', width: 400 },
{
text: `${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_status", scenario.id)}`,
field: 'statusId',
width: 400,
renderer: ({ value }) => {
const convertedValue = convertToSelectableValuesTranslated(schedulersStatus, language.id).filter(schedulerStatus => schedulerStatus.value === value)[0]?.label
return convertedValue
}
},
{ text: `${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_date", scenario.id)}`, field: 'startDate', width: 800 },
{
text: /*`${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_status", scenario.id)}`*/ 'Duración',
field: 'duration',
width: 800,
renderer: ({ value }) => {
return decimalDaysToDateValues(value)
}
},
{ text: `${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_createdBy", scenario.id)}`, field: 'createdUser', width: 800 },
{
text: `${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_on", scenario.id)}`,
field: 'createdDate',
width: 800,
renderer: ({ value }) => {
const convertedValue = moment(value).format('DD/MM/YYYY HH:mm')
return convertedValue
}
}
]
}
})
}
const schedulerConfig = !!schedulerResources && !!events && {
enableRecurringEvents: true,
eventStyle: 'colored',
eventColor: null,
resources: schedulerResourcesResults.map(sr => (sr)),
events: events,
features: {
eventResize: false,
stripe: false,
eventDrag: false,
eventDragCreate: false,
eventMenu: false,
cellMenu: false,
eventEdit: false,
scheduleMenu: false,
timeRanges: {
enableResizing: false,
showCurrentTimeLine: true,
showHeaderElements: true,
tooltipTemplate: ({ timeRange }) => timeRange.name
},
eventTooltip: {
template: (data) =>
(data.eventRecord.data.type !== 'personal')
?
`
<div class="b-sch-event-tooltip b-sch-event-tooltip--no-margin">
<div class="tooltip-content tooltip-content--centered">
${!!data?.eventRecord?.projectId ? `${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_projectId", scenario.id)}: ${data.eventRecord.projectId}<br/>` : ''}
${!!data?.eventRecord?.name ? `${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_taskId", scenario.id)}: ${data.eventRecord.name}<br/>` : ''}
${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_resourceType", scenario.id)}: ${!!data?.eventRecord?.resource?.resourceType ? data.eventRecord.resource.resourceType : ''} <br/>
${!!getSchedulerStatusValue(data) ? `${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_status", scenario.id)}: ${getSchedulerStatusValue(data)}<br/>` : ''}
<div>
<div class="flex flex--row flex--space-between">
${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_date", scenario.id)}: ${moment(data.startDate).format('DD/MM/YYYY')}
<div class="b-sch-clockwrap b-sch-clock-hour clock--centered">
<div class="b-sch-clock">
<div class="b-sch-hour-indicator">${moment(data.startDate).format('MMM')}</div>
<div class="b-sch-minute-indicator">${moment(data.startDate).format('D')}</div>
<div class="b-sch-clock-dot"></div>
</div>
<span class="b-sch-clock-text"> ${!!getScheduleTaskDurationBusiness(data.endDate, data.startDate) ? `: ${getScheduleTaskDurationBusiness(data.endDate, data.startDate)}` : ''}</span>
</div>
</div>
</div>
</div>
<div class="divider"></div>
<div class="tooltip-content">
${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_createdBy", scenario.id)}: ${!!data?.eventRecord?.createdUser ? data.eventRecord.createdUser : ''} ${onTranslation(language.id, fieldsPage, "scheduler_taskscheduler_eventTooltip_on", scenario.id)} ${moment(data.eventRecord.createdDate).format('DD/MM/YYYY HH:mm')}<br/>
</div>
</div>
`
:
null
},
timeAxisHeaderMenu: {
items: {
zoomLevel: false,
currentTimeLine: false
}
},
excelExporter: {
zipcelx,
group: 'role',
dateFormat: 'DD/MM/YYYY HH:mm'
}
},
columns: [
{
type: 'resourceInfo',
text: 'L{resourceList_filter_orderBy_name}',
width: '11.5rem',
editor: false,
showEventCount: false,
renderer: ({ record }) => {
let startDate = (!!record.eventStore.lastDateRange?.startDate && record.eventStore.lastDateRange?.startDate !== undefined) && new Date(record.eventStore.lastDateRange?.startDate)
let endDate = (!!record.eventStore.lastDateRange?.endDate && record.eventStore.lastDateRange?.endDate !== undefined) && new Date(record.eventStore.lastDateRange?.endDate)
let range = ((!!startDate && startDate !== undefined) && (!!endDate && endDate !== undefined)) && moment.range(startDate, endDate)
const taskCounter = personalTasksRemovalTool(record, range)
return {
class: 'b-resource-info',
children: [
{
tag: 'div',
class: 'b-resource-avatar b-resource-initials',
html: record.initials
},
{
tag: 'dl',
html: `<dl>
<dt>${record.name}</dt>
<dd>${taskCounter}${getTranslation(language.id, fieldsPage, "taskscheduler_taskEventName", scenario.id)}${(taskCounter !== 1 ? 's' : '')}</dd>
</dl>`
},
]
};
}
},
{
text: 'L{resourceList_filter_orderBy_resourceType}',
field: 'resourceType',
width: '7.5rem',
editor: false,
filterable: {
filterField: {
placeholder: 'L{Column.resourceList_filter_tooltip_resourceType}',
}
}
},
{
width: '1rem',
type: 'extendedwidget',
widgets:
[{
cls: 'b-transparent',
type: 'button',
tooltip: 'L{Widgets.resourceList_resource_btn_members}',
icon: 'b-fa b-fa-fw b-fa-ellipsis-v',
onAction: ({ source: btn }) => {
const menu = btn.menu || (btn.menu = new Menu({
cls: 'b-transparent',
forElement: btn.element,
items: [
{
cls: 'b-transparent',
text: onTranslation(language.id, fieldsPage, "resourceList_resource_btn_members", scenario.id),
onItem({ item }) {
console.log('showing team members', item);
}
}
],
focusOnHover: false,
}));
menu.show();
}
}]
},
],
barMargin: 5,
rowHeight: 40,
createEventOnDblClick: false,
enableDeleteKey: false,
filterBarFeature: true,
startDate: getViewStartDateTaskSchedulerBusiness(),
endDate: getViewEndDateTaskSchedulerBusiness(),
autoAdjustTimeAxis: true,
presets: presets,
viewPreset: presets[0],
useInitialAnimation: 'slide-from-left',
tbar: [
{
type: 'buttongroup',
cls: 'tbar-left',
width: '22rem',
items: [
{
type: 'button',
tooltip: onTranslation(language.id, fieldsPage, "resourceList_filter_tooltip_popup", scenario.id),
cls: 'b-transparent',
icon: 'b-fa-search',
onClick: () => handleOnOpenResourcesSchedulerSearchPopup()
},
{
type: 'button',
tooltip: onTranslation(language.id, fieldsPage, "resourceList_filter_tooltip_deleteFilters", scenario.id),
cls: 'b-transparent',
icon: 'b-fa-trash',
onClick: () => handleClearClick()
}
]
},
{
type: 'buttongroup',
cls: 'tbar-left',
style: 'flex-grow: 1',
items: [
{
type: 'button',
tooltip: onTranslation(language.id, fieldsPage, "resourceList_filter_tooltip_popup", scenario.id),
cls: 'b-transparent',
icon: 'b-fa-filter',
onClick: () => handleOnOpenProjectIdSchedulerSearchPopup()
},
{
type: 'button',
tooltip: onTranslation(language.id, fieldsPage, "resourceList_filter_tooltip_popup", scenario.id),
cls: 'b-transparent',
icon: 'b-fa-file-export', // icono del excel para exportación
onClick: () => handleExcelExporter()//handleOnOpenProjectIdSchedulerSearchPopup()
},
]
},
{
type: 'buttongroup',
cls: 'tbar-right',
items: [
{
type: 'button',
ref: 'todayButton',
text: 'L{Header.today}',
cls: 'b-transparent',
onAction: (e, base = 'today') => handlePresetView(e, base)
},
{
type: 'button',
ref: 'hoursButton',
text: 'L{Header.day}',
cls: 'b-transparent',
onAction: (e, base = 'hourAndDay') => handlePresetView(e, base)
},
{
type: 'button',
ref: 'daysButton',
text: 'L{Header.week}',
cls: 'b-transparent',
onAction: (e, base = 'weekAndDay') => handlePresetView(e, base)
},
{
type: 'button',
ref: 'monthsButton',
text: 'L{Header.month}',
cls: 'b-transparent',
onAction: (e, base = 'weekAndMonth') => handlePresetView(e, base)
},
{
type: 'button',
icon: 'b-fa-angle-left',
tooltip: 'L{Header.previousDate}',
cls: 'b-transparent',
onAction: () => handleShiftPrevious()
},
{
type: 'button',
icon: 'b-fa-angle-right',
tooltip: 'L{Header.nextDate}',
cls: 'b-transparent',
onAction: () => handleShiftNext()
}
]
}
],
listeners: {
eventDblClick({ eventRecord }) {
if (!eventRecord.type || eventRecord.type !== 'personal') {
eventRecord?.type === 'booking' ? handleBookingEditionClick(eventRecord.name) : handleTaskEditionClick(eventRecord.name)
} else {
return false
}
}
},
eventBodyTemplate: data =>
(data.eventType !== 'personal')
?
`
<div class="b-sch-event-header">${data.headerText}</div>
<div class="b-sch-event-footer">${data.footerText}</div>
`
:
''
,
eventRenderer({ eventRecord, resourceRecord, renderData }) {
if (eventRecord.cls.value === '' || eventRecord.cls.value === null) {
renderData.eventStyle = 'colored'
if (eventRecord.color !== null) {
renderData.eventColor = eventRecord.color
}
} else {
renderData.eventStyle = null
renderData.eventColor = null
}
return {
headerText: moment(eventRecord.startDate).format(this.localeManager.locale.PresetManager[this.viewPreset.base].displayDateFormat),
footerText: eventRecord.name || '',
eventType: !!eventRecord.type ? eventRecord.type : ''
}
}
};
return (
<>
<BryntumScheduler
ref={scheduler} {...schedulerConfig}
style={{ headerHeight: '1rem' }}
onSelectionChange={selectionChangeHandler}
></BryntumScheduler>
<Popup
openPopup={openResourcesSchedulerSearchPopup}
setOpenPopup={setOpenResourcesSchedulerSearchPopup}
title={onTranslation(language.id, fieldsPage, 'popup_resourceSearch_title', scenario.id)}
maxWidth={"sm"}
>
<TaskSchedulerSearchResourcesPopup
fieldsPage={fieldsPage}
translation={translation}
language={language}
scenario={scenario}
localization={localization}
userConfiguration={userConfiguration}
companies={companies}
clientsClassesScenaries={clientsClassesScenaries}
onTranslation={onTranslation}
messages={messages}
usersCostCenters={usersCostCenters}
handleOnPopupSearch={handleOnPopupSearch}
searchModel={searchModel}
cecos={cecos}
setCecos={setCecos}
/>
</Popup>
<Popup
openPopup={openResourcesProjectIdSearchPopup}
setOpenPopup={setOpenResourcesProjectIdSearchPopup}
title={onTranslation(language.id, fieldsPage, 'popup_resourceSearch_title', scenario.id)}
maxWidth={"sm"}
>
<TaskSchedulerSearchProjectIdPopup
fieldsPage={fieldsPage}
translation={translation}
language={language}
scenario={scenario}
onTranslation={onTranslation}
handleOnPopupSearch={handleOnPopupSearchByProjectId}
searchModel={searchModel}
projectId={projectId}
handleClearProjectId={handleClearProjectId}
setOpenResourcesProjectIdSearchPopup={setOpenResourcesProjectIdSearchPopup}
/>
</Popup>
</>
)
}
export default React.memo(SchedulerPro)