import Container from '@/components/shared/Container' import { DX_CLASSNAMES } from '@/constants/app.constant' import { GridDto, PlatformEditorTypes, UiLookupDataSourceTypeEnum } from '@/proxy/form/models' import { useLocalization } from '@/utils/hooks/useLocalization' import Scheduler, { Editing, Item, Resource, SchedulerRef, Toolbar, View, } from 'devextreme-react/scheduler' import { useCallback, useEffect, useRef, useState } from 'react' import { Helmet } from 'react-helmet' import { getList } from '@/services/form.service' import { useListFormCustomDataSource } from './useListFormCustomDataSource' import { addCss, addJs } from './Utils' 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 CustomStore from 'devextreme/data/custom_store' import { Loading } from '@/components/shared' import { usePermission } from '@/utils/hooks/usePermission' import { useListFormColumns } from './useListFormColumns' import { EditingFormItemDto } from '@/proxy/form/models' import useResponsive from '@/utils/hooks/useResponsive' import { SimpleItemWithColData } from '../form/types' interface SchedulerViewProps { listFormCode: string searchParams?: URLSearchParams isSubForm?: boolean level?: number refreshData?: () => Promise gridDto?: GridDto } const SchedulerView = (props: SchedulerViewProps) => { const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props const { translate } = useLocalization() const isPwaMode = usePWA() const schedulerRef = useRef() const refListFormCode = useRef('') const widgetGroupRef = useRef(null) const { checkPermission } = usePermission() const { smaller } = useResponsive() const [schedulerDataSource, setSchedulerDataSource] = useState>() const [gridDto, setGridDto] = useState() const [widgetGroupHeight, setWidgetGroupHeight] = useState(0) const [currentView, setCurrentView] = useState('week') const [isPopupFullScreen, setIsPopupFullScreen] = useState(false) const layout = layoutTypes.scheduler || 'scheduler' useEffect(() => { const initializeScheduler = async () => { const response = await getList({ listFormCode }) setGridDto(response.data) } if (extGridDto === undefined) { initializeScheduler() } else { setGridDto(extGridDto) } setCurrentView(extGridDto?.gridOptions.schedulerOptionDto?.defaultView || 'week') }, [listFormCode, extGridDto]) useEffect(() => { if (schedulerRef?.current) { const instance = schedulerRef?.current?.instance() if (instance) { instance.option('dataSource', undefined) } } if (refListFormCode.current !== listFormCode) { // Reset state if needed } }, [listFormCode]) const { createSelectDataSource } = useListFormCustomDataSource({ gridRef: schedulerRef }) const { getBandedColumns } = useListFormColumns({ gridDto, listFormCode, isSubForm, gridRef: schedulerRef, }) useEffect(() => { if (!gridDto) { return } // Set js and css const grdOpt = gridDto.gridOptions if (grdOpt.customJsSources.length) { for (const js of grdOpt.customJsSources) { addJs(js) } } if (grdOpt.customStyleSources.length) { for (const css of grdOpt.customStyleSources) { addCss(css) } } }, [gridDto]) useEffect(() => { if (!gridDto) return const dataSource = createSelectDataSource( gridDto.gridOptions, listFormCode, searchParams, layout, undefined, ) setSchedulerDataSource(dataSource) }, [gridDto, searchParams, createSelectDataSource]) useEffect(() => { refListFormCode.current = listFormCode }, [listFormCode]) // WidgetGroup yüksekliğini hesapla useEffect(() => { const calculateWidgetHeight = () => { if (widgetGroupRef.current) { const height = widgetGroupRef.current.offsetHeight setWidgetGroupHeight(height) } } calculateWidgetHeight() const resizeObserver = new ResizeObserver(calculateWidgetHeight) if (widgetGroupRef.current) { resizeObserver.observe(widgetGroupRef.current) } return () => { resizeObserver.disconnect() } }, [gridDto?.widgets]) const settingButtonClick = useCallback(() => { window.open( ROUTES_ENUM.protected.saas.listFormManagement.edit.replace(':listFormCode', listFormCode), isPwaMode ? '_self' : '_blank', ) }, [listFormCode, isPwaMode]) const handleRefresh = useCallback(() => { const instance = schedulerRef.current?.instance() if (instance) { const dataSource = instance.getDataSource() if (dataSource) { dataSource.reload() } } }, []) const onCurrentViewChange = useCallback((value: string) => { setCurrentView(value) }, []) const onAppointmentFormOpening = useCallback( (e: any) => { if (!gridDto) return // Popup ayarlarını her açılışta yeniden yapılandır e.popup.option('title', translate('::' + gridDto.gridOptions.editingOptionDto?.popup?.title)) e.popup.option('showTitle', gridDto.gridOptions.editingOptionDto?.popup?.showTitle) e.popup.option('width', gridDto.gridOptions.editingOptionDto?.popup?.width || 800) e.popup.option('height', gridDto.gridOptions.editingOptionDto?.popup?.height || 600) e.popup.option('resizeEnabled', gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled) e.popup.option('fullScreen', isPopupFullScreen) // Toolbar butonlarını ekle const toolbarItems = [ { widget: 'dxButton', toolbar: 'bottom', location: 'after', options: { text: translate('::Save'), type: 'default', onClick: async () => { const formInstance = e.form const validationResult = formInstance.validate() if (validationResult.isValid) { // Form verilerini al const formData = formInstance.option('formData') try {0 // Scheduler instance'ını al const scheduler = schedulerRef.current?.instance() if (e.appointmentData && scheduler) { // Yeni appointment mı yoksa güncelleme mi kontrol et if ( e.appointmentData.id || e.appointmentData[gridDto.gridOptions.keyFieldName || 'id'] ) { // Güncelleme await scheduler.updateAppointment(e.appointmentData, formData) } else { // Yeni ekleme await scheduler.addAppointment(formData) } // Popup'ı kapat e.popup.hide() // RefreshData varsa çağır if (props.refreshData) { await props.refreshData() } } } catch (error) { console.error('Appointment save error:', error) } } }, }, }, { widget: 'dxButton', toolbar: 'bottom', location: 'after', options: { text: translate('::Cancel'), onClick: () => { e.cancel = true e.popup.hide() }, }, }, { widget: 'dxButton', toolbar: 'top', location: 'after', options: { icon: 'fullscreen', hint: translate('::Tam Ekran'), stylingMode: 'text', onClick: () => { // Popup'tan mevcut fullScreen durumunu al const currentFullScreen = e.popup.option('fullScreen') const newFullScreenState = !currentFullScreen // State'i güncelle setIsPopupFullScreen(newFullScreenState) // Popup'ı güncelle e.popup.option('fullScreen', newFullScreenState) // Width ve height'i da güncelle if (newFullScreenState) { e.popup.option('width', '100%') e.popup.option('height', '100%') } else { e.popup.option('width', gridDto.gridOptions.editingOptionDto?.popup?.width || 600) e.popup.option('height', gridDto.gridOptions.editingOptionDto?.popup?.height || 'auto') } // Button icon ve hint'i güncelle const button = e.popup._$element.find('.dx-toolbar-after .dx-button').last() if (button.length) { const buttonInstance = button.dxButton('instance') if (buttonInstance) { buttonInstance.option('icon', newFullScreenState ? 'collapse' : 'fullscreen') buttonInstance.option('hint', newFullScreenState ? translate('::Normal Boyut') : translate('::Tam Ekran')) } } }, }, }, ] e.popup.option('toolbarItems', toolbarItems) // EditingFormDto'dan form items oluştur const formItems: any[] = [] if (gridDto.gridOptions.editingFormDto?.length > 0) { const sortedFormDto = gridDto.gridOptions.editingFormDto .slice() .sort((a: any, b: any) => (a.order >= b.order ? 1 : -1)) sortedFormDto.forEach((group: any) => { // Items'ları da order'a göre sırala const sortedItems = (group.items || []) .slice() .sort((a: any, b: any) => (a.order >= b.order ? 1 : -1)) const groupItems = sortedItems.map((i: EditingFormItemDto) => { let editorOptions: any = {} try { if (i.editorOptions) { editorOptions = JSON.parse(i.editorOptions) } } catch {} const fieldName = i.dataField.split(':')[0] const listFormField = gridDto.columnFormats.find((x: any) => x.fieldName === fieldName) // EditorType belirleme - Grid'deki gibi let editorType: any = i.editorType2 || i.editorType if (i.editorType2 === PlatformEditorTypes.dxGridBox) { editorType = 'dxDropDownBox' } else if (i.editorType2) { editorType = i.editorType2 } // Lookup DataSource oluştur if (listFormField?.lookupDto) { const lookup = listFormField.lookupDto // EditorType'ı dxSelectBox olarak ayarla if (!editorType || editorType === 'dxTextBox') { editorType = 'dxSelectBox' } if (lookup.dataSourceType === UiLookupDataSourceTypeEnum.Query) { editorOptions.dataSource = new CustomStore({ key: 'key', loadMode: 'raw', load: async () => { try { const { dynamicFetch } = await import('@/services/form.service') const response = await dynamicFetch('list-form-select/lookup', 'POST', null, { listFormCode, listFormFieldName: fieldName, filters: [], }) return (response.data ?? []).map((a: any) => ({ key: a.Key, name: a.Name, group: a.Group, })) } catch (error) { console.error('Lookup load error:', error) return [] } }, }) editorOptions.valueExpr = 'key' editorOptions.displayExpr = 'name' } else if (lookup.dataSourceType === UiLookupDataSourceTypeEnum.StaticData) { if (lookup.lookupQuery) { try { const staticData = JSON.parse(lookup.lookupQuery) editorOptions.dataSource = staticData editorOptions.valueExpr = lookup.valueExpr || 'key' editorOptions.displayExpr = lookup.displayExpr || 'name' } catch (error) { console.error('Static data parse error:', error) } } } } // Validation rules const validationRules: any[] = [] if (i.isRequired) { validationRules.push({ type: 'required' }) } const item: any = { dataField: i.dataField, name: i.dataField, editorType, colSpan: i.colSpan, editorOptions, validationRules: validationRules.length > 0 ? validationRules : undefined, } // Label sadece caption varsa ekle if (listFormField?.captionName) { item.label = { text: translate('::' + listFormField.captionName) } } return item }) if (group.itemType === 'group') { // Grup kullanmadan direkt items'ları ekle - form'un colCount'u geçerli olsun formItems.push(...groupItems) } else if (group.itemType === 'tabbed') { formItems.push({ itemType: 'tabbed', tabs: (group.tabs || []).map((tab: any) => { // Tab items'larını da order'a göre sırala const sortedTabItems = (tab.items || []) .slice() .sort((a: any, b: any) => (a.order >= b.order ? 1 : -1)) return { title: translate('::' + tab.title), colCount: tab.colCount || 2, items: sortedTabItems.map((i: EditingFormItemDto) => { // Tab içindeki itemlar için de aynı mapping let editorOptions: any = {} try { if (i.editorOptions) { editorOptions = JSON.parse(i.editorOptions) } } catch {} const fieldName = i.dataField.split(':')[0] const listFormField = gridDto.columnFormats.find( (x: any) => x.fieldName === fieldName, ) // EditorType belirleme - Grid'deki gibi let editorType: any = i.editorType2 || i.editorType if (i.editorType2 === PlatformEditorTypes.dxGridBox) { editorType = 'dxDropDownBox' } else if (i.editorType2) { editorType = i.editorType2 } if (listFormField?.lookupDto) { const lookup = listFormField.lookupDto // EditorType'ı dxSelectBox olarak ayarla if (!editorType || editorType === 'dxTextBox') { editorType = 'dxSelectBox' } if (lookup.dataSourceType === UiLookupDataSourceTypeEnum.Query) { editorOptions.dataSource = new CustomStore({ key: 'key', loadMode: 'raw', load: async () => { try { const { dynamicFetch } = await import('@/services/form.service') const response = await dynamicFetch( 'list-form-select/lookup', 'POST', null, { listFormCode, listFormFieldName: fieldName, filters: [], }, ) return (response.data ?? []).map((a: any) => ({ key: a.Key, name: a.Name, group: a.Group, })) } catch (error) { console.error('Lookup load error:', error) return [] } }, }) editorOptions.valueExpr = 'key' editorOptions.displayExpr = 'name' } else if (lookup.dataSourceType === UiLookupDataSourceTypeEnum.StaticData) { if (lookup.lookupQuery) { try { const staticData = JSON.parse(lookup.lookupQuery) editorOptions.dataSource = staticData editorOptions.valueExpr = lookup.valueExpr || 'key' editorOptions.displayExpr = lookup.displayExpr || 'name' } catch (error) { console.error('Static data parse error:', error) } } } } const validationRules: any[] = [] if (i.isRequired) { validationRules.push({ type: 'required' }) } const item: any = { dataField: i.dataField, name: i.dataField, editorType, colSpan: i.colSpan, editorOptions, validationRules: validationRules.length > 0 ? validationRules : undefined, } // Label sadece caption varsa ekle if (listFormField?.captionName) { item.label = { text: translate('::' + listFormField.captionName) } } return item }), } }), }) } else { // No group, add items directly formItems.push(...groupItems) } }) } // Form'u tamamen yeniden yapılandır const formConfig = { colCount: gridDto.gridOptions.editingFormDto?.[0]?.colCount || 2, showValidationSummary: false, items: formItems, } // Form'u tamamen yeniden baştan yapılandır e.form.option('colCount', formConfig.colCount) e.form.option('showValidationSummary', formConfig.showValidationSummary) e.form.option('items', formConfig.items) // Form'u repaint et e.form.repaint() }, [gridDto, translate, isPopupFullScreen, listFormCode], ) return ( <>
{!isSubForm && ( )} {!gridDto && (
Loading scheduler configuration...
)} {gridDto && !schedulerDataSource && (
Loading data source...
)} {gridDto && schedulerDataSource && ( <>
{ props.refreshData?.() }} onAppointmentUpdating={() => { props.refreshData?.() }} onAppointmentDeleting={() => { props.refreshData?.() }} height={ gridDto.gridOptions.height > 0 ? gridDto.gridOptions.height : gridDto.gridOptions.fullHeight ? `calc(100vh - ${170 + widgetGroupHeight}px)` : undefined } showAllDayPanel={gridDto.gridOptions.schedulerOptionDto?.showAllDayPanel ?? true} crossScrollingEnabled={ gridDto.gridOptions.schedulerOptionDto?.crossScrollingEnabled ?? false } cellDuration={gridDto.gridOptions.schedulerOptionDto?.cellDuration || 30} firstDayOfWeek={ (gridDto.gridOptions.schedulerOptionDto?.firstDayOfWeek as | 0 | 1 | 2 | 3 | 4 | 5 | 6) || 1 } adaptivityEnabled={true} > {checkPermission(gridDto?.gridOptions.permissionDto.u) && ( )} {gridDto.gridOptions.schedulerOptionDto?.resources?.map((resource, index) => ( ))}
)}
) } export default SchedulerView