Form komponenti SelectBox Lookup

This commit is contained in:
Sedat Öztürk 2025-09-21 23:05:13 +03:00
parent a913220999
commit e14d6930c2
11 changed files with 267 additions and 48 deletions

View file

@ -1,5 +1,5 @@
{ {
"commit": "3f69cc5", "commit": "a913220",
"releases": [ "releases": [
{ {
"version": "1.0.8", "version": "1.0.8",

View file

@ -11,8 +11,10 @@ import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent' import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent'
import { RowMode, SimpleItemWithColData } from './types' import { RowMode, SimpleItemWithColData } from './types'
import { PlatformEditorTypes } from '@/proxy/form/models' import { PlatformEditorTypes } from '@/proxy/form/models'
import { useLookupDataSource } from './useLookupDataSource'
const FormDevExpress = (props: { const FormDevExpress = (props: {
listFormCode: string
isSubForm?: boolean isSubForm?: boolean
mode: RowMode mode: RowMode
refForm: RefObject<FormDx> refForm: RefObject<FormDx>
@ -20,7 +22,8 @@ const FormDevExpress = (props: {
formItems: GroupItem[] formItems: GroupItem[]
setFormData: Dispatch<any> setFormData: Dispatch<any>
}) => { }) => {
const { isSubForm, mode, refForm, formData, formItems, setFormData } = props const { listFormCode, isSubForm, mode, refForm, formData, formItems, setFormData } = props
const { getLookupDataSource } = useLookupDataSource({ listFormCode, isSubForm })
return ( return (
<form className={`${DX_CLASSNAMES} p-2`}> <form className={`${DX_CLASSNAMES} p-2`}>
@ -66,6 +69,10 @@ const FormDevExpress = (props: {
...formItem.editorOptions, ...formItem.editorOptions,
...(mode === 'view' ? { readOnly: true } : {}), ...(mode === 'view' ? { readOnly: true } : {}),
}} }}
dataSource={getLookupDataSource(
formItem.colData?.editorOptions,
formItem.colData,
)}
></TagBoxEditorComponent> ></TagBoxEditorComponent>
)} )}
> >
@ -88,6 +95,10 @@ const FormDevExpress = (props: {
...formItem.editorOptions, ...formItem.editorOptions,
...(mode === 'view' ? { readOnly: true } : {}), ...(mode === 'view' ? { readOnly: true } : {}),
}} }}
dataSource={getLookupDataSource(
formItem.colData?.editorOptions,
formItem.colData,
)}
></GridBoxEditorComponent> ></GridBoxEditorComponent>
)} )}
> >
@ -99,6 +110,16 @@ const FormDevExpress = (props: {
{...formItem} {...formItem}
editorOptions={{ editorOptions={{
...formItem.editorOptions, ...formItem.editorOptions,
...(formItem.colData?.lookupDto?.dataSourceType
? {
dataSource: getLookupDataSource(
formItem.colData?.editorOptions,
formItem.colData,
),
valueExpr: formItem.colData?.lookupDto?.valueExpr,
displayExpr: formItem.colData?.lookupDto?.displayExpr,
}
: {}),
...(mode === 'view' ? { readOnly: true } : { autoFocus: i === 1 }), ...(mode === 'view' ? { readOnly: true } : { autoFocus: i === 1 }),
}} }}
/> />

View file

@ -94,6 +94,7 @@ const FormEdit = (
formData={formData} formData={formData}
formItems={formItems} formItems={formItems}
setFormData={setFormData} setFormData={setFormData}
listFormCode={listFormCode}
/> />
<SubForms gridDto={gridDto!} formData={formData} level={level ?? 0} /> <SubForms gridDto={gridDto!} formData={formData} level={level ?? 0} />
</Container> </Container>

View file

@ -179,6 +179,7 @@ const FormNew = (
formData={formData} formData={formData}
formItems={formItems} formItems={formItems}
setFormData={setFormData} setFormData={setFormData}
listFormCode={listFormCode}
/> />
</Container> </Container>
) )

View file

@ -88,6 +88,7 @@ const FormView = (
formData={formData} formData={formData}
formItems={formItems} formItems={formItems}
setFormData={() => {}} setFormData={() => {}}
listFormCode={listFormCode}
/> />
<SubForms gridDto={gridDto!} formData={formData} level={level ?? 0} refreshData={fetchData} /> <SubForms gridDto={gridDto!} formData={formData} level={level ?? 0} refreshData={fetchData} />
</Container> </Container>

View file

@ -1,5 +1,4 @@
import { GridBoxOptionsDto } from '@/proxy/form/models' import { ColumnFormatDto, GridBoxOptionsDto } from '@/proxy/form/models'
import { GridColumnData } from '@/views/list/GridColumnData'
import { Button } from 'devextreme-react/button' import { Button } from 'devextreme-react/button'
import DataGrid from 'devextreme-react/data-grid' import DataGrid from 'devextreme-react/data-grid'
import DropDownBox from 'devextreme-react/drop-down-box' import DropDownBox from 'devextreme-react/drop-down-box'
@ -12,29 +11,32 @@ const GridBoxEditorComponent = ({
onValueChanged, onValueChanged,
col, col,
editorOptions, editorOptions,
dataSource
}: { }: {
value: any value: any
options?: GridBoxOptionsDto options?: GridBoxOptionsDto
values: any // all row data values: any // all row data
onValueChanged: (e: any) => void onValueChanged: (e: any) => void
col?: GridColumnData col?: ColumnFormatDto
editorOptions: any editorOptions: any
dataSource: any
}): ReactElement => { }): ReactElement => {
const gridRef = useRef<DataGrid>(null) const gridRef = useRef<DataGrid>(null)
const val = Array.isArray(value) ? value : [value] const val = Array.isArray(value) ? value : [value]
const lookup = col?.lookup const lookupDto = col?.lookupDto
const dataSource: any =
typeof lookup?.dataSource === 'function' // const dataSource: any =
? lookup.dataSource(values) // typeof lookupDto?.dataSource === 'function'
: (lookup?.dataSource ?? []) // ? lookupDto.dataSource(values)
// : (lookupDto?.dataSource ?? [])
return ( return (
<DropDownBox <DropDownBox
{...editorOptions} {...editorOptions}
value={val} value={val}
dataSource={dataSource} dataSource={dataSource}
valueExpr={lookup?.valueExpr} valueExpr={lookupDto?.valueExpr}
displayExpr={lookup?.displayExpr} displayExpr={lookupDto?.displayExpr}
showClearButton={options?.showClearButton} showClearButton={options?.showClearButton}
acceptCustomValue={options?.acceptCustomValue} acceptCustomValue={options?.acceptCustomValue}
onValueChanged={(e) => { onValueChanged={(e) => {
@ -51,7 +53,7 @@ const GridBoxEditorComponent = ({
<DataGrid <DataGrid
ref={gridRef} ref={gridRef}
dataSource={dataSource} dataSource={dataSource}
key={lookup?.valueExpr} key={lookupDto?.valueExpr}
filterRow={{ filterRow={{
visible: options?.filterRowVisible, visible: options?.filterRowVisible,
applyFilter: 'auto', applyFilter: 'auto',

View file

@ -1,7 +1,5 @@
import { TagBoxOptionsDto } from '@/proxy/form/models' import { ColumnFormatDto, TagBoxOptionsDto } from '@/proxy/form/models'
import { GridColumnData } from '@/views/list/GridColumnData'
import TagBox from 'devextreme-react/tag-box' import TagBox from 'devextreme-react/tag-box'
import { ApplyValueMode } from 'devextreme/common'
import { ReactElement } from 'react' import { ReactElement } from 'react'
const TagBoxEditorComponent = ({ const TagBoxEditorComponent = ({
@ -12,29 +10,32 @@ const TagBoxEditorComponent = ({
onValueChanged, onValueChanged,
col, col,
editorOptions, editorOptions,
dataSource
}: { }: {
value: any value: any
setDefaultValue: boolean setDefaultValue: boolean
options?: TagBoxOptionsDto options?: TagBoxOptionsDto
values: any // all row data values: any // all row data
onValueChanged: (e: any) => void onValueChanged: (e: any) => void
col?: GridColumnData col?: ColumnFormatDto
editorOptions: any editorOptions: any
dataSource: any
}): ReactElement => { }): ReactElement => {
const val = Array.isArray(value) ? value : [value] const val = Array.isArray(value) ? value : [value]
const lookup = col?.lookup const lookupDto = col?.lookupDto
const dataSource =
typeof lookup?.dataSource === 'function' // const dataSource =
? lookup.dataSource(values) // typeof lookup?.dataSource === 'function'
: (lookup?.dataSource ?? []) // ? lookup.dataSource(values)
// : (lookup?.dataSource ?? [])
return ( return (
<TagBox <TagBox
{...editorOptions} {...editorOptions}
{...(setDefaultValue ? { defaultValue: val } : { value: val })} {...(setDefaultValue ? { defaultValue: val } : { value: val })}
dataSource={dataSource} dataSource={dataSource}
valueExpr={lookup?.valueExpr} valueExpr={lookupDto?.valueExpr}
displayExpr={lookup?.displayExpr} displayExpr={lookupDto?.displayExpr}
showClearButton={options?.showClearButton} showClearButton={options?.showClearButton}
showSelectionControls={options?.showSelectionControls ?? true} showSelectionControls={options?.showSelectionControls ?? true}
maxDisplayedTags={options?.maxDisplayedTags} maxDisplayedTags={options?.maxDisplayedTags}

View file

@ -1,14 +1,13 @@
import { FormItemComponent, SimpleItem } from 'devextreme/ui/form' import { FormItemComponent, SimpleItem } from 'devextreme/ui/form'
import { GridColumnData } from '../list/GridColumnData'
import { Overwrite } from '../../utils/types' import { Overwrite } from '../../utils/types'
import { GridBoxOptionsDto, PlatformEditorTypes, TagBoxOptionsDto } from '../../proxy/form/models' import { ColumnFormatDto, GridBoxOptionsDto, PlatformEditorTypes, TagBoxOptionsDto } from '../../proxy/form/models'
import { Meta } from '@/@types/routes' import { Meta } from '@/@types/routes'
export type EditorType2 = FormItemComponent | PlatformEditorTypes.dxGridBox export type EditorType2 = FormItemComponent | PlatformEditorTypes.dxGridBox
export type SimpleItemWithColData = Overwrite< export type SimpleItemWithColData = Overwrite<
SimpleItem, SimpleItem,
{ {
colData?: GridColumnData colData?: ColumnFormatDto
tagBoxOptions?: TagBoxOptionsDto tagBoxOptions?: TagBoxOptionsDto
gridBoxOptions?: GridBoxOptionsDto gridBoxOptions?: GridBoxOptionsDto
canRead: boolean canRead: boolean

View file

@ -244,7 +244,7 @@ const useGridData = (props: {
colSpan: i.colSpan, colSpan: i.colSpan,
isRequired: i.isRequired, isRequired: i.isRequired,
editorOptions, editorOptions,
colData: cols?.find((x) => x.dataField === i.dataField), colData: gridDto?.columnFormats.find((x) => x.fieldName === i.dataField),
tagBoxOptions: i.tagBoxOptions, tagBoxOptions: i.tagBoxOptions,
gridBoxOptions: i.gridBoxOptions, gridBoxOptions: i.gridBoxOptions,
} }

View file

@ -0,0 +1,153 @@
import { dynamicFetch } from '@/services/form.service'
import { UiLookupDataSourceTypeEnum } from '@/proxy/form/models'
import CustomStore from 'devextreme/data/custom_store'
import { useCallback } from 'react'
const createLookupStaticDataSource = (
load: () => any,
filter: any = null,
key: any = 'key',
sort: any = 'name',
) => ({
store: new CustomStore({
key,
loadMode: 'raw',
load,
}),
sort,
filter,
})
const createLookupQueryDataSource = (
listFormCode?: string,
listFormFieldName?: string,
filters?: any[],
isSubForm?: boolean,
) => {
return new CustomStore({
loadMode: 'raw',
load: async () => {
if (!isSubForm && listFormCode && !window.location.pathname.includes(listFormCode)) {
return
}
try {
const response = await dynamicFetch('list-form-select/lookup', 'POST', null, {
listFormCode,
listFormFieldName,
filters,
})
return response.data.map((a: any) => ({
key: a.Key,
name: a.Name,
group: a.Group,
}))
} catch (error: any) {
return null
}
},
})
}
const createLookupApiDataSource = (
listFormCode?: string,
lookupQuery?: string,
filters?: any[],
keyName?: string,
isSubForm?: boolean,
) => {
return new CustomStore({
key: keyName,
loadMode: 'raw',
load: async () => {
if (!isSubForm && listFormCode && !window.location.pathname.includes(listFormCode)) {
return
}
if (!lookupQuery) {
return
}
const [method, url, body, keySelector, nameSelector, groupSelector] = lookupQuery.split(';')
if (filters?.length) {
for (let i = 0; i < filters.length; i++) {
body.replace(`@param${i}`, filters[i])
}
}
try {
const response = await dynamicFetch(url, method, null, body)
let { data } = response
if (!data) {
return
}
if (!Array.isArray(data)) {
data = [data]
}
return data.map((a: any) => ({
key: eval(keySelector),
name: eval(nameSelector),
group: eval(groupSelector),
}))
} catch {
return
}
},
})
}
export const useLookupDataSource = ({
listFormCode,
isSubForm,
}: {
listFormCode: string
isSubForm?: boolean
}) => {
const getLookupDataSource = useCallback(
(options: any, colData: any) => {
const { lookupDto } = colData
const filters = []
if (lookupDto.cascadeParentFields) {
if (lookupDto.dataSourceType == UiLookupDataSourceTypeEnum.StaticData) {
filters.push([
lookupDto?.cascadeRelationField,
lookupDto?.cascadeFilterOperator,
options?.data[lookupDto?.cascadeParentField],
])
} else {
const data = options?.data ?? options
for (const cascadeParentField of lookupDto.cascadeParentFields.split(',')) {
filters.push(data[cascadeParentField])
}
}
}
if (lookupDto.dataSourceType == UiLookupDataSourceTypeEnum.StaticData) {
return createLookupStaticDataSource(
() => JSON.parse(lookupDto?.lookupQuery),
filters.length ? filters : null,
)
} else if (lookupDto.dataSourceType == UiLookupDataSourceTypeEnum.Query) {
return createLookupQueryDataSource(listFormCode, colData.fieldName, filters, isSubForm)
} else if (lookupDto.dataSourceType == UiLookupDataSourceTypeEnum.WebService) {
return createLookupApiDataSource(
listFormCode,
lookupDto?.lookupQuery,
filters,
colData.lookupDto?.valueExpr?.toLowerCase(),
isSubForm,
)
} else {
return {
store: [],
}
}
},
[listFormCode, isSubForm],
)
return { getLookupDataSource }
}

View file

@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { GridDto } from '@/proxy/form/models' import { EditingFormItemDto, GridDto, PlatformEditorTypes } from '@/proxy/form/models'
import { captionize } from 'devextreme/core/utils/inflector' import { captionize } from 'devextreme/core/utils/inflector'
import { useListFormCustomDataSource } from '@/shared/useListFormCustomDataSource' import { useListFormCustomDataSource } from '@/shared/useListFormCustomDataSource'
import { Pagination, Select } from '@/components/ui' import { Pagination, Select } from '@/components/ui'
@ -7,13 +7,13 @@ import classNames from 'classnames'
import { getList } from '@/services/form.service' import { getList } from '@/services/form.service'
import { FaInbox } from 'react-icons/fa' import { FaInbox } from 'react-icons/fa'
import FormDevExpress from '../form/FormDevExpress' import FormDevExpress from '../form/FormDevExpress'
import { GroupItem, SimpleItem } from 'devextreme/ui/form' import { GroupItem } from 'devextreme/ui/form'
import { Form as FormDx } from 'devextreme-react/form' import { Form as FormDx } from 'devextreme-react/form'
import FormButtons from '../form/FormButtons' import FormButtons from '../form/FormButtons'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import CustomStore from 'devextreme/data/custom_store' import CustomStore from 'devextreme/data/custom_store'
import { PermissionResults } from '../form/types' import { PermissionResults, SimpleItemWithColData } from '../form/types'
import { usePermission } from '@/utils/hooks/usePermission' import { usePermission } from '@/utils/hooks/usePermission'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
@ -54,22 +54,61 @@ const CardItem = ({
const formItems: GroupItem[] = useMemo(() => { const formItems: GroupItem[] = useMemo(() => {
if (!gridDto) return [] if (!gridDto) return []
const items: SimpleItem[] = gridDto.columnFormats return gridDto?.gridOptions.editingFormDto
.filter((col) => col.visible && col.listOrderNo > 0) ?.sort((a: any, b: any) => {
.sort((a, b) => a.listOrderNo - b.listOrderNo) return a.order >= b.order ? 1 : -1
.slice(0, 10) })
.map((col) => ({ .map((e: any) => {
dataField: col.fieldName, return {
label: { text: captionize(col.captionName || col.fieldName!) }, itemType: e.itemType,
})) colCount: e.colCount,
colSpan: e.colSpan,
return [ caption: e.caption,
{ items: e.items
itemType: 'group', ?.sort((a: any, b: any) => {
colCount: 1, return a.order >= b.order ? 1 : -1
items: items, })
}, .map((i: EditingFormItemDto) => {
] let editorOptions = {}
try {
editorOptions = i.editorOptions && JSON.parse(i.editorOptions)
} catch {}
const item: SimpleItemWithColData = {
canRead:
gridDto.columnFormats.find((x: any) => x.fieldName === i.dataField)?.canRead ??
false,
canUpdate:
gridDto.columnFormats.find((x: any) => x.fieldName === i.dataField)?.canUpdate ??
false,
canCreate:
gridDto.columnFormats.find((x: any) => x.fieldName === i.dataField)?.canCreate ??
false,
canExport:
gridDto.columnFormats.find((x: any) => x.fieldName === i.dataField)?.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,
colData: gridDto.columnFormats.find((x) => x.fieldName === i.dataField),
tagBoxOptions: i.tagBoxOptions,
gridBoxOptions: i.gridBoxOptions,
}
if (i.dataField.indexOf(':') >= 0) {
item.label = { text: captionize(i.dataField.split(':')[1]) }
}
item.editorOptions = {
...item.editorOptions,
readOnly: true,
}
return item
}),
} as GroupItem
})
}, [gridDto]) }, [gridDto])
const permissionResults: PermissionResults = { const permissionResults: PermissionResults = {
@ -130,6 +169,7 @@ const CardItem = ({
formData={formData} formData={formData}
formItems={formItems} formItems={formItems}
setFormData={setFormData} setFormData={setFormData}
listFormCode={listFormCode}
/> />
</div> </div>
</div> </div>
@ -194,7 +234,7 @@ const Card = ({ listFormCode, searchParams }: CardProps) => {
const allowedSizes = pagerOptions?.allowedPageSizes const allowedSizes = pagerOptions?.allowedPageSizes
?.split(',') ?.split(',')
.map((s) => Number(s.trim())) .map((s) => Number(s.trim()))
.filter((n) => !isNaN(n) && n > 0) || [20, 50, 100] .filter((n) => !isNaN(n) && n > 0) || [10, 20, 50, 100]
setPageSizeOptions(allowedSizes.map((size) => ({ value: size, label: `${size} page` }))) setPageSizeOptions(allowedSizes.map((size) => ({ value: size, label: `${size} page` })))
}, [gridDto, listFormCode, searchParams]) }, [gridDto, listFormCode, searchParams])