import React, { Suspense, startTransition, useCallback, useEffect, useMemo, useRef, useState, } from 'react' import '@/utils/registerDevExtremeEditors' import { useParams, useSearchParams } from 'react-router-dom' import classNames from 'classnames' import Container from '@/components/shared/Container' import { Badge, Button, Dialog } from '@/components/ui' import RichText from '@/components/shared/RichText' import { useLocalization } from '@/utils/hooks/useLocalization' import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon' import { useStoreActions, useStoreState } from '@/store/store' import { GridDto } from '@/proxy/form/models' import { getList } from '@/services/form.service' import { ListViewLayoutType } from '../admin/listForm/edit/types' import { FaChartArea, FaThLarge, FaList, FaSitemap, FaTable, FaCalendarAlt, FaProjectDiagram, FaTasks, FaInfoCircle, } from 'react-icons/fa' /* ======================= 🔥 LAZY VIEW IMPORTS ======================= */ const Grid = React.lazy(() => import('./Grid')) const CardView = React.lazy(() => import('./CardView')) const Pivot = React.lazy(() => import('./Pivot')) const Tree = React.lazy(() => import('./Tree')) const Chart = React.lazy(() => import('./Chart')) const GanttView = React.lazy(() => import('./GanttView')) const SchedulerView = React.lazy(() => import('./SchedulerView')) const TodoBoard = React.lazy(() => import('./TodoBoard')) function isLayoutValid(dto: GridDto, layout: ListViewLayoutType | undefined): boolean { if (!layout || layout === 'grid') return true if (layout === 'card') return true if (layout === 'pivot') return !!dto.gridOptions?.layoutDto?.pivot if (layout === 'chart') return !!dto.gridOptions?.layoutDto?.chart if (layout === 'tree') return !!( dto.gridOptions?.layoutDto?.tree && dto.gridOptions?.treeOptionDto?.keyExpr && dto.gridOptions?.treeOptionDto?.parentIdExpr ) if (layout === 'gantt') return !!( dto.gridOptions?.layoutDto?.gantt && dto.gridOptions?.ganttOptionDto?.keyExpr && dto.gridOptions?.ganttOptionDto?.parentIdExpr && dto.gridOptions?.ganttOptionDto?.titleExpr && dto.gridOptions?.ganttOptionDto?.startExpr && dto.gridOptions?.ganttOptionDto?.endExpr && dto.gridOptions?.ganttOptionDto?.progressExpr ) if (layout === 'scheduler') return !!( dto.gridOptions?.layoutDto?.scheduler && dto.gridOptions?.schedulerOptionDto?.textExpr && dto.gridOptions?.schedulerOptionDto?.startDateExpr && dto.gridOptions?.schedulerOptionDto?.endDateExpr ) if (layout === 'todo') return !!( dto.gridOptions?.layoutDto?.todo && dto.gridOptions?.todoOptionDto?.titleExpr && dto.gridOptions?.todoOptionDto?.statusExpr ) return false } interface ListProps { listFormCode?: string gridDto?: GridDto isSubForm?: boolean onRefreshGridDto?: () => Promise | void /** * Filter carrier of a list embedded in a page. A standalone list reads the * page address, but an embedded one is filtered by its host — the designer * builds these from static values, the URL or a Form record. */ searchParams?: URLSearchParams } const List: React.FC = ({ listFormCode: externalListFormCode, gridDto: externalGridDto, isSubForm = false, onRefreshGridDto, searchParams: externalSearchParams, }) => { const { listFormCode: routeListFormCode = '' } = useParams() const listFormCode = externalListFormCode || routeListFormCode const [routeSearchParams] = useSearchParams() const searchParams = externalSearchParams ?? routeSearchParams const { translate } = useLocalization() const mode = useStoreState((state) => state.theme.mode) const MenuIcon = useCurrentMenuIcon('w-5 h-5') const { states } = useStoreState((state) => state.admin.lists) const { setStates } = useStoreActions((a) => a.admin.lists) const activeListFormCodeRef = useRef(listFormCode) activeListFormCodeRef.current = listFormCode const [gridDtoState, setGridDtoState] = useState<{ listFormCode: string gridDto: GridDto | null }>({ listFormCode: '', gridDto: null }) const gridDto = externalGridDto ?? (gridDtoState.listFormCode === listFormCode ? gridDtoState.gridDto : null) const [viewModeState, setViewModeState] = useState<{ listFormCode: string layoutType?: ListViewLayoutType }>({ listFormCode: '' }) const viewMode = viewModeState.listFormCode === listFormCode ? viewModeState.layoutType : undefined /* ======================= 🔹 GRID DTO FETCH ======================= */ const refreshGridDto = useCallback(async () => { if (!listFormCode) return if (onRefreshGridDto) { await onRefreshGridDto() return } const requestedListFormCode = listFormCode try { const response = await getList({ listFormCode: requestedListFormCode }) if (activeListFormCodeRef.current !== requestedListFormCode) return setGridDtoState({ listFormCode: requestedListFormCode, gridDto: response.data }) } catch (err) { console.error('GridDto load error:', err) } }, [listFormCode, onRefreshGridDto]) useEffect(() => { if (!externalGridDto) void refreshGridDto() }, [externalGridDto, refreshGridDto]) /* ======================= 🔹 INITIAL VIEW MODE ======================= */ useEffect(() => { if (!gridDto) return const savedLayout = states.find((s) => s.listFormCode === listFormCode)?.layout const defaultLayout = gridDto.gridOptions?.layoutDto?.defaultLayout const candidate = savedLayout ?? defaultLayout setViewModeState({ listFormCode, layoutType: isLayoutValid(gridDto, candidate) ? candidate : 'grid', }) }, [gridDto, states, listFormCode]) /* ======================= 🔹 VIEW SWITCH HANDLER ======================= */ const setLayout = useCallback( (layout: ListViewLayoutType) => { startTransition(() => { setViewModeState({ listFormCode, layoutType: layout }) }) setStates({ listFormCode, layout }) }, [listFormCode, setStates], ) /* ======================= 🔹 PRELOAD MAP (HOVER) ======================= */ const preload = useMemo( () => ({ grid: () => import('./Grid'), card: () => import('./CardView'), pivot: () => import('./Pivot'), tree: () => import('./Tree'), chart: () => import('./Chart'), gantt: () => import('./GanttView'), scheduler: () => import('./SchedulerView'), todo: () => import('./TodoBoard'), }), [], ) /* ======================= 🔹 HEADER DESCRIPTION (LISTFORM DESCRIPTION) ======================= */ // Description bir dil anahtarıdır; karşılığı yoksa translate anahtarı geri döndürür, // o durumda kullanıcıya anahtar gösterilmez. const [descriptionOpen, setDescriptionOpen] = useState(false) const description = useMemo(() => { const key = gridDto?.gridOptions?.description if (!key) return null const text = translate('::' + key) return text && text !== key ? text : null }, [gridDto?.gridOptions?.description, translate]) if (!listFormCode || !gridDto || !viewMode) { return null } return ( {/* ======================= HEADER ======================= */}
{MenuIcon} {description ? ( ) : (

{translate('::' + gridDto.gridOptions.title)}

)} {/* ======================= VIEW BUTTONS ======================= */}
{gridDto?.gridOptions?.layoutDto.todo && gridDto?.gridOptions?.todoOptionDto?.titleExpr && gridDto?.gridOptions?.todoOptionDto?.statusExpr && ( )} {gridDto?.gridOptions?.layoutDto.gantt && gridDto?.gridOptions?.ganttOptionDto?.parentIdExpr && gridDto?.gridOptions?.ganttOptionDto?.titleExpr && ( )} {gridDto?.gridOptions?.layoutDto.tree && gridDto?.gridOptions?.treeOptionDto?.parentIdExpr && ( )} {gridDto.gridOptions.layoutDto.card && ( )} {gridDto.gridOptions.layoutDto.pivot && ( )} {gridDto.gridOptions.layoutDto.chart && ( )}
{/* ======================= VIEW RENDER ======================= */} {translate('::App.Platform.LoadingWithThreeDot')}}> {viewMode === 'grid' && ( )} {viewMode === 'card' && ( )} {viewMode === 'pivot' && ( )} {viewMode === 'tree' && ( )} {viewMode === 'scheduler' && ( )} {viewMode === 'gantt' && ( )} {viewMode === 'todo' && ( )} {viewMode === 'chart' && ( )} {description && ( setDescriptionOpen(false)} onRequestClose={() => setDescriptionOpen(false)} >

{translate('::' + gridDto.gridOptions.title)}

)}
) } export default List