import Container from '@/components/shared/Container' import { Dialog, Notification, toast } from '@/components/ui' import { DX_CLASSNAMES } from '@/constants/app.constant' import { DbTypeEnum, EditingFormItemDto, GridDto, ListFormCustomizationTypeEnum, PlatformEditorTypes, } from '@/proxy/form/models' import { getList } from '@/services/form.service' import { getListFormCustomization, postListFormCustomization, } from '@/services/list-form-customization.service' import { useListFormColumns } from '@/shared/useListFormColumns' import { useListFormCustomDataSource } from '@/shared/useListFormCustomDataSource' import { useLocalization } from '@/utils/hooks/useLocalization' import useResponsive from '@/utils/hooks/useResponsive' import { Template } from 'devextreme-react/core/template' import DataGrid, { ColumnChooser, ColumnFixing, DataGridTypes, Editing, Export, FilterPanel, FilterRow, Grouping, GroupItem as GroupItemDx, GroupPanel, HeaderFilter, IStateStoringProps, LoadPanel, Pager, Scrolling, SearchPanel, Selection, Sorting, Summary, Toolbar, TotalItem, } from 'devextreme-react/data-grid' import { Item } from 'devextreme-react/toolbar' import { DataType } from 'devextreme/common' import { captionize } from 'devextreme/core/utils/inflector' import CustomStore from 'devextreme/data/custom_store' import { GroupItem } from 'devextreme/ui/form' import { useCallback, useEffect, useRef, useState } from 'react' import { Helmet } from 'react-helmet' import SubForms from '../form/SubForms' import { RowMode, SimpleItemWithColData } from '../form/types' import { GridColumnData } from './GridColumnData' import GridFilterDialogs from './GridFilterDialogs' import { addCss, addJs, controlStyleCondition, setFormEditingExtraItemValues, setGridPanelColor, } from './Utils' import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent' import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent' import { useFilters } from './useFilters' import { useToolbar } from './useToolbar' import { ImportDashboard } from '@/components/importManager/ImportDashboard' interface GridProps { listFormCode: string searchParams?: URLSearchParams isSubForm?: boolean level?: number refreshData?: () => Promise } const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)' // kullanici tanimli gridState ile islem gormus gridin paneline ait renk const Grid = (props: GridProps) => { const { listFormCode, searchParams, isSubForm, level } = props const { translate } = useLocalization() const { smaller } = useResponsive() const gridRef = useRef() const refListFormCode = useRef('') const [gridDto, setGridDto] = useState() const [gridDataSource, setGridDataSource] = useState>() const [columnData, setColumnData] = useState() const [formData, setFormData] = useState() const [mode, setMode] = useState('view') const preloadExportLibs = () => { import('exceljs') import('file-saver') import('devextreme/excel_exporter') import('jspdf') import('devextreme/pdf_exporter') } const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({ gridDto, listFormCode, getSelectedRowKeys, getSelectedRowsData, refreshData, getFilter, }) const { filterToolbarData, ...filterData } = useFilters({ gridDto, gridRef, listFormCode, }) const { createSelectDataSource } = useListFormCustomDataSource({ gridRef }) const { getBandedColumns } = useListFormColumns({ gridDto, listFormCode, isSubForm, }) async function getSelectedRowKeys() { const grd = gridRef.current?.instance if (!grd) { return [] } return await grd.getSelectedRowKeys() } function getSelectedRowsData() { const grd = gridRef.current?.instance if (!grd) { return [] } return grd.getSelectedRowsData() } function refreshData() { gridRef.current?.instance.refresh() } function getFilter() { const grd = gridRef.current?.instance if (!grd) { return } return grd.getCombinedFilter() } function onSelectionChanged(data: any) { const grdOpt = gridDto?.gridOptions const grd = gridRef.current?.instance if (!grdOpt || !grd) { return } // kullanicinin yetkisi varsa ve birden fazla kayit secili ise coklu silme gorunsun if (grdOpt.editingOptionDto?.allowDeleting) { // && abp.auth.isGranted(grdOpt.permissionDto?.d) // kullanicinin silme yetkisi var ise const opt = grd.option('toolbar') const deleteSelectedRecordsIndex = opt?.items ?.map((e: any) => e.name) .indexOf('deleteSelectedRecords') // deleteSelectedRecords ismindeki custom butonun index degerini bul grd.option( `toolbar.items[${deleteSelectedRecordsIndex}].options.visible`, data.selectedRowsData.length > 1, ) // birden fazla kayit secilmis ise gorunsun } // SubForm'ları gösterebilmek için secili satiri formData'ya at if (data.selectedRowsData.length) { setFormData(data.selectedRowsData[0]) } } function onCellPrepared(e: any) { const columnFormats = gridDto?.columnFormats if (!columnFormats) { return } // satir, hucre yada header vb. kisimlara conditional style uygulamak icin for (let indxCol = 0; indxCol < columnFormats.length; indxCol++) { const colFormat = columnFormats[indxCol] for (let indxStyl = 0; indxStyl < colFormat.columnStylingDto.length; indxStyl++) { const colStyle = colFormat.columnStylingDto[indxStyl] // uygulanacak style if (e.rowType == colStyle.rowType) { // header, filter, data, group, summaries ..her birisine style uygulanabilir // style bütün satıra uygulansın olarak seçili ise yada sadece ilgili field üzerinde ise if (colStyle.useRow || e.column.dataField == colFormat.fieldName) { if ( !colStyle.conditionValue || controlStyleCondition(e.data, colFormat.fieldName, colStyle) ) { // css sınıf ismi var ise uygula if (colStyle.cssClassName) { e.cellElement.addClass(colStyle.cssClassName) } // css inline style var ise uygula if (colStyle.cssStyles) { e.cellElement.attr('style', e.cellElement.attr('style') + ';' + colStyle.cssStyles) } } } } } } } function onInitNewRow(e: any) { if (!gridDto?.columnFormats) { return } setMode('new') for (const colFormat of gridDto?.columnFormats) { if (!colFormat.fieldName) { continue } // Grid'den gelen columnFormat'ları kullanarak default değerleri set et if (colFormat.defaultValue != null) { e.data[colFormat.fieldName] = colFormat.defaultValue } // URL'den veya Component Prop'dan gelen parametreleri set et if (!searchParams) { continue } if (searchParams.has(colFormat.fieldName)) { const dType = colFormat.dataType as DataType switch (dType) { case 'date': case 'datetime': e.data[colFormat.fieldName] = new Date(searchParams.get(colFormat.fieldName)!) break case 'number': e.data[colFormat.fieldName] = Number(searchParams.get(colFormat.fieldName)) break case 'boolean': if (searchParams.get(colFormat.fieldName) === 'true') { e.data[colFormat.fieldName] = true } else if (searchParams.get(colFormat.fieldName) === 'false') { e.data[colFormat.fieldName] = false } break case 'object': try { e.data[colFormat.fieldName] = JSON.parse( searchParams.get(colFormat.fieldName) as string, ) } catch (error) {} break default: e.data[colFormat.fieldName] = searchParams.get(colFormat.fieldName) break } } } } function onRowInserting(e: DataGridTypes.RowInsertingEvent) { e.data = setFormEditingExtraItemValues(e.data) } function onRowUpdating(e: DataGridTypes.RowUpdatingEvent) { if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) { if (Object.keys(e.newData).some((a) => a.includes(':'))) { Object.keys(e.oldData).forEach((col) => { if (col.includes(':')) { e.newData[col] = e.newData[col] ?? e.oldData[col] } }) } e.newData = setFormEditingExtraItemValues(e.newData) } else { let newData = { ...e.oldData, ...e.newData } newData = setFormEditingExtraItemValues(newData) Object.keys(newData).forEach((key) => { if (key.includes(':')) { delete newData[key] } }) e.newData = newData } if (gridDto?.gridOptions.keyFieldName) { delete e.newData[gridDto?.gridOptions.keyFieldName] } } function onEditingStart(e: DataGridTypes.EditingStartEvent) { setMode('edit') const columns = e.component.option('columns') as GridColumnData[] // FormEditingExtraItem field ise datayı doldur columns?.forEach((col) => { if (!col.dataField?.includes(':')) { return } const field = col.dataField.split(':') if (!e.data[field[0]]) { return } const json = JSON.parse(e.data[field[0]]) e.data[col.dataField] = json[field[1]] }) } function onDataErrorOccurred(e: DataGridTypes.DataErrorOccurredEvent) { toast.push( {e.error?.message} , { placement: 'top-end', }, ) } const customSaveState = useCallback( (state: any) => postListFormCustomization({ listFormCode: listFormCode, customizationType: ListFormCustomizationTypeEnum.GridState, filterName: `list-${gridRef.current?.instance.option('stateStoring')?.storageKey ?? ''}`, customizationData: JSON.stringify(state), }).then(() => { setGridPanelColor(statedGridPanelColor) }), [listFormCode], ) const customLoadState = useCallback( () => getListFormCustomization( listFormCode, ListFormCustomizationTypeEnum.GridState, `list-${gridRef.current?.instance.option('stateStoring')?.storageKey ?? ''}`, ).then((response: any) => { setGridPanelColor(statedGridPanelColor) if (response.data?.length > 0) { return JSON.parse(response.data[0].customizationData) } }), [listFormCode], ) useEffect(() => { if (gridRef?.current) { gridRef.current.instance.option('columns', undefined) gridRef.current.instance.option('remoteOperations', false) gridRef.current.instance.option('dataSource', undefined) gridRef.current.instance.state(null) } const initializeGrid = async () => { const response = await getList({ listFormCode }) setGridDto(response.data) } if (refListFormCode.current !== listFormCode) { initializeGrid() } }, [listFormCode]) 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 } // Set columns const cols = getBandedColumns() setColumnData(cols) // Set data source const dataSource: CustomStore = createSelectDataSource( gridDto.gridOptions, listFormCode, searchParams, cols, ) setGridDataSource(dataSource) }, [gridDto, searchParams]) useEffect(() => { refListFormCode.current = listFormCode if (!gridRef?.current) { return } gridRef.current.instance.option('remoteOperations', { groupPaging: true }) gridRef.current.instance.option('columns', columnData) gridRef.current.instance.option('dataSource', gridDataSource) const stateStoring: IStateStoringProps = { enabled: gridDto?.gridOptions.stateStoringDto?.enabled, type: gridDto?.gridOptions.stateStoringDto?.type, savingTimeout: gridDto?.gridOptions.stateStoringDto?.savingTimeout, storageKey: gridDto?.gridOptions.stateStoringDto?.storageKey, } if ( gridDto?.gridOptions.stateStoringDto?.enabled && gridDto?.gridOptions.stateStoringDto?.type === 'custom' ) { stateStoring.customSave = customSaveState stateStoring.customLoad = customLoadState } gridRef.current.instance.option('stateStoring', stateStoring) }, [columnData]) const onExporting = async (e: DataGridTypes.ExportingEvent) => { // DevExtreme’in varsayılan export davranışını iptal ediyoruz; kendi akışımızı çalıştıracağız e.cancel = true const grid = gridRef?.current?.instance if (!grid) return try { if (e.format === 'xlsx' || e.format === 'csv') { // exceljs + file-saver + devextreme excel exporter => ihtiyaç anında yükle const [{ Workbook }, { saveAs }, { exportDataGrid: exportDataExcel }] = await Promise.all([ import('exceljs'), import('file-saver'), import('devextreme/excel_exporter'), ]) const workbook = new Workbook() const worksheet = workbook.addWorksheet(`${listFormCode}_sheet`) await exportDataExcel({ component: grid, worksheet, autoFilterEnabled: true, }) if (e.format === 'xlsx') { const buffer = await workbook.xlsx.writeBuffer() saveAs( new Blob([buffer], { type: 'application/octet-stream' }), `${listFormCode}_export.xlsx`, ) } else { const buffer = await workbook.csv.writeBuffer() saveAs( new Blob([buffer], { type: 'application/octet-stream' }), `${listFormCode}_export.csv`, ) } } else if (e.format === 'pdf') { // jspdf + devextreme pdf exporter => ihtiyaç anında yükle const [jspdfMod, { exportDataGrid: exportDataPdf }] = await Promise.all([ import('jspdf'), import('devextreme/pdf_exporter'), ]) // jsPDF bazı paketlemelerde default, bazılarında named export olarak gelir const JsPDFCtor = (jspdfMod as any).default ?? (jspdfMod as any).jsPDF const doc = new JsPDFCtor({}) await exportDataPdf({ jsPDFDocument: doc, component: grid, indent: 5, }) doc.save(`${listFormCode}_export.pdf`) } } catch (err) { console.error('Export error:', err) toast.push( {translate('::App.Common.ExportError') ?? 'Dışa aktarma sırasında hata oluştu.'} , { placement: 'top-end' }, ) } } return ( {!isSubForm && ( )} {gridDto && columnData && ( <> { setMode('view') }} onSaved={() => { setMode('view') }} onRowInserted={() => { props.refreshData?.() }} onRowUpdated={() => { props.refreshData?.() }} onRowRemoved={() => { props.refreshData?.() }} > 0 ? gridDto.gridOptions.editingFormDto ?.sort((a: any, b: any) => { return a.order >= b.order ? 1 : -1 }) .map((e: any) => { return { itemType: e.itemType, colCount: e.colCount, colSpan: e.colSpan, caption: e.caption, items: e.items ?.sort((a: any, b: any) => { return a.order >= b.order ? 1 : -1 }) .map((i: EditingFormItemDto) => { let editorOptions = {} try { editorOptions = i.editorOptions && JSON.parse(i.editorOptions) // Eğer default value varsa, bu editörü readonly yapıyoruz if (searchParams?.has(i.dataField)) { editorOptions = { ...editorOptions, readOnly: true, } } } catch {} const fieldName = i.dataField.split(':')[0] const listFormField = gridDto.columnFormats.find( (x: any) => x.fieldName === fieldName, ) if (listFormField?.sourceDbType === DbTypeEnum.Date) { editorOptions = { ...{ type: 'date', dateSerializationFormat: 'yyyy-MM-dd', displayFormat: 'shortDate', }, ...editorOptions, } } else if ( listFormField?.sourceDbType === DbTypeEnum.DateTime || listFormField?.sourceDbType === DbTypeEnum.DateTime2 || listFormField?.sourceDbType === DbTypeEnum.DateTimeOffset ) { editorOptions = { ...{ type: 'datetime', dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ssxxx', displayFormat: 'shortDateShortTime', }, ...editorOptions, } } const item: SimpleItemWithColData = { canRead: listFormField?.canRead ?? false, canUpdate: listFormField?.canUpdate ?? false, canCreate: listFormField?.canCreate ?? false, canExport: listFormField?.canExport ?? false, dataField: i.dataField, name: i.dataField, editorType2: i.editorType2, editorType: i.editorType2 == PlatformEditorTypes.dxGridBox ? 'dxDropDownBox' : i.editorType2, colSpan: i.colSpan, isRequired: i.isRequired, editorOptions, } if (i.dataField.indexOf(':') >= 0) { item.label = { text: captionize(i.dataField.split(':')[1]) } } if ( (mode == 'edit' && !item.canUpdate) || (mode == 'new' && !item.canCreate) ) { item.editorOptions = { ...item.editorOptions, readOnly: true, } } return item }) .filter((a: any) => { // return a.canRead if (mode === 'view') { return a.canRead } else if (mode === 'new') { return a.canCreate || a.canRead } else if (mode === 'edit') { return a.canUpdate || a.canRead } else { return false } }), } as GroupItem }) : undefined, }} >