TreeList ve ListForm Layout hatası düzeltildi.

This commit is contained in:
Sedat Öztürk 2025-11-08 02:00:51 +03:00
parent a2632705c7
commit f3e187644c
26 changed files with 1210 additions and 140 deletions

View file

@ -149,6 +149,19 @@ public class GridOptionsDto : AuditedEntityDto<Guid>
set { PivotOptionJson = JsonSerializer.Serialize(value); }
}
[JsonIgnore]
public string TreeOptionJson { get; set; }
public TreeOptionDto TreeOptionDto
{
get
{
if (!string.IsNullOrEmpty(TreeOptionJson))
return JsonSerializer.Deserialize<TreeOptionDto>(TreeOptionJson);
return new TreeOptionDto();
}
set { TreeOptionJson = JsonSerializer.Serialize(value); }
}
[JsonIgnore]
public string PagerOptionJson { get; set; }
public GridPagerOptionDto PagerOptionDto

View file

@ -5,6 +5,7 @@ public class LayoutDto
public bool Grid { get; set; } = true;
public bool Card { get; set; } = true;
public bool Pivot { get; set; } = true;
public bool Tree { get; set; } = true;
public bool Chart { get; set; } = true;
public string DefaultLayout { get; set; } = "grid";
public int CardLayoutColumn { get; set; } = 4;

View file

@ -0,0 +1,37 @@
namespace Kurs.Platform.ListForms;
/// <summary>
/// TreeList için özel ayarları içerir
/// </summary>
public class TreeOptionDto
{
/// <summary>
/// Parent kaydı belirten field adı (örn: "parentId")
/// </summary>
public string ParentIdExpr { get; set; }
/// <summary>
/// Alt kayıtların olup olmadığını belirten field adı (opsiyonel)
/// </summary>
public string HasItemsExpr { get; set; }
/// <summary>
/// Root (en üst) seviyedeki kayıtların parent değeri (genelde null veya 0)
/// </summary>
public object RootValue { get; set; } = null;
/// <summary>
/// Başlangıçta açık olacak node'ların ID'leri
/// </summary>
public object[] ExpandedRowKeys { get; set; } = [];
/// <summary>
/// Tüm node'ları başlangıçta açık göster
/// </summary>
public bool AutoExpandAll { get; set; } = false;
/// <summary>
/// Alt kayıtlar seçildiğinde parent kayıtları da seç (recursive selection)
/// </summary>
public bool RecursiveSelection { get; set; } = false;
}

View file

@ -46,6 +46,7 @@ public class ListFormEditTabs
public const string SelectForm = "select";
public const string ColumnForm = "column";
public const string PivotForm = "pivot";
public const string TreeForm = "tree";
public const string PagerForm = "pager";
public const string StateForm = "state";
public const string SubFormJsonRow = "subForm";

View file

@ -146,6 +146,10 @@ public class ListFormsAppService : CrudAppService<
{
item.PivotOptionJson = JsonSerializer.Serialize(input.PivotOptionDto);
}
else if (input.EditType == ListFormEditTabs.TreeForm)
{
item.TreeOptionJson = JsonSerializer.Serialize(input.TreeOptionDto);
}
else if (input.EditType == ListFormEditTabs.PagerForm)
{
item.PagerOptionJson = JsonSerializer.Serialize(input.PagerOptionDto);

View file

@ -3943,6 +3943,12 @@
"en": "Pivot Settings",
"tr": "Pivot Settings"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.TabTree",
"en": "Tree",
"tr": "Ağaç"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.TabDetails",
@ -4189,6 +4195,12 @@
"en": "Pivot Layout",
"tr": "Pivot Düzeni"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.DetailsLayoutDto.TreeLayout",
"en": "Tree Layout",
"tr": "Ağaç Düzeni"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.DetailsLayoutDto.ChartLayout",
@ -5167,6 +5179,36 @@
"en": "Visible",
"tr": "Görünür"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.ParentIdExpr",
"en": "Parent ID Expression",
"tr": "Üst Kimlik İfadesi"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.RootValue",
"en": "Root Value",
"tr": "Kök Değer"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.HasItemsExpr",
"en": "Has Items Expression",
"tr": "Öğeler Var İfadesi"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.RecursiveSelection",
"en": "Recursive Selection",
"tr": "Özyinelemeli Seçim"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.AutoExpandAll",
"en": "Auto Expand All",
"tr": "Otomatik Tümünü Genişlet"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.GroupingAutoExpandAll",

View file

@ -5,4 +5,6 @@ public static class ListFormTypeEnum
public const string List = "List";
public const string Form = "Form";
public const string Chart = "Chart";
public const string Pivot = "Pivot";
public const string Tree = "Tree";
}

View file

@ -32,6 +32,7 @@ public class ListForm : FullAuditedEntity<Guid>
public string DefaultFilter { get; set; } // Her sorgunun sonuna eklenecek default WHERE condition
public string ColumnOptionJson { get; set; }
public string PivotOptionJson { get; set; }
public string TreeOptionJson { get; set; }
public string FilterRowJson { get; set; } // Filtre ayarlari, Json olarak tutulur, donus sinifi FilterRowDto
public string RowJson { get; set; } // Row ayarları, Json olarak tutulur, donus sinifi FilterRowDto
public string HeaderFilterJson { get; set; } // Header filtreleme ayarlari, Json olarak tutulur

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Kurs.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20251107194939_Initial")]
[Migration("20251107212125_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -5210,6 +5210,9 @@ namespace Kurs.Platform.Migrations
b.Property<string>("TooltipJson")
.HasColumnType("text");
b.Property<string>("TreeOptionJson")
.HasColumnType("nvarchar(max)");
b.Property<string>("UpdateCommand")
.HasColumnType("text");

View file

@ -1885,6 +1885,7 @@ namespace Kurs.Platform.Migrations
DefaultFilter = table.Column<string>(type: "text", nullable: true),
ColumnOptionJson = table.Column<string>(type: "text", nullable: true),
PivotOptionJson = table.Column<string>(type: "text", nullable: true),
TreeOptionJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
FilterRowJson = table.Column<string>(type: "text", nullable: true),
RowJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
HeaderFilterJson = table.Column<string>(type: "text", nullable: true),

View file

@ -5207,6 +5207,9 @@ namespace Kurs.Platform.Migrations
b.Property<string>("TooltipJson")
.HasColumnType("text");
b.Property<string>("TreeOptionJson")
.HasColumnType("nvarchar(max)");
b.Property<string>("UpdateCommand")
.HasColumnType("text");

View file

@ -39,6 +39,7 @@ export const ListFormEditTabs = {
SelectForm: 'select',
ColumnForm: 'column',
PivotForm: 'pivot',
TreeForm: 'tree',
PagerForm: 'pager',
StateForm: 'state',
SubForm: 'subForm',
@ -86,6 +87,7 @@ export const tabVisibilityConfig: Record<string, string[]> = {
'select',
'columns',
'pivots',
'tree',
'pager',
'state',
'extrafilter',

View file

@ -377,6 +377,15 @@ export interface GridPivotOptionDto {
chartCommonSeriesType: SeriesType
}
export interface TreeOptionDto {
parentIdExpr?: string
hasItemsExpr?: string
rootValue?: any
expandedRowKeys?: any[]
autoExpandAll?: boolean
recursiveSelection?: boolean
}
export interface GridEditingDto {
mode?: GridsEditMode
refreshMode?: GridsEditRefreshMode
@ -472,6 +481,8 @@ export interface GridOptionsDto extends AuditedEntityDto<string> {
columnOptionDto: GridColumnOptionDto
pivotOptionJson?: string
pivotOptionDto: GridPivotOptionDto
treeOptionJson?: string
treeOptionDto: TreeOptionDto
pagerOptionJson?: string
pagerOptionDto: GridPagerOptionDto
editingOptionJson?: string
@ -800,6 +811,7 @@ export interface LayoutDto {
grid: boolean
card: boolean
pivot: boolean
tree: boolean
chart: boolean
defaultLayout: ListViewLayoutType
cardLayoutColumn: number

View file

@ -125,7 +125,7 @@ const useListFormColumns = ({
gridDto,
listFormCode,
isSubForm,
gridRef,
gridRef
}: {
gridDto?: GridDto
listFormCode: string

View file

@ -7,15 +7,17 @@ import { GridOptionsDto } from '../proxy/form/models'
import { GridColumnData } from '../views/list/GridColumnData'
import { dynamicFetch } from '../services/form.service'
import { MULTIVALUE_DELIMITER } from '../constants/app.constant'
import { TreeList } from 'devextreme-react'
const filteredGridPanelColor = 'rgba(10, 200, 10, 0.5)' // kullanici tanimli filtre ile filtrelenmis gridin paneline ait renk
const useListFormCustomDataSource = ({
gridRef,
pivotRef,
}: {
gridRef?: MutableRefObject<DataGrid<any, any> | undefined>
pivotRef?: MutableRefObject<PivotGrid | undefined>
gridRef:
| MutableRefObject<DataGrid<any, any> | undefined>
| MutableRefObject<PivotGrid | undefined>
| MutableRefObject<TreeList<any, any> | undefined>
}) => {
const createSelectDataSource = useCallback(
(
@ -58,8 +60,15 @@ const useListFormCustomDataSource = ({
if (gridRef?.current) {
//TODO:
}
const columns =
(gridRef?.current?.instance.option('columns') as GridColumnData[]) ?? cols
// Type guard to handle union type for gridRef
let columns = cols
if (gridRef?.current?.instance) {
const instance = gridRef.current.instance as any
const instanceColumns = instance.option('columns')
if (instanceColumns) {
columns = instanceColumns as GridColumnData[]
}
}
// URL'deki filtreyi grid'in mevcut filtrelerinin üzerine uygula
if (columns?.length && searchParams) {
const filters: (string[] | string)[] = []

View file

@ -36,6 +36,7 @@ import FormTabWidgets from './FormTabWidgets'
import FormTabExtraFilters from './FormTabExtraFilters'
import FormTabEditForm from './FormTabEditForm'
import FormTabPivots from './FormTabPivots'
import FormTabTree from './FormTabTree'
import ChartTabAnimation from './ChartTabAnimation'
import ChartTabAnnotations from './ChartTabAnnotations'
import ChartTabZoomAndPan from './ChartTabZoomAndPan'
@ -232,6 +233,9 @@ const FormEdit = () => {
{visibleTabs.includes('pivots') && (
<TabNav value="pivots">{translate('::ListForms.ListFormEdit.TabPivots')}</TabNav>
)}
{visibleTabs.includes('tree') && (
<TabNav value="tree">{translate('::ListForms.ListFormEdit.TabTree')}</TabNav>
)}
{visibleTabs.includes('pager') && (
<TabNav value="pager">{translate('::ListForms.ListFormEdit.TabPaging')}</TabNav>
)}
@ -355,6 +359,9 @@ const FormEdit = () => {
<TabContent value="pivots" className="px-2">
<FormTabPivots onSubmit={onSubmit} />
</TabContent>
<TabContent value="tree" className="px-2">
<FormTabTree onSubmit={onSubmit} />
</TabContent>
<TabContent value="pager" className="px-2">
<FormTabPager onSubmit={onSubmit} />
</TabContent>

View file

@ -26,6 +26,8 @@ const schema = Yup.object().shape({
grid: Yup.boolean(),
card: Yup.boolean(),
pivot: Yup.boolean(),
chart: Yup.boolean(),
tree: Yup.boolean(),
defaultLayout: Yup.string(),
cardLayoutColumn: Yup.number(),
}),
@ -123,57 +125,6 @@ function FormTabDetails(
</Field>
</FormItem>
{values.listFormType === 'List' && (
<>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.DefaultLayout')}
invalid={errors.layoutDto?.defaultLayout && touched.layoutDto?.defaultLayout}
errorMessage={errors.layoutDto?.defaultLayout}
>
<Field
type="text"
autoComplete="off"
name="layoutDto.defaultLayout"
placeholder={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.DefaultLayout',
)}
>
{({ field, form }: FieldProps<SelectBox>) => (
<Select
field={field}
form={form}
isClearable={true}
options={listFormDefaultLayoutOptions}
value={listFormDefaultLayoutOptions?.filter(
(option) => option.value === values.layoutDto.defaultLayout,
)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.CardLayoutColumn')}
invalid={
errors.layoutDto?.cardLayoutColumn && touched.layoutDto?.cardLayoutColumn
}
errorMessage={errors.layoutDto?.cardLayoutColumn}
>
<Field
type="number"
className="w-20"
autoComplete="off"
name="layoutDto.cardLayoutColumn"
placeholder={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.CardLayoutColumn',
)}
component={Input}
/>
</FormItem>
</>
)}
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsTitle')}
invalid={errors.title && touched.title}
@ -268,6 +219,57 @@ function FormTabDetails(
</FormItem>
</div>
{values.listFormType === 'List' && (
<>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.DefaultLayout')}
invalid={errors.layoutDto?.defaultLayout && touched.layoutDto?.defaultLayout}
errorMessage={errors.layoutDto?.defaultLayout}
>
<Field
type="text"
autoComplete="off"
name="layoutDto.defaultLayout"
placeholder={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.DefaultLayout',
)}
>
{({ field, form }: FieldProps<SelectBox>) => (
<Select
field={field}
form={form}
isClearable={true}
options={listFormDefaultLayoutOptions}
value={listFormDefaultLayoutOptions?.filter(
(option) => option.value === values.layoutDto.defaultLayout,
)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.CardLayoutColumn')}
invalid={
errors.layoutDto?.cardLayoutColumn && touched.layoutDto?.cardLayoutColumn
}
errorMessage={errors.layoutDto?.cardLayoutColumn}
>
<Field
type="number"
className="w-20"
autoComplete="off"
name="layoutDto.cardLayoutColumn"
placeholder={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.CardLayoutColumn',
)}
component={Input}
/>
</FormItem>
</>
)}
<div className="flex gap-2">
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GridLayout')}
@ -324,6 +326,20 @@ function FormTabDetails(
component={Checkbox}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.TreeLayout')}
invalid={errors.layoutDto?.tree && touched.layoutDto?.tree}
errorMessage={errors.layoutDto?.tree}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.tree"
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.TreeLayout')}
component={Checkbox}
/>
</FormItem>
</div>
<FormItem

View file

@ -0,0 +1,180 @@
import { Container } from '@/components/shared'
import {
Button,
Notification,
Checkbox,
FormContainer,
FormItem,
Input,
Select,
toast,
} from '@/components/ui'
import { ListFormEditTabs } from '@/proxy/admin/list-form/options'
import { useStoreState } from '@/store'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Field, FieldProps, Form, Formik } from 'formik'
import * as Yup from 'yup'
import { FormEditProps } from './FormEdit'
import { SelectBoxOption } from '@/shared/types'
import { useEffect, useState } from 'react'
import { getListFormFields } from '@/services/admin/list-form-field.service'
import { groupBy } from 'lodash'
import { useParams } from 'react-router-dom'
const validationSchema = Yup.object().shape({})
function FormTabTree(props: FormEditProps) {
const { listFormCode } = useParams()
const { translate } = useLocalization()
const [fieldList, setFieldList] = useState<SelectBoxOption[]>([])
const getFields = async () => {
if (!listFormCode) {
return
}
try {
const resp = await getListFormFields({
listFormCode,
sorting: 'ListOrderNo',
maxResultCount: 1000,
})
if (resp.data?.items) {
const fieldNames = groupBy(resp?.data?.items, 'fieldName')
setFieldList(
Object.keys(fieldNames).map((a) => ({
value: a,
label: a,
})),
)
}
} catch (error: any) {
toast.push(
<Notification type="danger" duration={2000}>
Alanlar getirilemedi
{error.toString()}
</Notification>,
{
placement: 'top-end',
},
)
}
}
useEffect(() => {
getFields()
}, [listFormCode])
const listFormValues = useStoreState((s) => s.admin.lists.values)
if (!listFormValues) {
return null
}
return (
<Container className="grid xl:grid-cols-2">
<Formik
initialValues={listFormValues}
validationSchema={validationSchema}
onSubmit={async (values, formikHelpers) => {
await props.onSubmit(ListFormEditTabs.TreeForm, values, formikHelpers)
}}
>
{({ touched, errors, isSubmitting, values }) => (
<Form>
<FormContainer size="sm">
<FormItem
label={translate('::ListForms.ListFormEdit.ParentIdExpr')}
invalid={errors.treeOptionDto?.parentIdExpr && touched.treeOptionDto?.parentIdExpr}
errorMessage={errors.treeOptionDto?.parentIdExpr}
>
<Field
type="text"
name="treeOptionDto.parentIdExpr"
placeholder={translate('::ListForms.ListFormEdit.ParentIdExpr')}
>
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select
field={field}
form={form}
isClearable={true}
options={fieldList}
value={fieldList?.filter(
(option) => option.value === values.treeOptionDto.parentIdExpr,
)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
menuPlacement="auto"
maxMenuHeight={150}
/>
)}
</Field>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.HasItemsExpr')}
invalid={errors.treeOptionDto?.hasItemsExpr && touched.treeOptionDto?.hasItemsExpr}
errorMessage={errors.treeOptionDto?.hasItemsExpr}
>
<Field
type="text"
autoComplete="off"
name="treeOptionDto.hasItemsExpr"
placeholder={translate('::ListForms.ListFormEdit.HasItemsExpr')}
component={Input}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.RootValue')}
invalid={!!(errors.treeOptionDto?.rootValue && touched.treeOptionDto?.rootValue)}
errorMessage={errors.treeOptionDto?.rootValue as string}
>
<Field
type="text"
autoComplete="off"
name="treeOptionDto.rootValue"
placeholder={translate('::ListForms.ListFormEdit.RootValue')}
component={Input}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.AutoExpandAll')}
invalid={
errors.treeOptionDto?.autoExpandAll && touched.treeOptionDto?.autoExpandAll
}
errorMessage={errors.treeOptionDto?.autoExpandAll}
>
<Field
name="treeOptionDto.autoExpandAll"
placeholder={translate('::ListForms.ListFormEdit.AutoExpandAll')}
component={Checkbox}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.RecursiveSelection')}
invalid={
errors.treeOptionDto?.recursiveSelection &&
touched.treeOptionDto?.recursiveSelection
}
errorMessage={errors.treeOptionDto?.recursiveSelection}
>
<Field
name="treeOptionDto.recursiveSelection"
placeholder={translate('::ListForms.ListFormEdit.RecursiveSelection')}
component={Checkbox}
/>
</FormItem>
<Button block variant="solid" loading={isSubmitting} type="submit" className="my-2">
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
</FormContainer>
</Form>
)}
</Formik>
</Container>
)
}
export default FormTabTree

View file

@ -211,6 +211,7 @@ export const listFormDefaultLayoutOptions = [
{ value: 'card', label: 'Card' },
{ value: 'pivot', label: 'Pivot' },
{ value: 'chart', label: 'Chart' },
{ value: 'tree', label: 'Tree' },
]
export const listFormAlignmentOptions = [

View file

@ -1,3 +1,3 @@
export type ChartOperation = '' | 'select' | 'insert' | 'update' | 'delete'
export type ChartDialogType = '' | 'pane' | 'serie' | 'annotation' | 'axis'
export type ListViewLayoutType = 'grid' | 'card' | 'pivot' | 'chart'
export type ListViewLayoutType = 'grid' | 'card' | 'pivot' | 'tree' | 'chart'

View file

@ -44,7 +44,6 @@ import { Item } from 'devextreme-react/toolbar'
import { DataType } from 'devextreme/common'
import { captionize } from 'devextreme/core/utils/inflector'
import CustomStore from 'devextreme/data/custom_store'
import { GroupItem } from 'devextreme/ui/form'
import { useCallback, useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import SubForms from '../form/SubForms'

View file

@ -2,7 +2,7 @@ import { useLocation, useParams, useSearchParams } from 'react-router-dom'
import { useEffect, useState } from 'react'
import Container from '@/components/shared/Container'
import Grid from './Grid'
import { FaChartArea, FaList, FaTable, FaTh, FaUser } from 'react-icons/fa'
import { FaChartArea, FaList, FaSitemap, FaTable, FaTh, FaUser } from 'react-icons/fa'
import { useStoreActions, useStoreState } from '@/store/store'
import classNames from 'classnames'
import { useLocalization } from '@/utils/hooks/useLocalization'
@ -10,6 +10,7 @@ import { GridDto } from '@/proxy/form/models'
import Card from './Card'
import { Badge, Button } from '@/components/ui'
import Pivot from './Pivot'
import Tree from './Tree'
import { getList } from '@/services/form.service'
import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon'
import { ListViewLayoutType } from '../admin/listForm/edit/types'
@ -41,17 +42,21 @@ const List = () => {
}
useEffect(() => {
refreshGridDto().then(() => {
//base içerisine kaydedilen state içerisinden bakılacak
const listFormStates = states.find((a) => a.listFormCode === listFormCode)
if (listFormStates) {
setViewMode(listFormStates.layout || 'grid')
} else {
setViewMode(gridDto?.gridOptions?.layoutDto.defaultLayout || 'grid')
}
})
refreshGridDto()
}, [listFormCode])
useEffect(() => {
const listFormStates = states.find((a) => a.listFormCode === listFormCode)
if (listFormStates) {
setViewMode(listFormStates.layout)
}
if (gridDto) {
setViewMode(gridDto?.gridOptions?.layoutDto.defaultLayout)
}
}, [gridDto])
if (!listFormCode) {
return null
}
@ -86,6 +91,21 @@ const List = () => {
)}
<div className="flex gap-1">
{gridDto?.gridOptions?.layoutDto.tree &&
gridDto?.gridOptions?.treeOptionDto?.parentIdExpr && (
<Button
size="xs"
variant={viewMode === 'tree' ? 'solid' : 'default'}
onClick={() => {
setViewMode('tree')
setStates({ listFormCode, layout: 'tree' })
}}
title="TreeList Görünümü"
>
<FaSitemap className="w-4 h-4" />
</Button>
)}
{gridDto?.gridOptions?.layoutDto.grid && (
<Button
size="xs"
@ -152,6 +172,13 @@ const List = () => {
gridDto={gridDto}
refreshGridDto={refreshGridDto}
/>
) : viewMode === 'tree' ? (
<Tree
listFormCode={listFormCode}
searchParams={searchParams}
isSubForm={false}
gridDto={gridDto}
/>
) : viewMode === 'card' ? (
<Card
listFormCode={listFormCode}

View file

@ -34,11 +34,7 @@ import {
import { useFilters } from './useFilters'
import WidgetGroup from '@/components/common/WidgetGroup'
import { Button } from '@/components/ui'
import {
FaCog,
FaTimes,
FaUndo,
} from 'react-icons/fa'
import { FaCog, FaTimes, FaUndo } from 'react-icons/fa'
import { usePermission } from '@/utils/hooks/usePermission'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { usePWA } from '@/utils/hooks/usePWA'
@ -74,7 +70,7 @@ const Pivot = (props: PivotProps) => {
listFormCode,
})
const { createSelectDataSource } = useListFormCustomDataSource({ pivotRef: gridRef })
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
const { getBandedColumns } = useListFormColumns({
gridDto,
listFormCode,
@ -413,56 +409,3 @@ const Pivot = (props: PivotProps) => {
}
export default Pivot
/*
<Toolbar visible={toolbarData.length > 0 || filterToolbarData.length > 0}>
{toolbarData.map((item) => (
<Item key={item.name} {...item}></Item>
))}
{filterToolbarData.map((item) => (
<Item key={item.name} {...item}></Item>
))}
</Toolbar>
<Sorting mode={gridDto.gridOptions?.sortMode}></Sorting>
<FilterRow
visible={gridDto.gridOptions.filterRowDto?.visible}
applyFilter={gridDto.gridOptions.filterRowDto?.applyFilter}
></FilterRow>
<FilterPanel visible={gridDto.gridOptions.filterPanelDto.visible}></FilterPanel>
<HeaderFilter visible={gridDto.gridOptions.headerFilterDto.visible}></HeaderFilter>
<SearchPanel
visible={gridDto.gridOptions.searchPanelDto.visible}
width={gridDto.gridOptions.searchPanelDto.width}
></SearchPanel>
<GroupPanel visible={gridDto.gridOptions.groupPanelDto?.visible}></GroupPanel>
<Grouping autoExpandAll={gridDto.gridOptions.groupPanelDto?.autoExpandAll}></Grouping>
<Selection
mode={gridDto.gridOptions.selectionDto?.mode}
allowSelectAll={gridDto.gridOptions.selectionDto?.allowSelectAll}
selectAllMode={gridDto.gridOptions.selectionDto?.selectAllMode}
showCheckBoxesMode={gridDto.gridOptions.selectionDto?.showCheckBoxesMode}
></Selection>
<Pager
visible={gridDto.gridOptions.pagerOptionDto?.visible}
allowedPageSizes={gridDto.gridOptions.pagerOptionDto?.allowedPageSizes
?.split(',')
.map((a) => +a)}
showPageSizeSelector={gridDto.gridOptions.pagerOptionDto?.showPageSizeSelector}
showInfo={gridDto.gridOptions.pagerOptionDto?.showInfo}
showNavigationButtons={gridDto.gridOptions.pagerOptionDto?.showNavigationButtons}
infoText={gridDto.gridOptions.pagerOptionDto?.infoText}
displayMode={gridDto.gridOptions.pagerOptionDto?.displayMode}
></Pager>
<ColumnChooser
enabled={gridDto.gridOptions.columnOptionDto?.columnChooserEnabled}
mode={gridDto.gridOptions.columnOptionDto?.columnChooserMode}
></ColumnChooser>
<ColumnFixing
enabled={gridDto.gridOptions.columnOptionDto?.columnFixingEnabled}
></ColumnFixing>
<Scrolling mode={gridDto.gridOptions.pagerOptionDto?.scrollingMode}></Scrolling>
<LoadPanel
enabled={gridDto.gridOptions.pagerOptionDto?.loadPanelEnabled}
text={gridDto.gridOptions.pagerOptionDto?.loadPanelText}
></LoadPanel>
*/

715
ui/src/views/list/Tree.tsx Normal file
View file

@ -0,0 +1,715 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import Container from '@/components/shared/Container'
import { Dialog, Notification, toast } from '@/components/ui'
import { DX_CLASSNAMES } from '@/constants/app.constant'
import { GridDto, ListFormCustomizationTypeEnum } from '@/proxy/form/models'
import {
getListFormCustomization,
postListFormCustomization,
} from '@/services/list-form-customization.service'
import { useListFormColumns } from '@/shared/useListFormColumns'
import { useListFormCustomDataSource } from '@/shared/useListFormCustomDataSource'
import { useLocalization } from '@/utils/hooks/useLocalization'
import useResponsive from '@/utils/hooks/useResponsive'
import { Template } from 'devextreme-react/core/template'
import TreeListDx, {
ColumnChooser,
ColumnFixing,
Editing,
FilterPanel,
FilterRow,
HeaderFilter,
IStateStoringProps,
LoadPanel,
Pager,
RemoteOperations,
Scrolling,
SearchPanel,
Selection,
Sorting,
StateStoring,
TreeListTypes,
} from 'devextreme-react/tree-list'
import { Item, Toolbar } from 'devextreme-react/toolbar'
import CustomStore from 'devextreme/data/custom_store'
import { useCallback, useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import { RowMode } from '../form/types'
import { GridColumnData } from './GridColumnData'
import GridFilterDialogs from './GridFilterDialogs'
import {
addCss,
addJs,
controlStyleCondition,
GridExtraFilterState,
setFormEditingExtraItemValues,
setGridPanelColor,
} from './Utils'
import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent'
import { useFilters } from './useFilters'
import { useToolbar } from './useToolbar'
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
import { GridExtraFilterToolbar } from './GridExtraFilterToolbar'
import { getList } from '@/services/form.service'
interface TreeProps {
listFormCode: string
searchParams?: URLSearchParams
isSubForm?: boolean
level?: number
refreshData?: () => Promise<void>
gridDto?: GridDto
}
const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)'
const Tree = (props: TreeProps) => {
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
const { translate } = useLocalization()
const { smaller } = useResponsive()
const gridRef = useRef<TreeListDx>()
const refListFormCode = useRef('')
const [treeListDataSource, setTreeListDataSource] = useState<CustomStore<any, any>>()
const [columnData, setColumnData] = useState<GridColumnData[]>()
const [formData, setFormData] = useState<any>()
const [mode, setMode] = useState<RowMode>('view')
const [extraFilters, setExtraFilters] = useState<GridExtraFilterState[]>([])
const [gridDto, setGridDto] = useState<GridDto>()
const [isPopupFullScreen, setIsPopupFullScreen] = useState(false)
const [expandedRowKeys, setExpandedRowKeys] = useState<any[]>([])
const defaultSearchParams = useRef<string | null>(null)
useEffect(() => {
const initializeTreeList = async () => {
const response = await getList({ listFormCode })
setGridDto(response.data)
}
if (extGridDto === undefined) {
initializeTreeList()
} else {
setGridDto(extGridDto)
}
}, [listFormCode, extGridDto])
useEffect(() => {
if (!defaultSearchParams.current) {
defaultSearchParams.current = searchParams?.get('filter') ?? null
}
}, [searchParams])
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
gridDto,
listFormCode,
getSelectedRowKeys,
getSelectedRowsData,
refreshData,
getFilter,
})
const { filterToolbarData, ...filterData } = useFilters({
gridDto,
gridRef,
listFormCode,
})
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
const { getBandedColumns } = useListFormColumns({
gridDto,
listFormCode,
isSubForm,
gridRef,
})
function extractSearchParamsFields(filter: any): [string, string, any][] {
if (!Array.isArray(filter)) return []
if (filter.length === 3 && typeof filter[0] === 'string') {
return [filter as [string, string, any]]
}
return filter.flatMap((f) => extractSearchParamsFields(f))
}
async function getSelectedRowKeys() {
const tree = gridRef.current?.instance
if (!tree) {
return []
}
return await tree.getSelectedRowKeys()
}
function getSelectedRowsData() {
const tree = gridRef.current?.instance
if (!tree) {
return []
}
return tree.getSelectedRowsData()
}
function refreshData() {
gridRef.current?.instance.refresh()
}
function getFilter() {
const tree = gridRef.current?.instance
if (!tree) {
return
}
return tree.getCombinedFilter()
}
function onSelectionChanged(data: any) {
const treeOpt = gridDto?.gridOptions
const tree = gridRef.current?.instance
if (!treeOpt || !tree) {
return
}
if (treeOpt.editingOptionDto?.allowDeleting) {
try {
const opt = tree.option('toolbar')
if (opt && opt.items && Array.isArray(opt.items)) {
const deleteSelectedRecordsIndex = opt.items
.map((e: any) => e.name)
.indexOf('deleteSelectedRecords')
if (deleteSelectedRecordsIndex >= 0) {
tree.option(
`toolbar.items[${deleteSelectedRecordsIndex}].options.visible`,
data.selectedRowsData.length > 1,
)
}
}
} catch (error) {
console.error('Error updating toolbar items:', error)
}
}
if (data.selectedRowsData.length) {
setFormData(data.selectedRowsData[0])
}
}
function onCellPrepared(e: any) {
const columnFormats = gridDto?.columnFormats
if (!columnFormats) {
return
}
for (let indxCol = 0; indxCol < columnFormats.length; indxCol++) {
const colFormat = columnFormats[indxCol]
for (let indxStyl = 0; indxStyl < colFormat.columnStylingDto.length; indxStyl++) {
const colStyle = colFormat.columnStylingDto[indxStyl]
if (e.rowType == colStyle.rowType) {
if (colStyle.useRow || e.column.dataField == colFormat.fieldName) {
if (
!colStyle.conditionValue ||
controlStyleCondition(e.data, colFormat.fieldName, colStyle)
) {
if (colStyle.cssClassName) {
e.cellElement.addClass(colStyle.cssClassName)
}
if (colStyle.cssStyles) {
e.cellElement.attr('style', e.cellElement.attr('style') + ';' + colStyle.cssStyles)
}
}
}
}
}
}
}
function onInitNewRow(e: any) {
if (!gridDto?.columnFormats) {
return
}
setMode('new')
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
for (const colFormat of gridDto?.columnFormats) {
if (!colFormat.fieldName) {
continue
}
if (colFormat.defaultValue != null) {
e.data[colFormat.fieldName] = colFormat.defaultValue
}
if (extraFilters.some((f) => f.fieldName === colFormat.fieldName)) {
continue
}
if (!searchParams) {
continue
}
const rawFilter = searchParams?.get('filter')
if (rawFilter) {
const parsed = JSON.parse(rawFilter)
const filters = extractSearchParamsFields(parsed)
const hasFilter = filters.some(([field, op, val]) => field === colFormat.fieldName)
if (hasFilter) {
const fieldMatch = filters.find(([field, op, val]) => field === colFormat.fieldName)
if (fieldMatch) {
const [, , val] = fieldMatch
const dType = colFormat.dataType as any
switch (dType) {
case 'date':
case 'datetime':
e.data[colFormat.fieldName] = new Date(val)
break
case 'number':
e.data[colFormat.fieldName] = Number(val)
break
case 'boolean':
e.data[colFormat.fieldName] = val === true || val === 'true'
break
case 'object':
try {
e.data[colFormat.fieldName] = JSON.parse(val)
} catch {}
break
default:
e.data[colFormat.fieldName] = val
break
}
}
}
}
}
}
function onRowInserting(e: TreeListTypes.RowInsertingEvent) {
e.data = setFormEditingExtraItemValues(e.data)
}
function onRowUpdating(e: TreeListTypes.RowUpdatingEvent) {
if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) {
if (Object.keys(e.newData).some((a) => a.includes(':'))) {
Object.keys(e.oldData).forEach((col) => {
if (col.includes(':')) {
e.newData[col] = e.newData[col] ?? e.oldData[col]
}
})
}
e.newData = setFormEditingExtraItemValues(e.newData)
} else {
let newData = { ...e.oldData, ...e.newData }
newData = setFormEditingExtraItemValues(newData)
Object.keys(newData).forEach((key) => {
if (key.includes(':')) {
delete newData[key]
}
})
e.newData = newData
}
if (gridDto?.gridOptions.keyFieldName) {
delete e.newData[gridDto?.gridOptions.keyFieldName]
}
}
function onEditingStart(e: TreeListTypes.EditingStartEvent) {
setMode('edit')
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
const columns = e.component.option('columns') as GridColumnData[]
columns?.forEach((col) => {
if (!col.dataField?.includes(':')) {
return
}
const field = col.dataField.split(':')
if (!e.data[field[0]]) {
return
}
const json = JSON.parse(e.data[field[0]])
e.data[col.dataField] = json[field[1]]
})
}
function onDataErrorOccurred(e: TreeListTypes.DataErrorOccurredEvent) {
toast.push(
<Notification type="danger" duration={2000}>
{e.error?.message}
</Notification>,
{
placement: 'top-end',
},
)
}
const customSaveState = useCallback(
(state: any) =>
postListFormCustomization({
listFormCode: listFormCode,
customizationType: ListFormCustomizationTypeEnum.GridState,
filterName: `tree-${gridRef.current?.instance.option('stateStoring')?.storageKey ?? ''}`,
customizationData: JSON.stringify(state),
}).then(() => {
setGridPanelColor(statedGridPanelColor)
}),
[listFormCode],
)
const customLoadState = useCallback(
() =>
getListFormCustomization(
listFormCode,
ListFormCustomizationTypeEnum.GridState,
`tree-${gridRef.current?.instance.option('stateStoring')?.storageKey ?? ''}`,
).then((response: any) => {
setGridPanelColor(statedGridPanelColor)
if (response.data?.length > 0) {
return JSON.parse(response.data[0].customizationData)
}
}),
[listFormCode],
)
useEffect(() => {
if (gridRef?.current) {
gridRef.current.instance.option('columns', undefined)
gridRef.current.instance.option('dataSource', undefined)
gridRef.current.instance.state(null)
}
if (refListFormCode.current !== listFormCode) {
setExtraFilters([])
}
}, [listFormCode])
useEffect(() => {
if (!gridDto) {
return
}
const treeOpt = gridDto.gridOptions
if (treeOpt.customJsSources.length) {
for (const js of treeOpt.customJsSources) {
addJs(js)
}
}
if (treeOpt.customStyleSources.length) {
for (const css of treeOpt.customStyleSources) {
addCss(css)
}
}
if (gridDto?.gridOptions.extraFilterDto) {
setExtraFilters(
gridDto.gridOptions.extraFilterDto.map((f) => ({
fieldName: f.fieldName,
operator: f.operator,
controlType: f.controlType,
value: f.defaultValue ?? '',
})),
)
}
if (gridDto.gridOptions.editingOptionDto?.popup) {
setIsPopupFullScreen(gridDto.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
}
// Set initial expanded row keys
if (gridDto.gridOptions.treeOptionDto?.expandedRowKeys) {
setExpandedRowKeys(gridDto.gridOptions.treeOptionDto.expandedRowKeys)
} else if (gridDto.gridOptions.treeOptionDto?.autoExpandAll) {
setExpandedRowKeys([])
}
}, [gridDto])
useEffect(() => {
if (!gridDto) return
const cols = getBandedColumns()
setColumnData(cols)
const dataSource = createSelectDataSource(gridDto.gridOptions, listFormCode, searchParams, cols)
setTreeListDataSource(dataSource)
}, [gridDto, searchParams])
useEffect(() => {
const activeFilters = extraFilters.filter((f) => f.value)
let base: any = null
if (defaultSearchParams.current) {
base = JSON.parse(defaultSearchParams.current)
}
const baseTriplets = extractSearchParamsFields(base)
const extraTriplets = activeFilters.map(
(f) => [f.fieldName, f.operator, f.value] as [string, string, any],
)
const merged = [...baseTriplets, ...extraTriplets].reduce(
(acc, cur) => {
const [field, op] = cur
const idx = acc.findIndex((a) => a[0] === field && a[1] === op)
if (idx >= 0) {
acc[idx] = cur
} else {
acc.push(cur)
}
return acc
},
[] as [string, string, any][],
)
let filter: any = null
if (merged.length === 1) {
filter = merged[0]
} else if (merged.length > 1) {
filter = merged.reduce((acc, f, idx) => {
if (idx === 0) return f
return [acc, 'and', f]
}, null as any)
}
if (filter) {
searchParams?.set('filter', JSON.stringify(filter))
} else {
searchParams?.delete('filter')
}
gridRef.current?.instance.refresh()
}, [extraFilters])
useEffect(() => {
refListFormCode.current = listFormCode
if (!gridRef?.current) {
return
}
gridRef.current.instance.option('columns', columnData as any)
gridRef.current.instance.option('dataSource', treeListDataSource)
const stateStoring: IStateStoringProps = {
enabled: gridDto?.gridOptions.stateStoringDto?.enabled,
type: gridDto?.gridOptions.stateStoringDto?.type,
savingTimeout: gridDto?.gridOptions.stateStoringDto?.savingTimeout,
storageKey: gridDto?.gridOptions.stateStoringDto?.storageKey,
}
if (
gridDto?.gridOptions.stateStoringDto?.enabled &&
gridDto?.gridOptions.stateStoringDto?.type === 'custom'
) {
stateStoring.customSave = customSaveState
stateStoring.customLoad = customLoadState
}
gridRef.current.instance.option('stateStoring', stateStoring)
}, [columnData])
return (
<>
<WidgetGroup widgetGroups={gridDto?.widgets ?? []} />
<Container className={DX_CLASSNAMES}>
{!isSubForm && (
<Helmet
titleTemplate="%s | Erp Platform"
title={translate('::' + gridDto?.gridOptions.title)}
defaultTitle="Erp Platform"
></Helmet>
)}
{gridDto && columnData && (
<div className="p-1 bg-white dark:bg-neutral-800 dark:border-neutral-700 ">
<TreeListDx
ref={gridRef as any}
id={'TreeList-' + listFormCode}
height={gridDto.gridOptions.height || 'calc(100vh - 150px)'}
width={gridDto.gridOptions.width || '100%'}
dataStructure="plain"
keyExpr={gridDto.gridOptions.keyFieldName}
parentIdExpr={gridDto.gridOptions.treeOptionDto?.parentIdExpr || 'parentId'}
hasItemsExpr={gridDto.gridOptions.treeOptionDto?.hasItemsExpr}
rootValue={
gridDto.gridOptions.treeOptionDto?.rootValue === '' ||
gridDto.gridOptions.treeOptionDto?.rootValue === undefined
? null
: gridDto.gridOptions.treeOptionDto?.rootValue
}
{...(!gridDto.gridOptions.treeOptionDto?.autoExpandAll && {
expandedRowKeys: expandedRowKeys,
onRowExpanding: (e: any) => {
setExpandedRowKeys((prev) => [...prev, e.key])
},
onRowCollapsing: (e: any) => {
setExpandedRowKeys((prev) => prev.filter((k) => k !== e.key))
},
})}
autoExpandAll={gridDto.gridOptions.treeOptionDto?.autoExpandAll || false}
allowColumnResizing={gridDto.gridOptions.columnOptionDto?.allowColumnResizing}
allowColumnReordering={gridDto.gridOptions.columnOptionDto?.allowColumnReordering}
showBorders={gridDto.gridOptions.columnOptionDto?.showBorders}
showRowLines={gridDto.gridOptions.columnOptionDto?.showRowLines}
showColumnLines={gridDto.gridOptions.columnOptionDto?.showColumnLines}
columnResizingMode={gridDto.gridOptions.columnOptionDto?.columnResizingMode}
columnAutoWidth={gridDto.gridOptions.columnOptionDto?.columnAutoWidth}
rtlEnabled={gridDto.gridOptions.columnOptionDto?.rtlEnabled}
rowAlternationEnabled={gridDto.gridOptions.columnOptionDto?.rowAlternationEnabled}
hoverStateEnabled={gridDto.gridOptions.columnOptionDto?.hoverStateEnabled}
columnHidingEnabled={gridDto.gridOptions.columnOptionDto?.columnHidingEnabled}
focusedRowEnabled={gridDto.gridOptions.columnOptionDto?.focusedRowEnabled}
showColumnHeaders={gridDto.gridOptions.columnOptionDto?.showColumnHeaders}
filterSyncEnabled={true}
onSelectionChanged={onSelectionChanged}
onInitNewRow={onInitNewRow}
onCellPrepared={onCellPrepared}
onRowInserting={onRowInserting}
onRowUpdating={onRowUpdating}
onEditingStart={onEditingStart}
onDataErrorOccurred={onDataErrorOccurred}
onEditCanceled={() => {
setMode('view')
setIsPopupFullScreen(false)
}}
onSaved={() => {
setMode('view')
setIsPopupFullScreen(false)
}}
onRowInserted={() => {
props.refreshData?.()
}}
onRowUpdated={() => {
props.refreshData?.()
}}
onRowRemoved={() => {
props.refreshData?.()
}}
onContentReady={(e) => {
// Restore expanded keys after data refresh (only if autoExpandAll is false)
if (
!gridDto.gridOptions.treeOptionDto?.autoExpandAll &&
expandedRowKeys.length > 0
) {
e.component.option('expandedRowKeys', expandedRowKeys)
}
}}
>
<RemoteOperations filtering={true} sorting={true} grouping={false} />
<Editing
refreshMode={gridDto.gridOptions.editingOptionDto?.refreshMode}
mode={smaller.md ? 'form' : gridDto.gridOptions.editingOptionDto?.mode}
allowDeleting={gridDto.gridOptions.editingOptionDto?.allowDeleting}
allowUpdating={gridDto.gridOptions.editingOptionDto?.allowUpdating}
allowAdding={gridDto.gridOptions.editingOptionDto?.allowAdding}
useIcons={gridDto.gridOptions.editingOptionDto?.useIcons}
popup={{
title:
(mode === 'new' ? '✚ ' : '🖊️ ') +
translate('::' + gridDto.gridOptions.editingOptionDto?.popup?.title),
showTitle: gridDto.gridOptions.editingOptionDto?.popup?.showTitle,
hideOnOutsideClick:
gridDto.gridOptions.editingOptionDto?.popup?.hideOnOutsideClick,
width: gridDto.gridOptions.editingOptionDto?.popup?.width,
height: gridDto.gridOptions.editingOptionDto?.popup?.height,
resizeEnabled: gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled,
fullScreen: isPopupFullScreen,
toolbarItems: [
{
widget: 'dxButton',
toolbar: 'top',
location: 'after',
options: {
icon: isPopupFullScreen ? 'collapse' : 'fullscreen',
hint: isPopupFullScreen
? translate('::Normal Boyut')
: translate('::Tam Ekran'),
stylingMode: 'text',
onClick: () => setIsPopupFullScreen(!isPopupFullScreen),
},
},
],
}}
/>
<Template name={'cellEditTagBox'} render={TagBoxEditorComponent} />
<Template name={'cellEditGridBox'} render={GridBoxEditorComponent} />
<Template name="extraFilters">
<GridExtraFilterToolbar
filters={gridDto?.gridOptions.extraFilterDto ?? []}
extraFilters={extraFilters}
setExtraFilters={setExtraFilters}
/>
</Template>
<Toolbar
visible={(toolbarData?.length ?? 0) > 0 || (filterToolbarData?.length ?? 0) > 0}
>
{toolbarData?.map((item) => (
<Item key={item.name} {...item} />
))}
{filterToolbarData?.map((item) => (
<Item key={item.name} {...item} />
))}
{gridDto?.gridOptions.extraFilterDto?.length ? (
<Item location="before" template="extraFilters" cssClass="no-default" />
) : null}
</Toolbar>
<Sorting mode={gridDto.gridOptions?.sortMode}></Sorting>
<FilterRow
visible={gridDto.gridOptions.filterRowDto?.visible}
applyFilter={gridDto.gridOptions.filterRowDto?.applyFilter}
></FilterRow>
<FilterPanel visible={gridDto.gridOptions.filterPanelDto.visible}></FilterPanel>
<HeaderFilter visible={gridDto.gridOptions.headerFilterDto.visible}></HeaderFilter>
<SearchPanel
visible={gridDto.gridOptions.searchPanelDto.visible}
width={gridDto.gridOptions.searchPanelDto.width}
></SearchPanel>
<Selection
mode={gridDto.gridOptions.selectionDto?.mode}
recursive={gridDto.gridOptions.treeOptionDto?.recursiveSelection || false}
></Selection>
<Pager
visible={gridDto.gridOptions.pagerOptionDto?.visible}
allowedPageSizes={gridDto.gridOptions.pagerOptionDto?.allowedPageSizes
?.split(',')
.map((a: any) => +a)}
showPageSizeSelector={gridDto.gridOptions.pagerOptionDto?.showPageSizeSelector}
showInfo={gridDto.gridOptions.pagerOptionDto?.showInfo}
showNavigationButtons={gridDto.gridOptions.pagerOptionDto?.showNavigationButtons}
infoText={gridDto.gridOptions.pagerOptionDto?.infoText}
displayMode={gridDto.gridOptions.pagerOptionDto?.displayMode}
></Pager>
<ColumnChooser
enabled={gridDto.gridOptions.columnOptionDto?.columnChooserEnabled}
mode={gridDto.gridOptions.columnOptionDto?.columnChooserMode}
></ColumnChooser>
<ColumnFixing
enabled={gridDto.gridOptions.columnOptionDto?.columnFixingEnabled}
></ColumnFixing>
<Scrolling mode={gridDto.gridOptions.pagerOptionDto?.scrollingMode}></Scrolling>
<LoadPanel
enabled={gridDto.gridOptions.pagerOptionDto?.loadPanelEnabled}
text={gridDto.gridOptions.pagerOptionDto?.loadPanelText}
></LoadPanel>
</TreeListDx>
</div>
)}
<Dialog
isOpen={toolbarModalData?.open || false}
onClose={() => setToolbarModalData(undefined)}
onRequestClose={() => setToolbarModalData(undefined)}
>
{toolbarModalData?.content}
</Dialog>
<GridFilterDialogs gridRef={gridRef as any} listFormCode={listFormCode} {...filterData} />
</Container>
</>
)
}
export default Tree

View file

@ -97,11 +97,18 @@ export function pivotFieldConvertDataType(fieldDbType?: DataType): PivotGridData
}
export function setGridPanelColor(color: any) {
const pnl = document.getElementsByClassName(
const pnl1 = document.getElementsByClassName(
'dx-datagrid-header-panel',
) as HTMLCollectionOf<HTMLElement>
if (pnl?.length) {
pnl[0].style.backgroundColor = color
if (pnl1?.length) {
pnl1[0].style.backgroundColor = color
}
const pnl2 = document.getElementsByClassName(
'dx-treelist-header-panel',
) as HTMLCollectionOf<HTMLElement>
if (pnl2?.length) {
pnl2[0].style.backgroundColor = color
}
}

View file

@ -8,9 +8,11 @@ import { getListFormCustomization } from '@/services/list-form-customization.ser
import { useLocalization } from '@/utils/hooks/useLocalization'
import DataGrid from 'devextreme-react/data-grid'
import PivotGrid from 'devextreme-react/pivot-grid'
import TreeList from 'devextreme-react/tree-list'
import { ToolbarItem } from 'devextreme/ui/data_grid_types'
import dxDataGrid from 'devextreme/ui/data_grid'
import dxPivotGrid from 'devextreme/ui/pivot_grid'
import dxTreeList from 'devextreme/ui/tree_list'
import { Dispatch, MutableRefObject, SetStateAction, useEffect, useState } from 'react'
import { setGridPanelColor } from './Utils'
import { usePermission } from '@/utils/hooks/usePermission'
@ -22,20 +24,29 @@ export interface ISelectBoxData {
label?: string
}
type GridInstance = dxDataGrid | dxPivotGrid | dxTreeList
// Grid tipini kontrol eden yardımcı fonksiyonlar
const isDataGrid = (grid: dxDataGrid | dxPivotGrid): grid is dxDataGrid => {
const isDataGrid = (grid: GridInstance): grid is dxDataGrid => {
return 'clearFilter' in grid
}
const getToolbarItems = (grid: dxDataGrid | dxPivotGrid): any[] => {
const isTreeList = (grid: GridInstance): grid is dxTreeList => {
return 'getRootNode' in grid
}
const getToolbarItems = (grid: GridInstance): any[] => {
if (isDataGrid(grid)) {
const toolbarOptions = grid.option('toolbar')
return toolbarOptions?.items || []
} else if (isTreeList(grid)) {
const toolbarOptions = grid.option('toolbar')
return toolbarOptions?.items || []
}
return []
}
const setToolbarItemValue = (grid: dxDataGrid | dxPivotGrid, itemName: string, value: any) => {
const setToolbarItemValue = (grid: GridInstance, itemName: string, value: any) => {
if (isDataGrid(grid)) {
const toolbarOptions = grid.option('toolbar')
if (toolbarOptions?.items) {
@ -48,10 +59,22 @@ const setToolbarItemValue = (grid: dxDataGrid | dxPivotGrid, itemName: string, v
}
}
}
} else if (isTreeList(grid)) {
const toolbarOptions = grid.option('toolbar')
if (toolbarOptions?.items) {
const index = toolbarOptions.items.findIndex((item: any) => item.name === itemName)
if (index > -1 && toolbarOptions.items[index]) {
const item = toolbarOptions.items[index] as any
if (item.options) {
item.options.value = value
grid.option('toolbar', toolbarOptions)
}
}
}
}
}
const setToolbarItemItems = (grid: dxDataGrid | dxPivotGrid, itemName: string, items: any[]) => {
const setToolbarItemItems = (grid: GridInstance, itemName: string, items: any[]) => {
if (isDataGrid(grid)) {
const toolbarOptions = grid.option('toolbar')
if (toolbarOptions?.items) {
@ -64,30 +87,50 @@ const setToolbarItemItems = (grid: dxDataGrid | dxPivotGrid, itemName: string, i
}
}
}
} else if (isTreeList(grid)) {
const toolbarOptions = grid.option('toolbar')
if (toolbarOptions?.items) {
const index = toolbarOptions.items.findIndex((item: any) => item.name === itemName)
if (index > -1 && toolbarOptions.items[index]) {
const item = toolbarOptions.items[index] as any
if (item.options) {
item.options.items = items
grid.option('toolbar', toolbarOptions)
}
}
}
}
}
const clearGridFilter = (grid: dxDataGrid | dxPivotGrid) => {
const clearGridFilter = (grid: GridInstance) => {
if (isDataGrid(grid)) {
grid.clearFilter()
} else if (isTreeList(grid)) {
grid.clearFilter()
}
}
const setFilterPanelVisible = (grid: dxDataGrid | dxPivotGrid, visible: boolean) => {
const setFilterPanelVisible = (grid: GridInstance, visible: boolean) => {
if (isDataGrid(grid)) {
grid.option('filterPanel', { visible })
} else if (isTreeList(grid)) {
grid.option('filterPanel', { visible })
}
}
const setFilterValue = (grid: dxDataGrid | dxPivotGrid, value: any) => {
const setFilterValue = (grid: GridInstance, value: any) => {
if (isDataGrid(grid)) {
grid.option('filterValue', value)
} else if (isTreeList(grid)) {
grid.option('filterValue', value)
}
}
const resetGridState = (grid: dxDataGrid | dxPivotGrid) => {
const resetGridState = (grid: GridInstance) => {
if (isDataGrid(grid)) {
grid.state(null)
} else if (isTreeList(grid)) {
grid.state(null)
}
}
@ -100,6 +143,7 @@ const useFilters = ({
gridRef:
| MutableRefObject<DataGrid<any, any> | undefined>
| MutableRefObject<PivotGrid | undefined>
| MutableRefObject<TreeList<any, any> | undefined>
listFormCode: string
}): {
filterToolbarData: ToolbarItem[]