CardListView ve css düzenlemesi
This commit is contained in:
parent
0d69ae05ce
commit
9e4c95d120
73 changed files with 1093 additions and 913598 deletions
|
|
@ -239,7 +239,15 @@
|
|||
"dx.material.purple.light.compact": "App.Setting.material.purple.light.compact",
|
||||
"dx.material.purple.dark.compact": "App.Setting.material.purple.dark.compact",
|
||||
"dx.material.teal.light.compact": "App.Setting.material.teal.light.compact",
|
||||
"dx.material.teal.dark.compact": "App.Setting.material.teal.dark.compact"
|
||||
"dx.material.teal.dark.compact": "App.Setting.material.teal.dark.compact",
|
||||
"dx.fluent.blue.dark.compact": "App.Setting.fluent.blue.dark.compact",
|
||||
"dx.fluent.blue.dark": "App.Setting.fluent.blue.dark",
|
||||
"dx.fluent.blue.light.compact": "App.Setting.fluent.blue.light.compact",
|
||||
"dx.fluent.blue.light": "App.Setting.fluent.blue.light",
|
||||
"dx.fluent.saas.dark.compact": "App.Setting.fluent.saas.dark.compact",
|
||||
"dx.fluent.saas.dark": "App.Setting.fluent.saas.dark",
|
||||
"dx.fluent.saas.light.compact": "App.Setting.fluent.saas.light.compact",
|
||||
"dx.fluent.saas.light": "App.Setting.fluent.saas.light"
|
||||
},
|
||||
"order": 3
|
||||
},
|
||||
|
|
@ -2263,4 +2271,4 @@
|
|||
"IsActive": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -232,6 +232,14 @@ public static class PlatformConsts
|
|||
{ "dx.material.purple.dark.compact",$"{Prefix.App}.Setting.material.purple.dark.compact" },
|
||||
{ "dx.material.teal.light.compact", $"{Prefix.App}.Setting.material.teal.light.compact" },
|
||||
{ "dx.material.teal.dark.compact", $"{Prefix.App}.Setting.material.teal.dark.compact" },
|
||||
{ "dx.fluent.blue.dark.compact", $"{Prefix.App}.Setting.fluent.blue.dark.compact" },
|
||||
{ "dx.fluent.blue.dark", $"{Prefix.App}.Setting.fluent.blue.dark" },
|
||||
{ "dx.fluent.blue.light.compact", $"{Prefix.App}.Setting.fluent.blue.light.compact" },
|
||||
{ "dx.fluent.blue.light", $"{Prefix.App}.Setting.fluent.blue.light" },
|
||||
{ "dx.fluent.saas.dark.compact", $"{Prefix.App}.Setting.fluent.saas.dark.compact" },
|
||||
{ "dx.fluent.saas.dark", $"{Prefix.App}.Setting.fluent.saas.dark" },
|
||||
{ "dx.fluent.saas.light.compact", $"{Prefix.App}.Setting.fluent.saas.light.compact" },
|
||||
{ "dx.fluent.saas.light", $"{Prefix.App}.Setting.fluent.saas.light" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,6 +263,14 @@ public static class SeedConsts
|
|||
{ "dx.material.purple.dark.compact",$"{Prefix.App}.Setting.material.purple.dark.compact" },
|
||||
{ "dx.material.teal.light.compact", $"{Prefix.App}.Setting.material.teal.light.compact" },
|
||||
{ "dx.material.teal.dark.compact", $"{Prefix.App}.Setting.material.teal.dark.compact" },
|
||||
{ "dx.fluent.blue.dark.compact", $"{Prefix.App}.Setting.fluent.blue.dark.compact" },
|
||||
{ "dx.fluent.blue.dark", $"{Prefix.App}.Setting.fluent.blue.dark" },
|
||||
{ "dx.fluent.blue.light.compact", $"{Prefix.App}.Setting.fluent.blue.light.compact" },
|
||||
{ "dx.fluent.blue.light", $"{Prefix.App}.Setting.fluent.blue.light" },
|
||||
{ "dx.fluent.saas.dark.compact", $"{Prefix.App}.Setting.fluent.saas.dark.compact" },
|
||||
{ "dx.fluent.saas.dark", $"{Prefix.App}.Setting.fluent.saas.dark" },
|
||||
{ "dx.fluent.saas.light.compact", $"{Prefix.App}.Setting.fluent.saas.light.compact" },
|
||||
{ "dx.fluent.saas.light", $"{Prefix.App}.Setting.fluent.saas.light" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
ui/package-lock.json
generated
10
ui/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "erp-platform-ui",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "erp-platform-ui",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"dependencies": {
|
||||
"@babel/generator": "^7.28.3",
|
||||
"@babel/parser": "^7.28.0",
|
||||
|
|
@ -30,6 +30,7 @@
|
|||
"classnames": "^2.5.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"devextreme": "25.1.7",
|
||||
"devextreme-dist": "25.1.7",
|
||||
"devextreme-react": "25.1.7",
|
||||
"easy-peasy": "^6.0.5",
|
||||
"emoji-picker-react": "^4.14.1",
|
||||
|
|
@ -5783,6 +5784,11 @@
|
|||
"devextreme-bundler-init": "bin/bundler-init.js"
|
||||
}
|
||||
},
|
||||
"node_modules/devextreme-dist": {
|
||||
"version": "25.1.7",
|
||||
"resolved": "https://registry.npmjs.org/devextreme-dist/-/devextreme-dist-25.1.7.tgz",
|
||||
"integrity": "sha512-pgTiadUS239aL8RJo9aO+H0DzCaufM/pp+oY6C1Oo36Wk2/zScUNmjAytJDfEeHOm/eChXi3EtLn1FHvHstlcQ=="
|
||||
},
|
||||
"node_modules/devextreme-quill": {
|
||||
"version": "1.7.6",
|
||||
"resolved": "https://registry.npmjs.org/devextreme-quill/-/devextreme-quill-1.7.6.tgz",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "erp-platform-ui",
|
||||
"private": true,
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"elstarVersion": "2.1.6",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
|
@ -38,6 +38,7 @@
|
|||
"classnames": "^2.5.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"devextreme": "25.1.7",
|
||||
"devextreme-dist": "25.1.7",
|
||||
"devextreme-react": "25.1.7",
|
||||
"easy-peasy": "^6.0.5",
|
||||
"emoji-picker-react": "^4.14.1",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,9 +1,9 @@
|
|||
/**
|
||||
* DevExtreme (dx.common.css)
|
||||
* Version: 23.1.5
|
||||
* Build date: Wed Aug 30 2023
|
||||
* Version: 25.1.7
|
||||
* Build date: Mon Nov 10 2025
|
||||
*
|
||||
* Copyright (c) 2012 - 2023 Developer Express Inc. ALL RIGHTS RESERVED
|
||||
* Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
|
||||
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
|
||||
*/
|
||||
/*!
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
10
ui/public/css/dx.fluent.blue.dark.compact.css
Normal file
10
ui/public/css/dx.fluent.blue.dark.compact.css
Normal file
File diff suppressed because one or more lines are too long
10
ui/public/css/dx.fluent.blue.dark.css
Normal file
10
ui/public/css/dx.fluent.blue.dark.css
Normal file
File diff suppressed because one or more lines are too long
10
ui/public/css/dx.fluent.blue.light.compact.css
Normal file
10
ui/public/css/dx.fluent.blue.light.compact.css
Normal file
File diff suppressed because one or more lines are too long
10
ui/public/css/dx.fluent.blue.light.css
Normal file
10
ui/public/css/dx.fluent.blue.light.css
Normal file
File diff suppressed because one or more lines are too long
10
ui/public/css/dx.fluent.saas.dark.compact.css
Normal file
10
ui/public/css/dx.fluent.saas.dark.compact.css
Normal file
File diff suppressed because one or more lines are too long
10
ui/public/css/dx.fluent.saas.dark.css
Normal file
10
ui/public/css/dx.fluent.saas.dark.css
Normal file
File diff suppressed because one or more lines are too long
10
ui/public/css/dx.fluent.saas.light.compact.css
Normal file
10
ui/public/css/dx.fluent.saas.light.compact.css
Normal file
File diff suppressed because one or more lines are too long
10
ui/public/css/dx.fluent.saas.light.css
Normal file
10
ui/public/css/dx.fluent.saas.light.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
ui/public/css/fonts/Roboto-300.ttf
Normal file
BIN
ui/public/css/fonts/Roboto-300.ttf
Normal file
Binary file not shown.
BIN
ui/public/css/fonts/Roboto-300.woff
Normal file
BIN
ui/public/css/fonts/Roboto-300.woff
Normal file
Binary file not shown.
BIN
ui/public/css/fonts/Roboto-300.woff2
Normal file
BIN
ui/public/css/fonts/Roboto-300.woff2
Normal file
Binary file not shown.
BIN
ui/public/css/fonts/Roboto-400.ttf
Normal file
BIN
ui/public/css/fonts/Roboto-400.ttf
Normal file
Binary file not shown.
BIN
ui/public/css/fonts/Roboto-400.woff
Normal file
BIN
ui/public/css/fonts/Roboto-400.woff
Normal file
Binary file not shown.
BIN
ui/public/css/fonts/Roboto-400.woff2
Normal file
BIN
ui/public/css/fonts/Roboto-400.woff2
Normal file
Binary file not shown.
BIN
ui/public/css/fonts/Roboto-500.ttf
Normal file
BIN
ui/public/css/fonts/Roboto-500.ttf
Normal file
Binary file not shown.
BIN
ui/public/css/fonts/Roboto-500.woff
Normal file
BIN
ui/public/css/fonts/Roboto-500.woff
Normal file
Binary file not shown.
BIN
ui/public/css/fonts/Roboto-500.woff2
Normal file
BIN
ui/public/css/fonts/Roboto-500.woff2
Normal file
Binary file not shown.
BIN
ui/public/css/fonts/Roboto-700.ttf
Normal file
BIN
ui/public/css/fonts/Roboto-700.ttf
Normal file
Binary file not shown.
BIN
ui/public/css/fonts/Roboto-700.woff
Normal file
BIN
ui/public/css/fonts/Roboto-700.woff
Normal file
Binary file not shown.
BIN
ui/public/css/fonts/Roboto-700.woff2
Normal file
BIN
ui/public/css/fonts/Roboto-700.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
ui/public/css/icons/dxiconsfluent.ttf
Normal file
BIN
ui/public/css/icons/dxiconsfluent.ttf
Normal file
Binary file not shown.
BIN
ui/public/css/icons/dxiconsfluent.woff
Normal file
BIN
ui/public/css/icons/dxiconsfluent.woff
Normal file
Binary file not shown.
BIN
ui/public/css/icons/dxiconsfluent.woff2
Normal file
BIN
ui/public/css/icons/dxiconsfluent.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
828
ui/src/views/list/CardListView.tsx
Normal file
828
ui/src/views/list/CardListView.tsx
Normal file
|
|
@ -0,0 +1,828 @@
|
|||
import Container from '@/components/shared/Container'
|
||||
import { Dialog } from '@/components/ui'
|
||||
import { DX_CLASSNAMES } from '@/constants/app.constant'
|
||||
import {
|
||||
DbTypeEnum,
|
||||
EditingFormItemDto,
|
||||
GridDto,
|
||||
PlatformEditorTypes,
|
||||
} from '@/proxy/form/models'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import useResponsive from '@/utils/hooks/useResponsive'
|
||||
import { Template } from 'devextreme-react/core/template'
|
||||
import CardView, {
|
||||
CardViewRef,
|
||||
Editing,
|
||||
Pager,
|
||||
Paging,
|
||||
SearchPanel,
|
||||
Selection,
|
||||
LoadPanel,
|
||||
Sorting,
|
||||
} from 'devextreme-react/card-view'
|
||||
import { captionize } from 'devextreme/core/utils/inflector'
|
||||
import CustomStore from 'devextreme/data/custom_store'
|
||||
import { 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,
|
||||
autoNumber,
|
||||
GridExtraFilterState,
|
||||
} from './Utils'
|
||||
import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent'
|
||||
import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
|
||||
import { useFilters } from './useFilters'
|
||||
import { useToolbar } from './useToolbar'
|
||||
import { ImportDashboard } from '@/components/importManager/ImportDashboard'
|
||||
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
|
||||
import { GridExtraFilterToolbar } from './GridExtraFilterToolbar'
|
||||
import { getList } from '@/services/form.service'
|
||||
import { layoutTypes } from '../admin/listForm/edit/types'
|
||||
import { useListFormCustomDataSource } from './useListFormCustomDataSource'
|
||||
import { useListFormColumns } from './useListFormColumns'
|
||||
|
||||
interface CardListViewProps {
|
||||
listFormCode: string
|
||||
searchParams?: URLSearchParams
|
||||
isSubForm?: boolean
|
||||
level?: number
|
||||
refreshData?: () => Promise<void>
|
||||
gridDto?: GridDto
|
||||
}
|
||||
|
||||
const CardListView = (props: CardListViewProps) => {
|
||||
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
|
||||
const { translate } = useLocalization()
|
||||
const { smaller } = useResponsive()
|
||||
|
||||
const cardViewRef = useRef<CardViewRef>()
|
||||
const refListFormCode = useRef('')
|
||||
const widgetGroupRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [cardDataSource, setCardDataSource] = useState<CustomStore<any, any>>()
|
||||
const [columnData, setColumnData] = useState<GridColumnData[]>()
|
||||
const [formData, setFormData] = useState<any>()
|
||||
const [mode, setMode] = useState<RowMode>('view')
|
||||
const [extraFilters, setExtraFilters] = useState<GridExtraFilterState[]>([])
|
||||
const [gridDto, setGridDto] = useState<GridDto>()
|
||||
const [isPopupFullScreen, setIsPopupFullScreen] = useState(false)
|
||||
const [widgetGroupHeight, setWidgetGroupHeight] = useState(0)
|
||||
const isInitializedRef = useRef(false)
|
||||
const lastGridDtoRef = useRef<GridDto | null>(null)
|
||||
|
||||
type EditorOptionsWithButtons = {
|
||||
buttons?: any[]
|
||||
} & Record<string, any>
|
||||
|
||||
const defaultSearchParams = useRef<string | null>(null)
|
||||
const searchParamsString = useRef<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
const initializeCardView = async () => {
|
||||
const response = await getList({ listFormCode })
|
||||
setGridDto(response.data)
|
||||
}
|
||||
|
||||
if (extGridDto === undefined) {
|
||||
initializeCardView()
|
||||
} else {
|
||||
setGridDto(extGridDto)
|
||||
}
|
||||
}, [listFormCode, extGridDto])
|
||||
|
||||
useEffect(() => {
|
||||
if (!defaultSearchParams.current) {
|
||||
defaultSearchParams.current = searchParams?.get('filter') ?? null
|
||||
}
|
||||
// searchParams'ı string olarak sakla ki dependency'lerde sorun çıkarmasın
|
||||
searchParamsString.current = searchParams?.toString() || ''
|
||||
}, [searchParams])
|
||||
|
||||
const layout = layoutTypes.card
|
||||
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
|
||||
gridDto,
|
||||
listFormCode,
|
||||
getSelectedRowKeys,
|
||||
getSelectedRowsData,
|
||||
refreshData,
|
||||
getFilter,
|
||||
layout,
|
||||
})
|
||||
|
||||
const { filterToolbarData, ...filterData } = useFilters({
|
||||
gridDto,
|
||||
gridRef: cardViewRef as any,
|
||||
listFormCode,
|
||||
})
|
||||
|
||||
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef: cardViewRef as any })
|
||||
const { getBandedColumns } = useListFormColumns({
|
||||
gridDto,
|
||||
listFormCode,
|
||||
isSubForm,
|
||||
gridRef: cardViewRef as any,
|
||||
})
|
||||
|
||||
function extractSearchParamsFields(filter: any): [string, string, any][] {
|
||||
if (!Array.isArray(filter)) return []
|
||||
|
||||
// [field, op, val] formu mu?
|
||||
if (filter.length === 3 && typeof filter[0] === 'string') {
|
||||
return [filter as [string, string, any]]
|
||||
}
|
||||
|
||||
// içinde başka filter array'leri olabilir
|
||||
return filter.flatMap((f) => extractSearchParamsFields(f))
|
||||
}
|
||||
|
||||
function getSelectedRowKeys() {
|
||||
const card = cardViewRef.current?.instance()
|
||||
if (!card) {
|
||||
return []
|
||||
}
|
||||
|
||||
return (card.option('selectedItemKeys') as any[]) || []
|
||||
}
|
||||
|
||||
function getSelectedRowsData() {
|
||||
const card = cardViewRef.current?.instance()
|
||||
if (!card) {
|
||||
return []
|
||||
}
|
||||
|
||||
return (card.option('selectedItems') as any[]) || []
|
||||
}
|
||||
|
||||
function refreshData() {
|
||||
const instance = cardViewRef.current?.instance()
|
||||
if (instance) {
|
||||
instance.getDataSource()?.reload()
|
||||
}
|
||||
}
|
||||
|
||||
function getFilter() {
|
||||
const card = cardViewRef.current?.instance()
|
||||
if (!card) {
|
||||
return
|
||||
}
|
||||
|
||||
return card.option('searchExpr')
|
||||
}
|
||||
|
||||
function onSelectionChanged(data: any) {
|
||||
const grdOpt = gridDto?.gridOptions
|
||||
const card = cardViewRef.current?.instance()
|
||||
if (!grdOpt || !card) {
|
||||
return
|
||||
}
|
||||
|
||||
// kullanicinin yetkisi varsa ve birden fazla kayit secili ise coklu silme gorunsun
|
||||
// CardView'de toolbar desteği sınırlı olduğu için bu özellik devre dışı
|
||||
|
||||
// SubForm'ları gösterebilmek için secili satiri formData'ya at
|
||||
if (data.selectedItems.length) {
|
||||
setFormData(data.selectedItems[0])
|
||||
}
|
||||
}
|
||||
|
||||
function onEditorPreparing(editor: any) {
|
||||
if (editor.dataField && gridDto) {
|
||||
const formItem = gridDto.gridOptions.editingFormDto
|
||||
.flatMap((group) => group.items || [])
|
||||
.find((i) => i.dataField === editor.dataField)
|
||||
|
||||
// Cascade disabled mantığı
|
||||
const colFormat = gridDto.columnFormats.find((c) => c.fieldName === editor.dataField)
|
||||
if (colFormat?.lookupDto?.cascadeParentFields) {
|
||||
const parentFields = colFormat.lookupDto.cascadeParentFields
|
||||
.split(',')
|
||||
.map((f: string) => f.trim())
|
||||
|
||||
const prevHandler = editor.editorOptions.onValueChanged
|
||||
|
||||
editor.editorOptions.onValueChanged = (e: any) => {
|
||||
if (prevHandler) prevHandler(e)
|
||||
|
||||
// Parent field değiştiğinde tüm cascade childları kontrol et
|
||||
const card = editor.component
|
||||
const dataSource = card.getDataSource()
|
||||
const itemData = dataSource?.items?.() || []
|
||||
|
||||
// Bu field bir parent ise, child fieldleri temizle
|
||||
if (colFormat.lookupDto?.cascadeEmptyFields) {
|
||||
const childFields = colFormat.lookupDto.cascadeEmptyFields
|
||||
.split(',')
|
||||
.map((f: string) => f.trim())
|
||||
// CardView'de form üzerinden güncelleme yapılmalı
|
||||
const formInstance = card.option('editing.form') as any
|
||||
if (formInstance && formInstance.updateData) {
|
||||
const updates: any = {}
|
||||
childFields.forEach((childField: string) => {
|
||||
updates[childField] = null
|
||||
})
|
||||
formInstance.updateData(updates)
|
||||
}
|
||||
}
|
||||
|
||||
// Tüm cascade fieldlerin disabled durumlarını güncelle
|
||||
setTimeout(() => {
|
||||
const popup = card.option('editing.popup')
|
||||
if (popup) {
|
||||
const formInstance = card.option('editing.form') as any
|
||||
if (formInstance && formInstance.getEditor) {
|
||||
gridDto.columnFormats.forEach((col) => {
|
||||
if (col.lookupDto?.cascadeParentFields) {
|
||||
const colParentFields = col.lookupDto.cascadeParentFields
|
||||
.split(',')
|
||||
.map((f: string) => f.trim())
|
||||
const shouldDisable = colParentFields.some((pf: string) => !itemData[pf])
|
||||
|
||||
try {
|
||||
const editorInstance = formInstance.getEditor(col.fieldName!)
|
||||
if (editorInstance) {
|
||||
editorInstance.option('disabled', shouldDisable)
|
||||
}
|
||||
} catch (err) {
|
||||
console.debug('Cascade disabled update skipped for', col.fieldName, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}, 50)
|
||||
}
|
||||
|
||||
// İlk açılışta disabled durumunu kontrol et
|
||||
const card = editor.component
|
||||
const formInstance = card.option('editing.form') as any
|
||||
const formData = formInstance?.option?.('formData') || {}
|
||||
const shouldDisable = parentFields.some((pf: string) => !formData[pf])
|
||||
|
||||
if (shouldDisable) {
|
||||
editor.editorOptions.disabled = true
|
||||
}
|
||||
}
|
||||
|
||||
if (formItem?.editorScript) {
|
||||
const prevHandler = editor.editorOptions.onValueChanged
|
||||
|
||||
editor.editorOptions.onValueChanged = (e: any) => {
|
||||
if (prevHandler) prevHandler(e)
|
||||
|
||||
try {
|
||||
const card = editor.component
|
||||
const formInstance = card.option('editing.form') as any
|
||||
const formData = formInstance?.option?.('formData') || {}
|
||||
|
||||
const setFormData = (newData: any) => {
|
||||
if (formInstance && formInstance.updateData) {
|
||||
formInstance.updateData(newData)
|
||||
}
|
||||
}
|
||||
|
||||
eval(formItem.editorScript!)
|
||||
} catch (err) {
|
||||
console.error('Script exec error', formItem.dataField, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (editor.editorOptions?.buttons) {
|
||||
editor.editorOptions.buttons = editor.editorOptions.buttons.map((btn: any) => {
|
||||
if (btn?.options?.onClick && typeof btn.options.onClick === 'function') {
|
||||
const origClick = btn.options.onClick
|
||||
btn.options.onClick = (e: any) => {
|
||||
const card = editor.component
|
||||
const formInstance = card.option('editing.form') as any
|
||||
const formData = formInstance?.option?.('formData') || {}
|
||||
|
||||
origClick({
|
||||
...e,
|
||||
formData,
|
||||
fieldName: editor.dataField,
|
||||
})
|
||||
}
|
||||
}
|
||||
return btn
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (refListFormCode.current !== listFormCode) {
|
||||
// listFormCode değiştiğinde yeniden initialize et
|
||||
lastGridDtoRef.current = null
|
||||
setExtraFilters([])
|
||||
setCardDataSource(undefined)
|
||||
setColumnData(undefined)
|
||||
|
||||
if (cardViewRef?.current) {
|
||||
const instance = cardViewRef?.current?.instance()
|
||||
if (instance) {
|
||||
instance.option('dataSource', undefined)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [listFormCode])
|
||||
|
||||
useEffect(() => {
|
||||
// gridDto gerçekten değişmişse işlem yap
|
||||
if (!gridDto || lastGridDtoRef.current === gridDto) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log('CardListView: useEffect triggered for gridDto', listFormCode)
|
||||
lastGridDtoRef.current = gridDto
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// Kolonları hazırla
|
||||
const cols = getBandedColumns()
|
||||
|
||||
cols?.forEach((col) => {
|
||||
const eo = col.editorOptions
|
||||
|
||||
// Sadece phoneGlobal formatlı kolonlarda çalış
|
||||
if (eo?.format === 'phoneGlobal') {
|
||||
col.dataType = 'string'
|
||||
|
||||
const formatter = (value: any) => {
|
||||
if (!value) return ''
|
||||
|
||||
let digits = String(value).replace(/\D/g, '')
|
||||
|
||||
if (digits.startsWith('90') && digits.length > 10) digits = digits.slice(-10)
|
||||
if (digits.startsWith('0') && digits.length > 10) digits = digits.slice(-10)
|
||||
if (digits.length > 10) digits = digits.slice(-10)
|
||||
|
||||
if (digits.length !== 10) return ''
|
||||
|
||||
const match = digits.match(/^(\d{3})(\d{3})(\d{4})$/)
|
||||
return match ? `(${match[1]}) ${match[2]}-${match[3]}` : digits
|
||||
}
|
||||
|
||||
col.format = { formatter }
|
||||
col.customizeText = (cellInfo: any) => formatter(cellInfo?.value)
|
||||
}
|
||||
})
|
||||
|
||||
// DataSource'u sadece bir kez oluştur
|
||||
const dataSource = createSelectDataSource(
|
||||
gridDto.gridOptions,
|
||||
listFormCode,
|
||||
searchParams,
|
||||
layoutTypes.card,
|
||||
cols,
|
||||
)
|
||||
|
||||
console.log('CardListView: Setting new dataSource')
|
||||
|
||||
// Tüm state'leri sırayla güncelle
|
||||
setColumnData(cols)
|
||||
setCardDataSource(dataSource)
|
||||
|
||||
if (gridDto?.gridOptions.extraFilterDto) {
|
||||
setExtraFilters(
|
||||
gridDto.gridOptions.extraFilterDto.map((f) => ({
|
||||
fieldName: f.fieldName,
|
||||
operator: f.operator,
|
||||
controlType: f.controlType,
|
||||
value: f.defaultValue ?? '',
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
if (gridDto.gridOptions.editingOptionDto?.popup) {
|
||||
setIsPopupFullScreen(gridDto.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [gridDto])
|
||||
|
||||
// extraFilters useEffect kaldırıldı - filtreler dataSource seviyesinde işlenir
|
||||
|
||||
useEffect(() => {
|
||||
refListFormCode.current = listFormCode
|
||||
}, [listFormCode])
|
||||
|
||||
// WidgetGroup yüksekliğini hesapla
|
||||
useEffect(() => {
|
||||
const calculateWidgetHeight = () => {
|
||||
if (widgetGroupRef.current) {
|
||||
const height = widgetGroupRef.current.offsetHeight
|
||||
setWidgetGroupHeight(height)
|
||||
}
|
||||
}
|
||||
|
||||
// İlk render'da hesapla
|
||||
calculateWidgetHeight()
|
||||
|
||||
// Resize durumunda tekrar hesapla
|
||||
const resizeObserver = new ResizeObserver(calculateWidgetHeight)
|
||||
if (widgetGroupRef.current) {
|
||||
resizeObserver.observe(widgetGroupRef.current)
|
||||
}
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}, [gridDto?.widgets])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={widgetGroupRef}>
|
||||
<WidgetGroup widgetGroups={gridDto?.widgets ?? []} />
|
||||
</div>
|
||||
|
||||
<Container className={DX_CLASSNAMES}>
|
||||
{!isSubForm && (
|
||||
<Helmet
|
||||
titleTemplate="%s | Erp Platform"
|
||||
title={translate('::' + gridDto?.gridOptions.title)}
|
||||
defaultTitle="Erp Platform"
|
||||
></Helmet>
|
||||
)}
|
||||
{!gridDto && <div className="p-4">Loading card configuration...</div>}
|
||||
{gridDto && !columnData && <div className="p-4">Loading columns...</div>}
|
||||
{gridDto && columnData && !cardDataSource && (
|
||||
<div className="p-4">Loading data source...</div>
|
||||
)}
|
||||
{gridDto &&
|
||||
columnData &&
|
||||
cardDataSource &&
|
||||
(() => {
|
||||
return true
|
||||
})() && (
|
||||
<>
|
||||
<div className="p-1">
|
||||
<CardView
|
||||
ref={cardViewRef as any}
|
||||
key={`CardView-${listFormCode}`}
|
||||
id={'CardView-' + listFormCode}
|
||||
dataSource={cardDataSource}
|
||||
height={
|
||||
gridDto.gridOptions.height > 0
|
||||
? gridDto.gridOptions.height
|
||||
: gridDto.gridOptions.fullHeight
|
||||
? `calc(100vh - ${170 + widgetGroupHeight}px)`
|
||||
: undefined
|
||||
}
|
||||
width={gridDto.gridOptions.width || '100%'}
|
||||
onSelectionChanged={onSelectionChanged}
|
||||
focusStateEnabled={true}
|
||||
hoverStateEnabled={true}
|
||||
elementAttr={{
|
||||
class: 'custom-card-view'
|
||||
}}
|
||||
>
|
||||
<Editing
|
||||
allowDeleting={gridDto.gridOptions.editingOptionDto?.allowDeleting}
|
||||
allowUpdating={gridDto.gridOptions.editingOptionDto?.allowUpdating}
|
||||
allowAdding={gridDto.gridOptions.editingOptionDto?.allowAdding}
|
||||
popup={{
|
||||
title:
|
||||
(mode === 'new' ? '✚ ' : '🖊️ ') +
|
||||
translate('::' + gridDto.gridOptions.editingOptionDto?.popup?.title),
|
||||
showTitle: gridDto.gridOptions.editingOptionDto?.popup?.showTitle,
|
||||
hideOnOutsideClick:
|
||||
gridDto.gridOptions.editingOptionDto?.popup?.hideOnOutsideClick,
|
||||
width: gridDto.gridOptions.editingOptionDto?.popup?.width,
|
||||
height: gridDto.gridOptions.editingOptionDto?.popup?.height,
|
||||
resizeEnabled: gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled,
|
||||
fullScreen: isPopupFullScreen,
|
||||
toolbarItems: [
|
||||
{
|
||||
widget: 'dxButton',
|
||||
toolbar: 'bottom',
|
||||
location: 'after',
|
||||
options: {
|
||||
text: translate('::Save'),
|
||||
type: 'default',
|
||||
onClick: () => {
|
||||
const card = cardViewRef.current?.instance()
|
||||
card?.saveEditData()
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
widget: 'dxButton',
|
||||
toolbar: 'bottom',
|
||||
location: 'after',
|
||||
options: {
|
||||
text: translate('::Cancel'),
|
||||
onClick: () => {
|
||||
const card = cardViewRef.current?.instance()
|
||||
card?.cancelEditData()
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
widget: 'dxButton',
|
||||
toolbar: 'top',
|
||||
location: 'after',
|
||||
options: {
|
||||
icon: isPopupFullScreen ? 'collapse' : 'fullscreen',
|
||||
hint: isPopupFullScreen
|
||||
? translate('::Normal Boyut')
|
||||
: translate('::Tam Ekran'),
|
||||
stylingMode: 'text',
|
||||
onClick: () => setIsPopupFullScreen(!isPopupFullScreen),
|
||||
},
|
||||
},
|
||||
],
|
||||
}}
|
||||
form={{
|
||||
colCount: 1,
|
||||
onFieldDataChanged: (e) => {
|
||||
if (e.dataField) {
|
||||
const formItem = gridDto.gridOptions.editingFormDto
|
||||
.flatMap((group) => group.items || [])
|
||||
.find((i) => i.dataField === e.dataField)
|
||||
if (formItem?.editorScript) {
|
||||
try {
|
||||
eval(formItem.editorScript)
|
||||
} catch (err) {
|
||||
console.error('Script exec error', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
items:
|
||||
gridDto.gridOptions.editingFormDto?.length > 0
|
||||
? (() => {
|
||||
const sortedFormDto = gridDto.gridOptions.editingFormDto
|
||||
.slice()
|
||||
.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
|
||||
|
||||
// Tabbed item'ları grupla
|
||||
const tabbedItems = sortedFormDto.filter(
|
||||
(e: any) => e.itemType === 'tabbed',
|
||||
)
|
||||
const result: any[] = []
|
||||
|
||||
// Helper function: item mapper
|
||||
const mapFormItem = (i: EditingFormItemDto) => {
|
||||
let editorOptions: EditorOptionsWithButtons = {}
|
||||
try {
|
||||
editorOptions = i.editorOptions && JSON.parse(i.editorOptions)
|
||||
|
||||
if (editorOptions?.buttons) {
|
||||
editorOptions.buttons = (editorOptions?.buttons || []).map(
|
||||
(btn: any) => {
|
||||
if (
|
||||
btn?.options?.onClick &&
|
||||
typeof btn.options.onClick === 'string'
|
||||
) {
|
||||
btn.options.onClick = eval(`(${btn.options.onClick})`)
|
||||
}
|
||||
return btn
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const rawFilter = searchParams?.get('filter')
|
||||
if (rawFilter) {
|
||||
const parsed = JSON.parse(rawFilter)
|
||||
const filters = extractSearchParamsFields(parsed)
|
||||
|
||||
const hasFilter = filters.some(
|
||||
([field, op, val]) => field === i.dataField,
|
||||
)
|
||||
|
||||
if (hasFilter) {
|
||||
const existsInExtra = extraFilters.some(
|
||||
(f) => f.fieldName === i.dataField && !!f.value,
|
||||
)
|
||||
|
||||
if (!existsInExtra) {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// Set defaultValue for @AUTONUMBER fields
|
||||
if (
|
||||
typeof listFormField?.defaultValue === 'string' &&
|
||||
listFormField?.defaultValue === '@AUTONUMBER' &&
|
||||
mode === 'new'
|
||||
) {
|
||||
editorOptions = {
|
||||
...editorOptions,
|
||||
value: autoNumber(),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
editorScript: i.editorScript,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
sortedFormDto.forEach((e: any) => {
|
||||
if (e.itemType !== 'tabbed') {
|
||||
const effectiveColCount = e.colCount || 1
|
||||
const effectiveColSpan = e.colSpan || 1
|
||||
|
||||
result.push({
|
||||
itemType: e.itemType,
|
||||
colCount: effectiveColCount,
|
||||
colSpan: effectiveColSpan,
|
||||
caption: e.caption,
|
||||
items: e.items
|
||||
?.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
|
||||
.map(mapFormItem)
|
||||
.filter((a: any) => {
|
||||
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
|
||||
}
|
||||
}),
|
||||
})
|
||||
} else if (tabbedItems.length > 0 && e === tabbedItems[0]) {
|
||||
result.push({
|
||||
itemType: 'tabbed',
|
||||
colCount: 1,
|
||||
colSpan: 1,
|
||||
tabs: tabbedItems.map((tabbedItem: any) => {
|
||||
const effectiveColCount = tabbedItem.colCount || 1
|
||||
|
||||
return {
|
||||
title: tabbedItem.caption,
|
||||
colCount: effectiveColCount,
|
||||
items: tabbedItem.items
|
||||
?.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
|
||||
.map(mapFormItem)
|
||||
.filter((a: any) => {
|
||||
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
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
})()
|
||||
: undefined,
|
||||
}}
|
||||
></Editing>
|
||||
<Template name={'cellEditTagBox'} render={TagBoxEditorComponent} />
|
||||
<Template name={'cellEditGridBox'} render={GridBoxEditorComponent} />
|
||||
<Template name="extraFilters">
|
||||
<GridExtraFilterToolbar
|
||||
filters={gridDto?.gridOptions.extraFilterDto ?? []}
|
||||
extraFilters={extraFilters}
|
||||
setExtraFilters={setExtraFilters}
|
||||
/>
|
||||
</Template>
|
||||
<Sorting mode={gridDto.gridOptions?.sortMode}></Sorting>
|
||||
<SearchPanel
|
||||
visible={gridDto.gridOptions.searchPanelDto.visible}
|
||||
width={gridDto.gridOptions.searchPanelDto.width}
|
||||
></SearchPanel>
|
||||
<Selection
|
||||
mode={gridDto.gridOptions.selectionDto?.mode}
|
||||
selectAllMode={gridDto.gridOptions.selectionDto?.selectAllMode}
|
||||
></Selection>
|
||||
<Paging defaultPageSize={gridDto.gridOptions.pageSize ?? 20} />
|
||||
<Pager
|
||||
visible={gridDto.gridOptions.pagerOptionDto?.visible}
|
||||
allowedPageSizes={gridDto.gridOptions.pagerOptionDto?.allowedPageSizes
|
||||
?.split(',')
|
||||
.map((a: any) => +a)}
|
||||
showPageSizeSelector={gridDto.gridOptions.pagerOptionDto?.showPageSizeSelector}
|
||||
showInfo={false}
|
||||
showNavigationButtons={
|
||||
gridDto.gridOptions.pagerOptionDto?.showNavigationButtons
|
||||
}
|
||||
displayMode={gridDto.gridOptions.pagerOptionDto?.displayMode}
|
||||
></Pager>
|
||||
<LoadPanel
|
||||
visible={gridDto.gridOptions.pagerOptionDto?.loadPanelEnabled === true || gridDto.gridOptions.pagerOptionDto?.loadPanelEnabled === 'auto'}
|
||||
></LoadPanel>
|
||||
</CardView>
|
||||
|
||||
{gridDto?.gridOptions?.subFormsDto?.length > 0 && (
|
||||
<>
|
||||
<hr className="my-2" />
|
||||
<SubForms gridDto={gridDto!} formData={formData} level={level ?? 0} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Dialog
|
||||
width={smaller.md ? '100%' : 1000}
|
||||
isOpen={filterData.isImportModalOpen || false}
|
||||
onClose={() => filterData.setIsImportModalOpen(false)}
|
||||
onRequestClose={() => filterData.setIsImportModalOpen(false)}
|
||||
>
|
||||
<ImportDashboard gridDto={gridDto} />
|
||||
</Dialog>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Dialog
|
||||
isOpen={toolbarModalData?.open || false}
|
||||
onClose={() => setToolbarModalData(undefined)}
|
||||
onRequestClose={() => setToolbarModalData(undefined)}
|
||||
>
|
||||
{toolbarModalData?.content}
|
||||
</Dialog>
|
||||
<GridFilterDialogs gridRef={cardViewRef as any} listFormCode={listFormCode} {...filterData} />
|
||||
</Container>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardListView
|
||||
|
|
@ -7,7 +7,6 @@ import { useStoreActions, useStoreState } from '@/store/store'
|
|||
import classNames from 'classnames'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { GridDto } from '@/proxy/form/models'
|
||||
import Card from './Card'
|
||||
import { Badge, Button } from '@/components/ui'
|
||||
import Pivot from './Pivot'
|
||||
import Tree from './Tree'
|
||||
|
|
@ -15,6 +14,7 @@ import { getList } from '@/services/form.service'
|
|||
import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon'
|
||||
import { ListViewLayoutType } from '../admin/listForm/edit/types'
|
||||
import Chart from './Chart'
|
||||
import CardListView from './CardListView'
|
||||
|
||||
const List = () => {
|
||||
const params = useParams()
|
||||
|
|
@ -182,7 +182,7 @@ const List = () => {
|
|||
gridDto={gridDto}
|
||||
/>
|
||||
) : viewMode === 'card' ? (
|
||||
<Card
|
||||
<CardListView
|
||||
listFormCode={listFormCode}
|
||||
searchParams={searchParams}
|
||||
isSubForm={false}
|
||||
|
|
|
|||
Loading…
Reference in a new issue