extraFilters, FiltreBar, Widget ve useListFormCustomDataSource

This commit is contained in:
Sedat ÖZTÜRK 2025-09-19 16:30:19 +03:00
parent 656d162617
commit be34af298e
8 changed files with 177 additions and 209 deletions

View file

@ -101,6 +101,9 @@ public class ListFormFieldManager : PlatformDomainService, IListFormFieldManager
fields.AddRange(formFieldsRole.Where(a => !fields.Any(b => b.FieldName == a.FieldName))); fields.AddRange(formFieldsRole.Where(a => !fields.Any(b => b.FieldName == a.FieldName)));
fields.AddRange(formFieldsDefault.Where(a => !fields.Any(b => b.FieldName == a.FieldName))); fields.AddRange(formFieldsDefault.Where(a => !fields.Any(b => b.FieldName == a.FieldName)));
if (fields.Count == 0)
return null;
var field = fields.FirstOrDefault(); var field = fields.FirstOrDefault();
return await SetPermissionsAsync(field, listFormCode); return await SetPermissionsAsync(field, listFormCode);

View file

@ -82,7 +82,7 @@ define(['./workbox-a959eb95'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812" "revision": "3ca0b8505b4bec776b69afdba2768812"
}, { }, {
"url": "/index.html", "url": "/index.html",
"revision": "0.el21c3n5m38" "revision": "0.7curt4sl6"
}], {}); }], {});
workbox.cleanupOutdatedCaches(); workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("/index.html"), { workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("/index.html"), {

View file

@ -1,5 +1,10 @@
@import './assets/styles/app.css'; @import './assets/styles/app.css';
.dx-toolbar .dx-toolbar-item.no-default {
all: unset; /* tüm default style'ları sıfırla */
display: block; /* tekrar görünür yap */
}
/* React Modal prevent bg scroll */ /* React Modal prevent bg scroll */
.dialog-open { .dialog-open {
overflow: hidden; overflow: hidden;

View file

@ -23,9 +23,9 @@ const useListFormCustomDataSource = ({
listFormCode: string, listFormCode: string,
searchParams?: URLSearchParams, searchParams?: URLSearchParams,
cols?: GridColumnData[], cols?: GridColumnData[],
setWidgetGroups?: (widgetGroups: WidgetGroupDto[]) => void, // setWidgetGroups?: (widgetGroups: WidgetGroupDto[]) => void,
) => { ) => {
return new CustomStore({ const store: any = new CustomStore({
key: gridOptions.keyFieldName, key: gridOptions.keyFieldName,
useDefaultSearch: true, useDefaultSearch: true,
load: async (loadOptions) => { load: async (loadOptions) => {
@ -92,8 +92,6 @@ const useListFormCustomDataSource = ({
//parameters.filter = JSON.stringify(parameters.filter) //parameters.filter = JSON.stringify(parameters.filter)
const response = await dynamicFetch('list-form-select/select', 'GET', parameters) const response = await dynamicFetch('list-form-select/select', 'GET', parameters)
if (setWidgetGroups) setWidgetGroups(response.data.widgets ?? [])
// Column format multiValue ise, gelen stringi array yapmaliyiz // Column format multiValue ise, gelen stringi array yapmaliyiz
if (columns) { if (columns) {
columns.forEach((col: any) => { columns.forEach((col: any) => {
@ -141,12 +139,13 @@ const useListFormCustomDataSource = ({
: 'transparent', : 'transparent',
) )
//Widgets bilgisini store'un custom propertysinde tutuyoruz ki,
store._widgets = response.data.widgets ?? []
const retValue = { const retValue = {
data: response.data.data, data: response.data.data,
totalCount: response.data.totalCount, totalCount: response.data.totalCount,
summary: response.data.summary, summary: response.data.summary,
groupCount: response.data.groupCount, groupCount: response.data.groupCount,
widgets: response.data.widgets,
} }
return retValue return retValue
} catch (error: any) { } catch (error: any) {
@ -185,10 +184,10 @@ const useListFormCustomDataSource = ({
} }
if (combinedFilter && combinedFilter.length > 0) { if (combinedFilter && combinedFilter.length > 0) {
parameters.filter = JSON.stringify(combinedFilter) parameters.filter = JSON.stringify(combinedFilter)
} else { } else {
delete parameters.filter // hiç göndermesin delete parameters.filter // hiç göndermesin
} }
try { try {
const response = await dynamicFetch('list-form-select/select', 'GET', parameters) const response = await dynamicFetch('list-form-select/select', 'GET', parameters)
@ -206,29 +205,29 @@ const useListFormCustomDataSource = ({
return null return null
} }
}, },
// byKey: async (key) => { byKey: async (key) => {
// const parameters = getLoadOptions( const parameters = getLoadOptions(
// { key }, { key },
// { listFormCode, filter: '', createDeleteQuery: '' }, { listFormCode, filter: '', createDeleteQuery: '' },
// ) )
// parameters.filter = JSON.stringify(parameters.filter) parameters.filter = JSON.stringify(parameters.filter)
// try { try {
// const response = await dynamicFetch('list-form-select/select', 'GET', parameters) const response = await dynamicFetch('list-form-select/select', 'GET', parameters)
// return response.data.data[0] return response.data.data[0]
// } catch (error: any) { } catch (error: any) {
// // toast.push( // toast.push(
// // <Notification type="danger" duration={2000}> // <Notification type="danger" duration={2000}>
// // ByKey error // ByKey error
// // {error.toString()} // {error.toString()}
// // </Notification>, // </Notification>,
// // { // {
// // placement: 'top-end', // placement: 'top-end',
// // }, // },
// // ) // )
// return null return null
// } }
// }, },
remove: function (key) { remove: function (key) {
if (!gridOptions.deleteServiceAddress) { if (!gridOptions.deleteServiceAddress) {
return return
@ -265,6 +264,8 @@ const useListFormCustomDataSource = ({
console.log(error.message) console.log(error.message)
}, },
}) })
return store
}, },
[], [],
) )

View file

@ -188,12 +188,12 @@ const FormEdit = () => {
<TabNav value="columns">{translate('::ListForms.ListFormEdit.TabColumnsPivots')}</TabNav> <TabNav value="columns">{translate('::ListForms.ListFormEdit.TabColumnsPivots')}</TabNav>
<TabNav value="pager">{translate('::ListForms.ListFormEdit.TabPaging')}</TabNav> <TabNav value="pager">{translate('::ListForms.ListFormEdit.TabPaging')}</TabNav>
<TabNav value="state">{translate('::ListForms.ListFormEdit.TabState')}</TabNav> <TabNav value="state">{translate('::ListForms.ListFormEdit.TabState')}</TabNav>
<TabNav value="subForms">{translate('::ListForms.ListFormEdit.TabSubForms')}</TabNav>
<TabNav value="widget">{translate('::ListForms.ListFormEdit.TabWidgets')}</TabNav>
<TabNav value="fields">{translate('::ListForms.ListFormEdit.TabFields')}</TabNav> <TabNav value="fields">{translate('::ListForms.ListFormEdit.TabFields')}</TabNav>
<TabNav value="customization"> <TabNav value="customization">
{translate('::ListForms.ListFormEdit.TabCustomization')} {translate('::ListForms.ListFormEdit.TabCustomization')}
</TabNav> </TabNav>
<TabNav value="subForms">{translate('::ListForms.ListFormEdit.TabSubForms')}</TabNav>
<TabNav value="widget">{translate('::ListForms.ListFormEdit.TabWidgets')}</TabNav>
<TabNav value="extrafilter">{translate('::ListForms.ListFormEdit.ExtraFilters')}</TabNav> <TabNav value="extrafilter">{translate('::ListForms.ListFormEdit.ExtraFilters')}</TabNav>
</TabList> </TabList>
<TabContent value="details"> <TabContent value="details">

View file

@ -3,7 +3,7 @@ import TabContent from '@/components/ui/Tabs/TabContent'
import TabList from '@/components/ui/Tabs/TabList' import TabList from '@/components/ui/Tabs/TabList'
import TabNav from '@/components/ui/Tabs/TabNav' import TabNav from '@/components/ui/Tabs/TabNav'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { FaChartBar, FaList } from 'react-icons/fa'; import { FaChartBar, FaList } from 'react-icons/fa'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import Grid from '../list/Grid' import Grid from '../list/Grid'
import Chart from '../chart/Chart' import Chart from '../chart/Chart'
@ -41,18 +41,22 @@ const SubForms = (props: {
for (const subForm of gridDto.gridOptions.subFormsDto) { for (const subForm of gridDto.gridOptions.subFormsDto) {
if (formData && subForm.relation?.length) { if (formData && subForm.relation?.length) {
subForm.searchParams = new URLSearchParams( // sadece ilk eşleşmeyi al
subForm.relation.reduce( const rel = subForm.relation.find((a) => formData?.[a.parentFieldName])
(acc, a) => {
if (formData?.[a.parentFieldName]) { if (rel) {
acc[a.childFieldName] = formData[a.parentFieldName] const filter: [string, string, string] = [
} rel.childFieldName,
return acc '=',
}, formData[rel.parentFieldName],
{} as Record<string, string>, ]
),
) subForm.searchParams = new URLSearchParams({
subForm.id = formData[subForm.relation[0].parentFieldName] filter: JSON.stringify(filter),
})
subForm.id = formData[rel.parentFieldName]
}
} }
} }

View file

@ -103,6 +103,15 @@ const Grid = (props: GridProps) => {
import('devextreme/pdf_exporter') import('devextreme/pdf_exporter')
} }
const defaultSearchParamsFilter = useRef<string | null>(null)
useEffect(() => {
// sadece 1 kere açılışta al
if (!defaultSearchParamsFilter.current) {
defaultSearchParamsFilter.current = searchParams?.get('filter') ?? null
}
}, [searchParams])
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({ const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
gridDto, gridDto,
listFormCode, listFormCode,
@ -242,33 +251,37 @@ const Grid = (props: GridProps) => {
if (!searchParams) { if (!searchParams) {
continue continue
} }
if (searchParams.has(colFormat.fieldName)) {
const dType = colFormat.dataType as DataType const filterValue = searchParams.get('filter')
switch (dType) {
case 'date': if (filterValue) {
case 'datetime': if (filterValue.includes(colFormat.fieldName)) {
e.data[colFormat.fieldName] = new Date(searchParams.get(colFormat.fieldName)!) const parsed = JSON.parse(filterValue) as [string, string, any]
break const [field, op, val] = parsed
case 'number':
e.data[colFormat.fieldName] = Number(searchParams.get(colFormat.fieldName)) if (field === colFormat.fieldName) {
break const dType = colFormat.dataType as DataType
case 'boolean': switch (dType) {
if (searchParams.get(colFormat.fieldName) === 'true') { case 'date':
e.data[colFormat.fieldName] = true case 'datetime':
} else if (searchParams.get(colFormat.fieldName) === 'false') { e.data[colFormat.fieldName] = new Date(val)
e.data[colFormat.fieldName] = false 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
} }
break }
case 'object':
try {
e.data[colFormat.fieldName] = JSON.parse(
searchParams.get(colFormat.fieldName) as string,
)
} catch (error) {}
break
default:
e.data[colFormat.fieldName] = searchParams.get(colFormat.fieldName)
break
} }
} }
} }
@ -366,6 +379,10 @@ const Grid = (props: GridProps) => {
gridRef.current.instance.option('remoteOperations', false) gridRef.current.instance.option('remoteOperations', false)
gridRef.current.instance.option('dataSource', undefined) gridRef.current.instance.option('dataSource', undefined)
gridRef.current.instance.state(null) gridRef.current.instance.state(null)
//bir önceki gridin filtreleri kalmasın
setExtraFilters([])
setWidgetGroups([])
} }
const initializeGrid = async () => { const initializeGrid = async () => {
@ -395,6 +412,17 @@ const Grid = (props: GridProps) => {
addCss(css) addCss(css)
} }
} }
if (gridDto?.gridOptions.extraFilterDto) {
setExtraFilters(
gridDto.gridOptions.extraFilterDto.map((f) => ({
fieldName: f.fieldName,
operator: f.operator,
controlType: f.controlType,
value: f.defaultValue ?? '',
})),
)
}
}, [gridDto]) }, [gridDto])
useEffect(() => { useEffect(() => {
@ -403,48 +431,42 @@ const Grid = (props: GridProps) => {
const cols = getBandedColumns() const cols = getBandedColumns()
setColumnData(cols) setColumnData(cols)
// 1. extraFilters'tan gelenleri ekle const dataSource = createSelectDataSource(gridDto.gridOptions, listFormCode, searchParams, cols)
const filterArray: any[] = []
extraFilters.forEach((f) => {
if (f.value) {
if (filterArray.length > 0) filterArray.push('and')
filterArray.push([f.fieldName, f.operator, f.value])
}
})
// 2. searchParams içindeki kolonları filtreye çevir
const params = new URLSearchParams(searchParams)
if (cols?.length) {
cols.forEach((col) => {
if (!col.dataField) return
const sValue = params.get(col.dataField)
if (sValue) {
if (filterArray.length > 0) filterArray.push('and')
filterArray.push([col.dataField, '=', sValue])
// orijinal paramı kaldır
params.delete(col.dataField)
}
})
}
// 3. oluşan filtreleri `filter` paramına yaz
if (filterArray.length > 0) {
params.set('filter', JSON.stringify(filterArray))
} else {
params.delete('filter')
}
// 4. datasource oluştur
const dataSource = createSelectDataSource(
gridDto.gridOptions,
listFormCode,
params,
cols,
setWidgetGroups,
)
setGridDataSource(dataSource) setGridDataSource(dataSource)
}, [gridDto, searchParams, extraFilters]) }, [gridDto, searchParams])
useEffect(() => {
const activeFilters = extraFilters.filter((f) => f.value)
let filter: any = null
if (activeFilters.length === 1) {
filter = [activeFilters[0].fieldName, activeFilters[0].operator, activeFilters[0].value]
} else if (activeFilters.length > 1) {
filter = activeFilters.reduce((acc, f, idx) => {
if (idx === 0) return [f.fieldName, f.operator, f.value]
return [acc, 'and', [f.fieldName, f.operator, f.value]]
}, null as any)
}
if (filter) {
// hem defaultFilter hem extraFilter birleştir
if (defaultSearchParamsFilter.current) {
const base = JSON.parse(defaultSearchParamsFilter.current)
filter = [base, 'and', filter]
}
searchParams?.set('filter', JSON.stringify(filter))
} else {
// sadece default filter kalsın
if (defaultSearchParamsFilter.current) {
searchParams?.set('filter', defaultSearchParamsFilter.current)
} else {
searchParams?.delete('filter')
}
}
gridRef.current?.instance.refresh()
}, [extraFilters])
useEffect(() => { useEffect(() => {
refListFormCode.current = listFormCode refListFormCode.current = listFormCode
@ -540,85 +562,10 @@ const Grid = (props: GridProps) => {
} }
} }
useEffect(() => {
if (gridDto?.gridOptions.extraFilterDto.length === 0) return
if (gridDto?.gridOptions.extraFilterDto) {
setExtraFilters(
gridDto.gridOptions.extraFilterDto.map((f) => ({
fieldName: f.fieldName,
operator: f.operator,
controlType: f.controlType,
value: f.defaultValue ?? '',
})),
)
}
}, [gridDto])
useEffect(() => {
if (!searchParams) return
if (extraFilters.length === 0) return
// aktif filtreleri al
const activeFilters = extraFilters.filter((f) => f.value)
let filter: any = null
if (activeFilters.length === 1) {
// tek filtre -> düz array
const f = activeFilters[0]
filter = [f.fieldName, f.operator, f.value]
} else if (activeFilters.length > 1) {
// birden fazla filtre -> and ile bağla
filter = activeFilters.reduce((acc, f, idx) => {
if (idx === 0) return [f.fieldName, f.operator, f.value]
return [acc, 'and', [f.fieldName, f.operator, f.value]]
}, null as any)
}
if (filter) {
searchParams.set('filter', JSON.stringify(filter))
} else {
searchParams.delete('filter')
}
gridRef.current?.instance.refresh()
}, [extraFilters, searchParams])
return ( return (
<> <>
<WidgetGroup widgetGroups={widgetGroups} /> <WidgetGroup widgetGroups={widgetGroups} />
{/* {gridDto?.gridOptions.extraFilterDto && (
<div className="flex gap-4 pl-2 pb-1">
{gridDto?.gridOptions.extraFilterDto?.map((fs) => {
const current = extraFilters.find((f) => f.fieldName === fs.fieldName)
return (
<div key={fs.fieldName}>
<label className="mr-2">{fs.caption}</label>
<select
value={current?.value ?? ''}
onChange={(e) =>
setExtraFilters((prev) =>
prev.map((f) =>
f.fieldName === fs.fieldName ? { ...f, value: e.target.value } : f,
),
)
}
>
<option value="">Seçiniz</option>
{fs.items.map((item) => (
<option key={item.value} value={item.value}>
{item.text}
</option>
))}
</select>
</div>
)
})}
</div>
)} */}
<Container className={DX_CLASSNAMES}> <Container className={DX_CLASSNAMES}>
{!isSubForm && ( {!isSubForm && (
<Helmet <Helmet
@ -652,6 +599,15 @@ const Grid = (props: GridProps) => {
focusedRowEnabled={gridDto.gridOptions.columnOptionDto?.focusedRowEnabled} focusedRowEnabled={gridDto.gridOptions.columnOptionDto?.focusedRowEnabled}
showColumnHeaders={gridDto.gridOptions.columnOptionDto?.showColumnHeaders} showColumnHeaders={gridDto.gridOptions.columnOptionDto?.showColumnHeaders}
filterSyncEnabled={true} filterSyncEnabled={true}
onContentReady={(e) => {
const ds = e.component.getDataSource()
if (!ds) return
const store = ds.store() as any
if (store._widgets) {
setWidgetGroups(store._widgets)
}
}}
onSelectionChanged={onSelectionChanged} onSelectionChanged={onSelectionChanged}
onInitNewRow={onInitNewRow} onInitNewRow={onInitNewRow}
onCellPrepared={onCellPrepared} onCellPrepared={onCellPrepared}
@ -726,10 +682,19 @@ const Grid = (props: GridProps) => {
editorOptions = i.editorOptions && JSON.parse(i.editorOptions) editorOptions = i.editorOptions && JSON.parse(i.editorOptions)
// Eğer default value varsa, bu editörü readonly yapıyoruz // Eğer default value varsa, bu editörü readonly yapıyoruz
if (searchParams?.has(i.dataField)) { const rawFilter = searchParams?.get('filter')
editorOptions = { if (rawFilter) {
...editorOptions, const parsed = JSON.parse(rawFilter) as [
readOnly: true, string,
string,
any,
]
const [field, op, val] = parsed
if (field === i.dataField) {
editorOptions = {
...editorOptions,
readOnly: true,
}
} }
} }
} catch {} } catch {}
@ -826,7 +791,7 @@ const Grid = (props: GridProps) => {
))} ))}
{/* burada özel filtre alanını Template ile bağla */} {/* burada özel filtre alanını Template ile bağla */}
{gridDto?.gridOptions.extraFilterDto?.length ? ( {gridDto?.gridOptions.extraFilterDto?.length ? (
<Item location="before" template="extraFilters" /> <Item location="before" template="extraFilters" cssClass="no-default" />
) : null} ) : null}
</Toolbar> </Toolbar>
<Sorting mode={gridDto.gridOptions?.sortMode}></Sorting> <Sorting mode={gridDto.gridOptions?.sortMode}></Sorting>
@ -900,21 +865,11 @@ const Grid = (props: GridProps) => {
/> />
))} ))}
</Summary> </Summary>
{/* <Column
dataField="Quantity"
width={160}
alignment="right"
//format={'0.000'}
//format="fixedPoint"
editorOptions={{ type: 'fixedPoint', pricision: 1 }}
>
<Format type="fixedPoint" precision={2} />
</Column> */}
</DataGrid> </DataGrid>
{gridDto?.gridOptions?.subFormsDto?.length > 0 && ( {gridDto?.gridOptions?.subFormsDto?.length > 0 && (
<> <>
<hr className="my-4" /> <hr className="my-2" />
<SubForms gridDto={gridDto!} formData={formData} level={level ?? 0} /> <SubForms gridDto={gridDto!} formData={formData} level={level ?? 0} />
</> </>
)} )}

View file

@ -8,7 +8,7 @@ import { ToolbarItem } from 'devextreme/ui/data_grid_types'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useDialogContext } from '../shared/DialogContext' import { useDialogContext } from '../shared/DialogContext'
import { usePWA } from '@/utils/hooks/usePWA' import { usePWA } from '@/utils/hooks/usePWA'
import { GridExtraFilterState } from './GridColumnData' import { GridExtraFilterState } from './Utils'
type ToolbarModalData = { type ToolbarModalData = {
open: boolean open: boolean