Grid üzerinden extra filter özelliği eklendi.

This commit is contained in:
Sedat ÖZTÜRK 2025-09-17 17:13:48 +03:00
parent ca28fe8b5c
commit defbf93165
19 changed files with 564 additions and 272 deletions

View file

@ -0,0 +1,16 @@
namespace Kurs.Platform.ListForms;
public class ExtraFilterDto
{
public string FieldName { get; set; }
public string Caption { get; set; }
public string Operator { get; set; }
public string DefaultValue { get; set; }
public ExtraFilterItemsDto[] Items { get; set; }
}
public class ExtraFilterItemsDto
{
public string Value { get; set; }
public string Text { get; set; }
}

View file

@ -0,0 +1,6 @@
namespace Kurs.Platform.ListForms;
public class ExtraFilterEditDto : ExtraFilterDto
{
public string SqlQuery { get; set; }
}

View file

@ -291,4 +291,18 @@ public class GridOptionsDto : AuditedEntityDto<Guid>
}
set { SubFormsJson = JsonSerializer.Serialize(value); }
}
[JsonIgnore]
public string ExtraFilterJson { get; set; } // Cagrilacak Extra Filters
public ExtraFilterDto[] ExtraFilterDto
{
get
{
if (!string.IsNullOrEmpty(ExtraFilterJson))
return JsonSerializer.Deserialize<ExtraFilterDto[]>(ExtraFilterJson);
return [];
}
set { ExtraFilterJson = JsonSerializer.Serialize(value); }
}
}

View file

@ -91,6 +91,7 @@ public class GridOptionsEditDto : GridOptionsDto
set { FormFieldsDefaultValueJson = JsonSerializer.Serialize(value); }
}
//bu sadece ListFormSelectAppService/GetSelectAsync icerisinde kullanilir
[JsonIgnore]
public string WidgetsJson { get; set; } // Cagrilacak Widgetlar
public List<WidgetEditDto> WidgetsDto
@ -104,4 +105,17 @@ public class GridOptionsEditDto : GridOptionsDto
}
set { WidgetsJson = JsonSerializer.Serialize(value); }
}
[JsonIgnore]
public List<ExtraFilterEditDto> ExtraFilterEditDto
{
get
{
if (!string.IsNullOrEmpty(ExtraFilterJson))
return JsonSerializer.Deserialize<List<ExtraFilterEditDto>>(ExtraFilterJson);
return [];
}
set { ExtraFilterJson = JsonSerializer.Serialize(value); }
}
}

View file

@ -25,6 +25,9 @@ public class ListFormAutoMapperProfile : Profile
CreateMap<SubFormDto, SubForm>().ReverseMap();
CreateMap<SubFormRelationDto, SubFormRelation>().ReverseMap();
CreateMap<WidgetEditDto, Widget>().ReverseMap();
CreateMap<ExtraFilterDto, ExtraFilter>().ReverseMap();
CreateMap<ExtraFilterItemsDto, ExtraFilterItems>().ReverseMap();
CreateMap<ExtraFilterEditDto, ExtraFilterDto>().ReverseMap();
CreateMap<ListFormImport, ListFormsImportDto>();
CreateMap<ListFormImportExecute, ListFormImportExecuteDto>();

View file

@ -291,7 +291,6 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
};
var queryParameters = httpContextAccessor.HttpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value);
var defaultFields = await defaultValueManager.GenerateDefaultValuesAsync(listForm, Enums.OperationEnum.Select, queryParameters: queryParameters);
foreach (var field in defaultFields)

View file

@ -118,4 +118,7 @@ public class ListForm : FullAuditedEntity<Guid>
/// <summary>bu listform'un üstünde yer alan widgetların listesidir</summary>
public string WidgetsJson { get; set; }
/// <summary>bu listform'un üstünde yer alan widgetların listesidir</summary>
public string ExtraFilterJson { get; set; }
}

View file

@ -0,0 +1,26 @@
using System.Collections.Generic;
using Volo.Abp.Domain.Values;
namespace Kurs.Platform.Queries;
public class ExtraFilter : ValueObject
{
public string FieldName { get; set; }
public string Caption { get; set; }
public string Operator { get; set; }
public ExtraFilterItems[] Items { get; set; }
protected override IEnumerable<object> GetAtomicValues()
{
yield return FieldName;
yield return Caption;
yield return Operator;
yield return Items;
}
}
public class ExtraFilterItems
{
public string Value { get; set; }
public string Text { get; set; }
}

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Kurs.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20250911192105_Initial")]
[Migration("20250917121912_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -2806,6 +2806,9 @@ namespace Kurs.Platform.Migrations
b.Property<string>("EditingOptionJson")
.HasColumnType("nvarchar(max)");
b.Property<string>("ExtraFilterJson")
.HasColumnType("nvarchar(max)");
b.Property<string>("FilterPanelJson")
.HasColumnType("nvarchar(max)");

View file

@ -1240,6 +1240,7 @@ namespace Kurs.Platform.Migrations
IsSubForm = table.Column<bool>(type: "bit", nullable: false),
SubFormsJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
WidgetsJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
ExtraFilterJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),

View file

@ -2803,6 +2803,9 @@ namespace Kurs.Platform.Migrations
b.Property<string>("EditingOptionJson")
.HasColumnType("nvarchar(max)");
b.Property<string>("ExtraFilterJson")
.HasColumnType("nvarchar(max)");
b.Property<string>("FilterPanelJson")
.HasColumnType("nvarchar(max)");

View file

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

View file

@ -478,6 +478,8 @@ export interface GridOptionsDto extends AuditedEntityDto<string> {
isSubForm: boolean
subFormsJson?: string
subFormsDto: SubFormDto[]
extraFilterJson?: string
extraFilterDto: ExtraFilterDto[]
}
export interface GridOptionsEditDto extends GridOptionsDto, Record<string, any> {
@ -722,3 +724,16 @@ export interface WidgetEditDto {
subTitle: string
onClick: string
}
export interface ExtraFilterDto {
fieldName: string
caption: string
operator: string
defaultValue?: string
items: ExtraFilterItemsDto[]
}
export interface ExtraFilterItemsDto {
value: string
text: string
}

View file

@ -31,11 +31,24 @@ const useListFormCustomDataSource = ({
load: async (loadOptions) => {
const parameters = getLoadOptions(loadOptions, {
listFormCode,
filter: searchParams?.get('filter'),
filter: '',
createDeleteQuery: searchParams?.get('createDeleteQuery'),
group: '',
})
// 1. Default filter'ı al
const defaultFilter = searchParams?.get('filter')
? JSON.parse(searchParams.get('filter')!)
: null
let combinedFilter: any = parameters.filter
// 2. Eğer hem default hem de grid filter varsa merge et
if (defaultFilter && combinedFilter) {
combinedFilter = [defaultFilter, 'and', combinedFilter]
} else if (defaultFilter) {
combinedFilter = defaultFilter
}
//editing asamasinda her bir field de yapilan degisiklik load istegi olarak buraya dusuyor.
//TODO: bu bug halen devam ediyor!!
//Bunu engellemek icin eklendi.
@ -69,7 +82,8 @@ const useListFormCustomDataSource = ({
}
}
}
parameters.filter = JSON.stringify(parameters.filter)
parameters.filter = JSON.stringify(combinedFilter)
//parameters.filter = JSON.stringify(parameters.filter)
const response = await dynamicFetch('list-form-select/select', 'GET', parameters)
if (setWidgetGroups) setWidgetGroups(response.data.widgets ?? [])
@ -145,11 +159,24 @@ const useListFormCustomDataSource = ({
totalCount: async (loadOptions) => {
const parameters = getLoadOptions(loadOptions, {
listFormCode,
filter: searchParams?.get('filter'),
filter: '',
createDeleteQuery: searchParams?.get('createDeleteQuery'),
group: '',
})
parameters.filter = JSON.stringify(parameters.filter)
const defaultFilter = searchParams?.get('filter')
? JSON.parse(searchParams.get('filter')!)
: null
let combinedFilter: any = parameters.filter
if (defaultFilter && combinedFilter) {
combinedFilter = [defaultFilter, 'and', combinedFilter]
} else if (defaultFilter) {
combinedFilter = defaultFilter
}
parameters.filter = JSON.stringify(combinedFilter)
try {
const response = await dynamicFetch('list-form-select/select', 'GET', parameters)
return response.data.totalCount

View file

@ -10,7 +10,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
export function Forum() {
const { translate } = useLocalization()
const { user, tenant } = useStoreState((state) => state.auth)
const { user } = useStoreState((state) => state.auth)
const {
categories,
topics,

View file

@ -51,7 +51,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import SubForms from '../form/SubForms'
import { RowMode, SimpleItemWithColData } from '../form/types'
import { GridColumnData } from './GridColumnData'
import { GridColumnData, GridExtraFilterState } from './GridColumnData'
import GridFilterDialogs from './GridFilterDialogs'
import {
addCss,
@ -66,6 +66,7 @@ import { useFilters } from './useFilters'
import { useToolbar } from './useToolbar'
import { ImportDashboard } from '@/components/importManager/ImportDashboard'
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
import { GridExtraFilterToolbar } from './GridExtraFilterToolbar'
interface GridProps {
listFormCode: string
@ -91,6 +92,7 @@ const Grid = (props: GridProps) => {
const [formData, setFormData] = useState<any>()
const [mode, setMode] = useState<RowMode>('view')
const [widgetGroups, setWidgetGroups] = useState<WidgetGroupDto[]>([])
const [extraFilters, setExtraFilters] = useState<GridExtraFilterState[]>([])
const preloadExportLibs = () => {
import('exceljs')
@ -107,6 +109,8 @@ const Grid = (props: GridProps) => {
getSelectedRowsData,
refreshData,
getFilter,
extraFilters,
setExtraFilters,
})
const { filterToolbarData, ...filterData } = useFilters({
@ -401,6 +405,22 @@ const Grid = (props: GridProps) => {
const cols = getBandedColumns()
setColumnData(cols)
// Filters'i AND ile birleştir
let filterArray: any[] = []
extraFilters.forEach((f) => {
if (f.value) {
if (filterArray.length > 0) {
filterArray.push('and')
}
filterArray.push([f.fieldName, f.operator, f.value])
}
})
const params = new URLSearchParams()
if (filterArray.length > 0) {
params.set('filter', JSON.stringify(filterArray))
}
// Set data source
const dataSource: CustomStore<any, any> = createSelectDataSource(
gridDto.gridOptions,
@ -507,10 +527,81 @@ const Grid = (props: GridProps) => {
}
}
useEffect(() => {
if (gridDto?.gridOptions.extraFilterDto) {
setExtraFilters(
gridDto.gridOptions.extraFilterDto.map((f) => ({
fieldName: f.fieldName,
operator: f.operator,
value: f.defaultValue ?? '',
})),
)
}
}, [gridDto])
useEffect(() => {
if (!searchParams) 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 (
<>
<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}>
{!isSubForm && (
<Helmet
@ -520,6 +611,7 @@ const Grid = (props: GridProps) => {
></Helmet>
)}
{gridDto && columnData && (
<>
<div className="p-1">
<DataGrid
ref={gridRef as any}
@ -581,7 +673,9 @@ const Grid = (props: GridProps) => {
useIcons={gridDto.gridOptions.editingOptionDto?.useIcons}
confirmDelete={gridDto.gridOptions.editingOptionDto?.confirmDelete}
newRowPosition={gridDto.gridOptions.editingOptionDto?.newRowPosition}
selectTextOnEditStart={gridDto.gridOptions.editingOptionDto?.selectTextOnEditStart}
selectTextOnEditStart={
gridDto.gridOptions.editingOptionDto?.selectTextOnEditStart
}
startEditAction={gridDto.gridOptions.editingOptionDto?.startEditAction}
popup={{
title: gridDto.gridOptions.editingOptionDto?.popup?.title,
@ -699,6 +793,13 @@ const Grid = (props: GridProps) => {
></Editing>
<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 || filterToolbarData.length > 0}>
{toolbarData.map((item) => (
<Item key={item.name} {...item}></Item>
@ -706,6 +807,10 @@ const Grid = (props: GridProps) => {
{filterToolbarData.map((item) => (
<Item key={item.name} {...item}></Item>
))}
{/* burada özel filtre alanını Template ile bağla */}
{gridDto?.gridOptions.extraFilterDto?.length ? (
<Item location="before" template="extraFilters" />
) : null}
</Toolbar>
<Sorting mode={gridDto.gridOptions?.sortMode}></Sorting>
<FilterRow
@ -719,7 +824,9 @@ const Grid = (props: GridProps) => {
width={gridDto.gridOptions.searchPanelDto.width}
></SearchPanel>
<GroupPanel visible={gridDto.gridOptions.groupPanelDto?.visible}></GroupPanel>
<Grouping autoExpandAll={gridDto.gridOptions.groupPanelDto?.autoExpandAll}></Grouping>
<Grouping
autoExpandAll={gridDto.gridOptions.groupPanelDto?.autoExpandAll}
></Grouping>
<Selection
mode={gridDto.gridOptions.selectionDto?.mode}
allowSelectAll={gridDto.gridOptions.selectionDto?.allowSelectAll}
@ -787,6 +894,7 @@ const Grid = (props: GridProps) => {
<Format type="fixedPoint" precision={2} />
</Column> */}
</DataGrid>
{gridDto?.gridOptions?.subFormsDto?.length > 0 && (
<>
<hr className="my-4" />
@ -803,6 +911,7 @@ const Grid = (props: GridProps) => {
<ImportDashboard gridDto={gridDto} />
</Dialog>
</div>
</>
)}
<Dialog

View file

@ -1,6 +1,12 @@
import { DataGridTypes } from 'devextreme-react/data-grid'
import { ColumnFormatDto, GridBoxOptionsDto, TagBoxOptionsDto } from '../../proxy/form/models'
export interface GridExtraFilterState {
fieldName: string
operator: string
value: string
}
interface IGridColumnData extends DataGridTypes.Column {
colData?: ColumnFormatDto
extras?: {

View file

@ -0,0 +1,42 @@
import { GridExtraFilterState } from './GridColumnData'
export function GridExtraFilterToolbar({
filters,
extraFilters,
setExtraFilters,
}: {
filters: any[]
extraFilters: GridExtraFilterState[]
setExtraFilters: React.Dispatch<React.SetStateAction<GridExtraFilterState[]>>
}) {
return (
<div className="flex gap-4">
{filters.map((fs) => {
const current = extraFilters.find((f) => f.fieldName === fs.fieldName)
return (
<div key={fs.fieldName} className="flex items-center gap-2">
<label>{fs.caption}</label>
<select
className="border rounded px-0.5 py-0.5"
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: any) => (
<option key={item.value} value={item.value}>
{item.text}
</option>
))}
</select>
</div>
)
})}
</div>
)
}

View file

@ -8,6 +8,7 @@ import { ToolbarItem } from 'devextreme/ui/data_grid_types'
import { useEffect, useState } from 'react'
import { useDialogContext } from '../shared/DialogContext'
import { usePWA } from '@/utils/hooks/usePWA'
import { GridExtraFilterState } from './GridColumnData'
type ToolbarModalData = {
open: boolean
@ -23,6 +24,8 @@ const useToolbar = ({
getSelectedRowsData,
refreshData,
getFilter,
extraFilters,
setExtraFilters,
}: {
gridDto?: GridDto
listFormCode: string
@ -30,6 +33,8 @@ const useToolbar = ({
getSelectedRowsData: () => any
refreshData: () => void
getFilter: () => void
extraFilters: GridExtraFilterState[]
setExtraFilters: React.Dispatch<React.SetStateAction<GridExtraFilterState[]>>
}): {
toolbarData: ToolbarItem[]
toolbarModalData: ToolbarModalData | undefined