sozsoft-platform/ui/src/views/form/useFormData.tsx
2026-06-08 11:20:56 +03:00

456 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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'
const flattenFormItems = (items: any[] = []): SimpleItemWithColData[] =>
items.flatMap((item) => [
...(item?.dataField ? [item] : []),
...flattenFormItems(item?.items || []),
...(item?.tabs || []).flatMap((tab: any) => flattenFormItems(tab?.items || [])),
])
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)
const previousFormDataRef = useRef<any>()
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 &&
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) => {
let editorOptions: Record<string, any> = {}
let parsedEditorOptions: Record<string, any> = {}
const colData = gridDto.columnFormats.find((x) => x.fieldName === i.dataField)
try {
parsedEditorOptions = i.editorOptions ? JSON.parse(i.editorOptions) : {}
} catch {}
const lookupEditorOptions = colData?.lookupDto?.dataSourceType
? {
dataSource: getLookupDataSource(colData?.editorOptions, colData, formData),
valueExpr: colData?.lookupDto?.valueExpr?.toLowerCase(),
displayExpr: colData?.lookupDto?.displayExpr?.toLowerCase(),
}
: {}
editorOptions = {
...lookupEditorOptions,
...parsedEditorOptions,
}
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,
dataField: i.dataField,
name: i.dataField,
editorType2: i.editorType2,
editorType:
i.editorType2 == PlatformEditorTypes.dxGridBox
? 'dxDropDownBox'
: i.editorType2 == PlatformEditorTypes.dxImageUpload
? undefined
: i.editorType2,
colSpan: i.colSpan,
isRequired: i.isRequired,
editorOptions,
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') {
return a.canRead
} else if (mode === 'new') {
return a.canCreate && a.allowAdding
} else if (mode === 'edit') {
return a.canUpdate && a.allowEditing
} else {
return false
}
}),
} as GroupItem
})
setFormItems(items)
setGridReady(true)
}, [gridDto])
// formData değiştiğinde sadece etkilenen cascading lookup datasource'ları güncelle
useEffect(() => {
if (!gridDto || !formItems.length) {
previousFormDataRef.current = formData
return
}
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) => {
const colData = gridDto.columnFormats.find((x) => x.fieldName === item.dataField)
if (colData?.lookupDto?.dataSourceType && shouldRefreshLookup(item)) {
const currentDataSource = item.editorOptions?.dataSource
const keepCustomDataSource =
currentDataSource !== undefined && typeof currentDataSource?.load !== 'function'
return {
...item,
editorOptions: {
...item.editorOptions,
// formData null bile olsa getLookupDataSource çağrılmalı (null parametrelerle API çağrısı yapılacak)
dataSource: keepCustomDataSource
? currentDataSource
: getLookupDataSource(colData?.editorOptions, colData, formData || null),
valueExpr:
item.editorOptions?.valueExpr ?? colData?.lookupDto?.valueExpr?.toLowerCase(),
displayExpr:
item.editorOptions?.displayExpr ?? colData?.lookupDto?.displayExpr?.toLowerCase(),
},
}
}
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),
})),
}
}
return item
})
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),
})),
}))
previousFormDataRef.current = formData
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 }