sozsoft-platform/ui/src/views/form/useFormData.tsx

457 lines
15 KiB
TypeScript
Raw Normal View History

2026-02-24 20:44:16 +00:00
import { Notification, toast } from '@/components/ui'
import { getList } from '@/services/form.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { usePermission } from '@/utils/hooks/usePermission'
import { FormRef } from 'devextreme-react/form'
import { captionize } from 'devextreme/core/utils/inflector'
import CustomStore from 'devextreme/data/custom_store'
import { GroupItem } from 'devextreme/ui/form'
import { useEffect, useRef, useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { GridColumnData } from '../list/GridColumnData'
import { addCss, addJs } from '../list/Utils'
import { PermissionResults, RowMode, SimpleItemWithColData } from './types'
import { EditingFormItemDto, GridDto, PlatformEditorTypes } from '@/proxy/form/models'
import { getAccessDeniedPath } from '@/utils/routing'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLookupDataSource } from './useLookupDataSource'
import { layoutTypes } from '../admin/listForm/edit/types'
import { useListFormCustomDataSource } from '../list/useListFormCustomDataSource'
import { useListFormColumns } from '../list/useListFormColumns'
2026-06-08 08:20:56 +00:00
const flattenFormItems = (items: any[] = []): SimpleItemWithColData[] =>
items.flatMap((item) => [
...(item?.dataField ? [item] : []),
...flattenFormItems(item?.items || []),
...(item?.tabs || []).flatMap((tab: any) => flattenFormItems(tab?.items || [])),
])
2026-02-24 20:44:16 +00:00
const useGridData = (props: {
mode: RowMode
listFormCode: string
id?: string
level?: number
isSubForm?: boolean
onSubmitAction?: () => void
}) => {
const { mode, listFormCode, id, isSubForm } = props
const [gridReady, setGridReady] = useState(false)
const [loading, setLoading] = useState(false)
const [filter, setFilter] = useState<any[]>([])
const [gridDto, setGridDto] = useState<GridDto>()
const [dataSource, setDataSource] = useState<CustomStore<any, any>>()
const [commandColumnData, setCommandColumnData] = useState<GridColumnData>()
const [formDataOld, setFormDataOld] = useState<any>()
const [formData, setFormData] = useState<any>()
const [formItems, setFormItems] = useState<GroupItem[]>([])
const [permissionResults, setPermissionResults] = useState<PermissionResults>()
const refForm = useRef<FormRef>(null)
2026-06-08 08:20:56 +00:00
const previousFormDataRef = useRef<any>()
2026-02-24 20:44:16 +00:00
const [searchParams] = useSearchParams()
const navigate = useNavigate()
const { translate } = useLocalization()
const { checkPermission } = usePermission()
const { getBandedColumns } = useListFormColumns({
gridDto,
listFormCode,
isSubForm,
gridRef: undefined,
})
const { createSelectDataSource } = useListFormCustomDataSource({} as any)
const { getLookupDataSource } = useLookupDataSource({ listFormCode, isSubForm })
const fetchData = async () => {
setLoading(true)
try {
const response: any = await dataSource?.load({
filter,
skip: 0,
take: 1,
})
if (response?.data?.length) {
setFormData(response.data[0])
setFormDataOld({ ...response.data[0] })
} else {
setFormData(undefined)
setFormDataOld(undefined)
}
} catch (error: any) {
toast.push(<Notification title={error.message} type="danger" />, {
placement: 'top-end',
})
} finally {
setLoading(false)
}
}
const handleSubmit = async (e: any) => {
e.preventDefault()
if (!dataSource) {
return
}
const validationResult = refForm.current?.instance().validate()
if (!validationResult?.isValid) {
return
}
setLoading(true)
try {
const formValues = { ...formData }
if (mode === 'new') {
const result = await dataSource.insert(formValues)
if (result.data) {
if (!isSubForm) {
navigate(result.data)
} else if (props.onSubmitAction) {
props.onSubmitAction()
}
toast.push(
<Notification type="success" duration={2000}>
{translate('::ListForms.FormBilgileriKaydedildi')}
</Notification>,
{
placement: 'top-end',
},
)
} else {
throw new Error(translate('::ListForms.FormBilgileriKaydedilemedi'))
}
} else if (mode === 'edit') {
let data: any = {}
if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) {
Object.keys(formValues).forEach((key) => {
if (formValues[key] !== formDataOld[key]) {
data[key] = formValues[key]
}
})
} else {
data = { ...formValues }
}
if (gridDto?.gridOptions.keyFieldName) {
delete data[gridDto?.gridOptions.keyFieldName]
}
var result = await dataSource.update(id, data)
if (result.data > 0) {
if (!isSubForm) {
navigate(
ROUTES_ENUM.protected.admin.formView
.replace(':listFormCode', listFormCode)
.replace(':id', id!),
)
} else if (props.onSubmitAction) {
props.onSubmitAction()
}
toast.push(
<Notification type="success" duration={2000}>
{translate('::ListForms.FormBilgileriKaydedildi')}
</Notification>,
{
placement: 'top-end',
},
)
} else {
throw new Error(translate('::ListForms.FormBilgileriKaydedilemedi'))
}
}
} catch (error: any) {
toast.push(<Notification title={error.message} type="danger" />, {
placement: 'top-end',
})
} finally {
setLoading(false)
}
}
useEffect(() => {
setGridReady(false)
const initializeGrid = async () => {
const response = await getList({ listFormCode })
setGridDto(response.data)
}
initializeGrid()
}, [listFormCode])
useEffect(() => {
setGridReady(false)
if (!gridDto) {
return
}
setPermissionResults({
c:
gridDto?.gridOptions.editingOptionDto.allowAdding === true &&
checkPermission(gridDto?.gridOptions.permissionDto.c),
r: checkPermission(gridDto?.gridOptions.permissionDto.r),
u:
gridDto?.gridOptions.editingOptionDto.allowUpdating === true &&
2026-02-24 20:44:16 +00:00
checkPermission(gridDto?.gridOptions.permissionDto.u),
d:
gridDto?.gridOptions.editingOptionDto.allowDeleting === true &&
checkPermission(gridDto?.gridOptions.permissionDto.d),
e: checkPermission(gridDto?.gridOptions.permissionDto.e),
i: checkPermission(gridDto?.gridOptions.permissionDto.i),
})
// Set js and css
const grdOpt = gridDto.gridOptions
grdOpt.customJsSources.forEach(addJs)
grdOpt.customStyleSources.forEach(addCss)
// Set columns
const cols = getBandedColumns()
setCommandColumnData(cols?.find((a) => a.type == 'buttons'))
// Set data source
const dataSource: CustomStore<any, any> = createSelectDataSource(
gridDto.gridOptions,
listFormCode,
searchParams,
layoutTypes.grid,
cols,
)
setDataSource(dataSource)
const items = 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) => {
2026-05-31 18:16:41 +00:00
let editorOptions: Record<string, any> = {}
let parsedEditorOptions: Record<string, any> = {}
2026-02-24 20:44:16 +00:00
const colData = gridDto.columnFormats.find((x) => x.fieldName === i.dataField)
try {
2026-05-31 18:16:41 +00:00
parsedEditorOptions = i.editorOptions ? JSON.parse(i.editorOptions) : {}
2026-02-24 20:44:16 +00:00
} catch {}
2026-05-31 18:16:41 +00:00
const lookupEditorOptions = colData?.lookupDto?.dataSourceType
? {
dataSource: getLookupDataSource(colData?.editorOptions, colData, formData),
valueExpr: colData?.lookupDto?.valueExpr?.toLowerCase(),
displayExpr: colData?.lookupDto?.displayExpr?.toLowerCase(),
}
: {}
editorOptions = {
...lookupEditorOptions,
...parsedEditorOptions,
}
2026-02-24 20:44:16 +00:00
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,
allowEditing:
gridDto.columnFormats.find((x: any) => x.fieldName === i.dataField)
?.allowEditing ?? true,
allowAdding:
gridDto.columnFormats.find((x: any) => x.fieldName === i.dataField)
?.allowAdding ?? true,
2026-02-24 20:44:16 +00:00
dataField: i.dataField,
name: i.dataField,
editorType2: i.editorType2,
editorType:
i.editorType2 == PlatformEditorTypes.dxGridBox
? 'dxDropDownBox'
: i.editorType2 == PlatformEditorTypes.dxImageUpload
? undefined
: i.editorType2,
2026-02-24 20:44:16 +00:00
colSpan: i.colSpan,
isRequired: i.isRequired,
2026-05-31 18:16:41 +00:00
editorOptions,
2026-02-24 20:44:16 +00:00
colData,
tagBoxOptions: i.tagBoxOptions,
gridBoxOptions: i.gridBoxOptions,
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
})
.filter((a: any) => {
if (mode === 'view') {
2026-05-23 21:53:56 +00:00
return a.canRead
2026-02-24 20:44:16 +00:00
} else if (mode === 'new') {
2026-05-23 21:53:56 +00:00
return a.canCreate && a.allowAdding
2026-02-24 20:44:16 +00:00
} else if (mode === 'edit') {
2026-05-23 21:53:56 +00:00
return a.canUpdate && a.allowEditing
2026-02-24 20:44:16 +00:00
} else {
return false
}
}),
} as GroupItem
})
setFormItems(items)
setGridReady(true)
}, [gridDto])
2026-06-08 08:20:56 +00:00
// formData değiştiğinde sadece etkilenen cascading lookup datasource'ları güncelle
2026-02-24 20:44:16 +00:00
useEffect(() => {
if (!gridDto || !formItems.length) {
2026-06-08 08:20:56 +00:00
previousFormDataRef.current = formData
2026-02-24 20:44:16 +00:00
return
}
2026-06-08 08:20:56 +00:00
const previousFormData = previousFormDataRef.current
const changedFields = previousFormData
? Object.keys({ ...(previousFormData || {}), ...(formData || {}) }).filter(
(field) => !Object.is(previousFormData?.[field], formData?.[field]),
)
: []
const shouldRefreshLookup = (item: SimpleItemWithColData) => {
const cascadeParentFields = item.colData?.lookupDto?.cascadeParentFields
?.split(',')
.map((field: string) => field.trim())
.filter(Boolean)
return (
!previousFormData ||
cascadeParentFields?.some((field: string) => changedFields.includes(field))
)
}
const updateItems = (items: any[] = []) =>
items.map((item) => {
2026-02-24 20:44:16 +00:00
const colData = gridDto.columnFormats.find((x) => x.fieldName === item.dataField)
2026-06-08 08:20:56 +00:00
if (colData?.lookupDto?.dataSourceType && shouldRefreshLookup(item)) {
2026-05-31 18:16:41 +00:00
const currentDataSource = item.editorOptions?.dataSource
const keepCustomDataSource =
currentDataSource !== undefined && typeof currentDataSource?.load !== 'function'
2026-02-24 20:44:16 +00:00
return {
...item,
editorOptions: {
...item.editorOptions,
// formData null bile olsa getLookupDataSource çağrılmalı (null parametrelerle API çağrısı yapılacak)
2026-05-31 18:16:41 +00:00
dataSource: keepCustomDataSource
? currentDataSource
: getLookupDataSource(colData?.editorOptions, colData, formData || null),
2026-06-08 08:20:56 +00:00
valueExpr:
item.editorOptions?.valueExpr ?? colData?.lookupDto?.valueExpr?.toLowerCase(),
2026-05-31 18:16:41 +00:00
displayExpr:
item.editorOptions?.displayExpr ?? colData?.lookupDto?.displayExpr?.toLowerCase(),
2026-02-24 20:44:16 +00:00
},
}
}
2026-06-08 08:20:56 +00:00
if (item?.items?.length) {
return {
...item,
items: updateItems(item.items),
}
}
if (item?.tabs?.length) {
return {
...item,
tabs: item.tabs.map((tab: any) => ({
...tab,
items: updateItems(tab.items),
})),
}
}
2026-02-24 20:44:16 +00:00
return item
2026-06-08 08:20:56 +00:00
})
const hasAffectedLookup =
!previousFormData ||
formItems
.flatMap((group) => flattenFormItems([group]))
.some((item) => item.colData?.lookupDto?.dataSourceType && shouldRefreshLookup(item))
if (!hasAffectedLookup) {
previousFormDataRef.current = formData
return
}
const updatedItems = formItems.map((groupItem) => ({
...groupItem,
items: updateItems(groupItem.items as any[]),
tabs: (groupItem as any).tabs?.map((tab: any) => ({
...tab,
items: updateItems(tab.items),
})),
2026-02-24 20:44:16 +00:00
}))
2026-06-08 08:20:56 +00:00
previousFormDataRef.current = formData
2026-02-24 20:44:16 +00:00
setFormItems(updatedItems)
}, [formData, gridDto])
useEffect(() => {
if (!gridReady) {
return
}
if (mode !== 'new') {
setFilter([gridDto?.gridOptions.keyFieldName ?? 'Id', '=', id])
}
}, [id, gridReady])
useEffect(() => {
if (filter?.length) {
fetchData()
}
}, [filter])
// Auth check
useEffect(() => {
if (!permissionResults) return
const noCreate = mode === 'new' && !permissionResults.c
const noUpdate = mode === 'edit' && !permissionResults.u
const noRead = mode === 'view' && !permissionResults.r
if (noCreate || noUpdate || noRead) {
navigate(getAccessDeniedPath(location.pathname), { replace: true, state: { from: location } })
}
}, [permissionResults])
return {
loading,
gridDto,
dataSource,
commandColumnData,
filter,
formItems,
formData,
refForm,
permissionResults,
fetchData,
setFormData,
handleSubmit,
}
}
export { useGridData }