SchedulerView ve TodoBoard komponenti useListFormColumns ile çalıştırıldı

This commit is contained in:
Sedat Öztürk 2026-07-31 22:39:52 +03:00
parent 3a03c8e1e8
commit f8dcf25b26
3 changed files with 139 additions and 61 deletions

View file

@ -19743,8 +19743,8 @@
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.Status",
"en": "Kanban / Status",
"tr": "Kanban / Durum"
"en": "Status",
"tr": "Durum"
},
{
"resourceName": "Platform",

View file

@ -7,7 +7,6 @@ import {
FieldCustomValueTypeEnum,
GridDto,
PlatformEditorTypes,
UiLookupDataSourceTypeEnum,
} from '@/proxy/form/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
import Scheduler, {
@ -38,6 +37,8 @@ import type { ValidationRule } from 'devextreme/common'
import { FaAlignLeft, FaClock, FaHeading } from 'react-icons/fa'
import { Avatar } from '@/components/ui'
import { getUsers } from '@/services/identity.service'
import { useListFormColumns } from './useListFormColumns'
import type { GridColumnData } from './GridColumnData'
interface SchedulerViewProps {
listFormCode: string
@ -82,6 +83,11 @@ const getValueByField = (data: Record<string, any>, field?: string) => {
return resolvedField ? data[resolvedField] : undefined
}
const flattenColumns = (columns: GridColumnData[] = []): GridColumnData[] =>
columns.flatMap((column) =>
column.columns?.length ? flattenColumns(column.columns as GridColumnData[]) : [column],
)
const SchedulerView = (props: SchedulerViewProps) => {
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
const { translate } = useLocalization()
@ -158,6 +164,13 @@ const SchedulerView = (props: SchedulerViewProps) => {
}, [listFormCode])
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef: schedulerRef })
const { getBandedColumns } = useListFormColumns({
gridDto,
listFormCode,
isSubForm,
gridRef: schedulerRef,
})
const listFormColumns = useMemo(() => flattenColumns(getBandedColumns()), [getBandedColumns])
useEffect(() => {
if (!gridDto) {
@ -643,8 +656,10 @@ const SchedulerView = (props: SchedulerViewProps) => {
console.log(err)
}
const fieldName = i.fieldName!
const listFormField = i
const listFormColumn = listFormColumns.find(
(column) => column.dataField?.toLowerCase() === i.fieldName?.toLowerCase(),
)
if (listFormField?.sourceDbType === DbTypeEnum.Date) {
editorOptions = {
@ -678,48 +693,14 @@ const SchedulerView = (props: SchedulerViewProps) => {
editorType = i.editorType2
}
// Lookup DataSource oluştur
if (listFormField?.lookupDto) {
const lookup = listFormField.lookupDto
if (lookup.dataSourceType === UiLookupDataSourceTypeEnum.Query) {
editorOptions.dataSource = new CustomStore({
key: 'key',
loadMode: 'raw',
load: async () => {
try {
const { dynamicFetch } = await import('@/services/form.service')
const response = await dynamicFetch('list-form-select/lookup', 'POST', null, {
listFormCode,
listFormFieldName: fieldName,
filters: [],
})
return (response.data ?? []).map((a: any) => ({
key: a.Key,
name: a.Name,
group: a.Group,
}))
} catch (error) {
console.error('Lookup load error:', error)
return []
}
},
})
editorOptions.valueExpr = 'key'
editorOptions.displayExpr = 'name'
} else if (lookup.dataSourceType === UiLookupDataSourceTypeEnum.StaticData) {
if (lookup.lookupQuery) {
try {
const staticData = JSON.parse(lookup.lookupQuery)
editorOptions.dataSource = staticData
editorOptions.valueExpr = lookup.valueExpr || 'key'
editorOptions.displayExpr = lookup.displayExpr || 'name'
} catch (error) {
console.error('Static data parse error:', error)
}
}
}
const lookup = listFormColumn?.lookup
if (lookup) {
editorOptions.dataSource =
typeof lookup.dataSource === 'function'
? lookup.dataSource({ data: e.appointmentData ?? {} })
: lookup.dataSource
editorOptions.valueExpr = lookup.valueExpr
editorOptions.displayExpr = lookup.displayExpr
}
const item: SimpleItemWithColData = {
@ -822,7 +803,7 @@ const SchedulerView = (props: SchedulerViewProps) => {
e.form.option('showValidationSummary', false)
e.form.option('items', result)
},
[gridDto, translate, isPopupFullScreen, listFormCode, useMobileEditPopup],
[gridDto, translate, isPopupFullScreen, listFormColumns, useMobileEditPopup],
)
const configuredEditPopup = gridDto?.gridOptions.editingOptionDto?.popup

View file

@ -45,6 +45,9 @@ import { getTenants } from '@/services/tenant.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { usePermission } from '@/utils/hooks/usePermission'
import { usePWA } from '@/utils/hooks/usePWA'
import { useListFormColumns } from './useListFormColumns'
import type { GridColumnData } from './GridColumnData'
import { useStoreState } from '@/store/store'
dayjs.extend(relativeTime)
@ -78,6 +81,13 @@ interface UserOption {
avatarUrl?: string
}
type LookupDisplayValues = Record<string, Map<string, string>>
const flattenColumns = (columns: GridColumnData[] = []): GridColumnData[] =>
columns.flatMap((column) =>
column.columns?.length ? flattenColumns(column.columns as GridColumnData[]) : [column],
)
const fieldValue = (row: TodoRow, field?: string) => (field ? row[field] : undefined)
const tenantIdValue = (row: TodoRow) => row.TenantId ?? row.tenantId
@ -157,7 +167,7 @@ const priorityLabel = (
: translate('::ListForms.TodoBoard.NoPriority')
}
const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
const TodoBoard = ({ listFormCode, searchParams, isSubForm, gridDto }: TodoBoardProps) => {
const options = gridDto.gridOptions.todoOptionDto as TodoOptionDto
const editingOptions = gridDto.gridOptions.editingOptionDto
const canAdd =
@ -167,9 +177,40 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
const canDelete =
editingOptions?.allowDeleting === true && Boolean(gridDto.gridOptions.deleteServiceAddress)
const keyField = gridDto.gridOptions.keyFieldName ?? 'id'
const userNameField =
gridDto.columnFormats?.find((column) => column.fieldName?.toLowerCase() === 'username')
?.fieldName ?? 'UserName'
const stateStoring = gridDto.gridOptions.stateStoringDto
const currentUserName = useStoreState((state) => state.auth.user.userName)
const gridRef = useRef<DataGridRef<any, any>>()
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
const { getBandedColumns, loadLookupDisplayValues } = useListFormColumns({
gridDto,
listFormCode,
isSubForm,
gridRef,
})
const getBandedColumnsRef = useRef(getBandedColumns)
const loadLookupDisplayValuesRef = useRef(loadLookupDisplayValues)
getBandedColumnsRef.current = getBandedColumns
loadLookupDisplayValuesRef.current = loadLookupDisplayValues
const listFormColumnsCacheRef = useRef<{
gridDto?: GridDto
listFormCode: string
columns: GridColumnData[]
}>()
if (
!listFormColumnsCacheRef.current ||
listFormColumnsCacheRef.current.gridDto !== gridDto ||
listFormColumnsCacheRef.current.listFormCode !== listFormCode
) {
listFormColumnsCacheRef.current = {
gridDto,
listFormCode,
columns: flattenColumns(getBandedColumnsRef.current()),
}
}
const listFormColumns = listFormColumnsCacheRef.current.columns
const [store, setStore] = useState<CustomStore<any, any>>()
const [rows, setRows] = useState<TodoRow[]>([])
const [statuses, setStatuses] = useState<string[]>([])
@ -194,6 +235,7 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
const [userOptions, setUserOptions] = useState<UserOption[]>([])
const [tenantNames, setTenantNames] = useState<Map<string, string>>(new Map())
const [usersLoading, setUsersLoading] = useState(false)
const [lookupDisplayValues, setLookupDisplayValues] = useState<LookupDisplayValues>({})
const columnConfigRef = useRef<StoredBoardColumns>({ columns: [], retired: [] })
const dragActiveRef = useRef(false)
const { translate } = useLocalization()
@ -201,6 +243,23 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
const isPwaMode = usePWA()
const uncategorized = translate('::ListForms.TodoBoard.Uncategorized')
const lookupValuesForField = useCallback(
(field?: string) => {
if (!field) return undefined
const dataField = listFormColumns.find(
(column) => column.dataField?.toLowerCase() === field.toLowerCase(),
)?.dataField
return dataField ? lookupDisplayValues[dataField] : undefined
},
[listFormColumns, lookupDisplayValues],
)
const lookupText = useCallback(
(field: string | undefined, value: unknown) =>
lookupValuesForField(field)?.get(String(value)) ?? String(value ?? ''),
[lookupValuesForField],
)
const clearDragState = useCallback(() => {
dragActiveRef.current = false
setDraggedKey(undefined)
@ -267,6 +326,22 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
}
}, [])
useEffect(() => {
let active = true
setLookupDisplayValues({})
loadLookupDisplayValuesRef
.current(listFormColumns)
.then((values) => {
if (active) setLookupDisplayValues(values)
})
.catch((error) => console.error('TodoBoard lookup load error:', error))
return () => {
active = false
}
}, [listFormColumns])
useEffect(() => {
let cancelled = false
@ -384,14 +459,23 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
[options.assigneeExpr, rows],
)
const assigneeOptions = useMemo<UserOption[]>(() => {
const knownUsers = new Set(userOptions.map((option) => option.value))
const lookupValues = lookupValuesForField(options.assigneeExpr)
const avatarUrls = new Map(userOptions.map((option) => [option.value, option.avatarUrl]))
const configuredOptions = lookupValues
? Array.from(lookupValues, ([value, label]) => ({
value,
label,
avatarUrl: avatarUrls.get(value),
}))
: userOptions
const knownUsers = new Set(configuredOptions.map((option) => option.value))
return [
...userOptions,
...configuredOptions,
...assigneeSuggestions
.filter((value) => !knownUsers.has(value))
.map((value) => ({ value, label: value })),
]
}, [assigneeSuggestions, userOptions])
}, [assigneeSuggestions, lookupValuesForField, options.assigneeExpr, userOptions])
const assigneeOptionMap = useMemo(
() => new Map(assigneeOptions.map((option) => [option.value, option])),
[assigneeOptions],
@ -681,6 +765,7 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
const values: Record<string, any> = {
[options.titleExpr]: title,
[options.statusExpr]: status,
[userNameField]: currentUserName,
}
if (options.completedExpr) values[options.completedExpr] = false
if (options.orderExpr) values[options.orderExpr] = itemsForStatus(status).length
@ -778,12 +863,11 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
<section className="min-h-[420px] rounded-xl shadow-sm">
{renderToolbar(true)}
<div className="mb-6 flex justify-end border-b border-gray-200 dark:border-gray-700">
{editingOptions?.allowUpdating === true &&
!gridDto.gridOptions.updateServiceAddress && (
<span className="text-xs text-amber-600">
{translate('::ListForms.TodoBoard.EditRequiresUpdateService')}
</span>
)}
{editingOptions?.allowUpdating === true && !gridDto.gridOptions.updateServiceAddress && (
<span className="text-xs text-amber-600">
{translate('::ListForms.TodoBoard.EditRequiresUpdateService')}
</span>
)}
</div>
<div className="grid gap-5 lg:grid-cols-3">
<div className="space-y-5 lg:col-span-2">
@ -941,6 +1025,16 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
/>
</div>
)}
<div>
<label className={labelClass}>
{translate('::App.Listform.ListformField.UserName')}
</label>
<input
readOnly
className={`${inputClass} cursor-default`}
value={String(draft[userNameField] ?? '')}
/>
</div>
{options.statusExpr && (
<div>
<label className={labelClass}>{translate('::ListForms.TodoBoard.Status')}</label>
@ -957,7 +1051,7 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
>
{statuses.map((status) => (
<option key={status} value={status}>
{status}
{lookupText(options.statusExpr, status)}
</option>
))}
</select>
@ -1176,8 +1270,11 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
</form>
) : (
<>
<h4 className="min-w-0 truncate font-sm" title={status}>
{status}
<h4
className="min-w-0 truncate font-sm"
title={lookupText(options.statusExpr, status)}
>
{lookupText(options.statusExpr, status)}
</h4>
{canUpdate && (
<button
@ -1412,7 +1509,7 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
className="inline-flex items-center gap-1 rounded-full bg-blue-50 px-2 py-0.5 text-[11px] text-blue-700 dark:bg-blue-950 dark:text-blue-200"
>
<FaTag />
{tag}
{lookupText(options.tagExpr, tag)}
</span>
))}
{tags.length > 3 && (