Grid üzerinde Widgets çalışma şekli değiştirildi.

This commit is contained in:
Sedat ÖZTÜRK 2025-09-22 12:11:15 +03:00
parent acfe8030dd
commit 5dd6e0bcd4
12 changed files with 194 additions and 125 deletions

View file

@ -8,8 +8,10 @@ public class GridDto
{ {
ColumnFormats = new List<ColumnFormatDto>(); ColumnFormats = new List<ColumnFormatDto>();
GridOptions = new GridOptionsDto(); GridOptions = new GridOptionsDto();
Widgets = new List<WidgetDto>();
} }
public List<ColumnFormatDto> ColumnFormats { get; set; } public List<ColumnFormatDto> ColumnFormats { get; set; }
public GridOptionsDto GridOptions { get; set; } public GridOptionsDto GridOptions { get; set; }
public List<WidgetDto> Widgets { get; set; }
} }

View file

@ -22,6 +22,4 @@ public class SelectDto
/// <summary> Sonuc ile ilgili bazi bilgileride gonderir. Ornegin: filtre uygulandı mı? Sunucu filtresi uygulandı mı? /// <summary> Sonuc ile ilgili bazi bilgileride gonderir. Ornegin: filtre uygulandı mı? Sunucu filtresi uygulandı mı?
/// </summary> /// </summary>
public QueryInfoDto QueryInfos { get; set; } public QueryInfoDto QueryInfos { get; set; }
public List<WidgetDto> Widgets { get; set; }
} }

View file

@ -214,8 +214,12 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
IsAppliedServerFilter = selectQueryManager.IsAppliedServerFilter, IsAppliedServerFilter = selectQueryManager.IsAppliedServerFilter,
}; };
//Widgets verileri gösterilecek ise return result;
result.Widgets = []; }
private async Task<List<WidgetDto>> GetWidgetsAsync(ListForm listForm)
{
var Widgets = new List<WidgetDto>();
if (!listForm.WidgetsJson.IsNullOrWhiteSpace()) if (!listForm.WidgetsJson.IsNullOrWhiteSpace())
{ {
@ -224,6 +228,9 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
{ {
if (!string.IsNullOrWhiteSpace(widget.SqlQuery)) if (!string.IsNullOrWhiteSpace(widget.SqlQuery))
{ {
var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
var w = new WidgetDto var w = new WidgetDto
{ {
ColGap = widget.ColGap, ColGap = widget.ColGap,
@ -253,12 +260,12 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
} }
result.Widgets.Add(w); Widgets.Add(w);
} }
} }
} }
return result; return Widgets;
} }
public async Task<GridDto> GetGridAsync(string ListFormCode) public async Task<GridDto> GetGridAsync(string ListFormCode)
@ -287,7 +294,8 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
var result = new GridDto var result = new GridDto
{ {
GridOptions = ObjectMapper.Map<ListForm, GridOptionsDto>(listForm), GridOptions = ObjectMapper.Map<ListForm, GridOptionsDto>(listForm),
ColumnFormats = ObjectMapper.Map<List<ListFormField>, List<ColumnFormatDto>>(fields) ColumnFormats = ObjectMapper.Map<List<ListFormField>, List<ColumnFormatDto>>(fields),
Widgets = await GetWidgetsAsync(listForm)
}; };
var queryParameters = httpContextAccessor.HttpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value); var queryParameters = httpContextAccessor.HttpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value);

View file

@ -12,5 +12,5 @@ RUN npm i && \
npm install @rollup/rollup-linux-x64-musl --save-dev && \ npm install @rollup/rollup-linux-x64-musl --save-dev && \
npm rebuild rollup npm rebuild rollup
COPY . . COPY . .
RUN npm run build -- --mode $ENV RUN npm run build && node scripts/write-version.js -- --mode $ENV
CMD ["npm", "run", "preview"] CMD ["npm", "run", "preview"]

View file

@ -1,6 +1,20 @@
{ {
"commit": "a913220", "commit": "acfe803",
"releases": [ "releases": [
{
"version": "1.0.10",
"buildDate": "2025-09-22",
"changeLog": [
"EditorOptions içerisine DataSource özelliği eklendi"
]
},
{
"version": "1.0.9",
"buildDate": "2025-09-21",
"changeLog": [
"Form komponentinin SelectBox -> lookup bilgisi varsa verileri dolduruyor"
]
},
{ {
"version": "1.0.8", "version": "1.0.8",
"buildDate": "2025-09-21", "buildDate": "2025-09-21",

View file

@ -31,6 +31,7 @@ export interface SelectListItem {
export interface GridDto { export interface GridDto {
columnFormats: ColumnFormatDto[] columnFormats: ColumnFormatDto[]
gridOptions: GridOptionsDto gridOptions: GridOptionsDto
widgets: WidgetGroupDto[]
} }
// 3 enums // 3 enums

View file

@ -23,7 +23,6 @@ const useListFormCustomDataSource = ({
listFormCode: string, listFormCode: string,
searchParams?: URLSearchParams, searchParams?: URLSearchParams,
cols?: GridColumnData[], cols?: GridColumnData[],
// setWidgetGroups?: (widgetGroups: WidgetGroupDto[]) => void,
) => { ) => {
const store: any = new CustomStore({ const store: any = new CustomStore({
key: gridOptions.keyFieldName, key: gridOptions.keyFieldName,
@ -139,8 +138,6 @@ 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,

View file

@ -67,8 +67,6 @@ const FormButtons = (props: {
getSelectedRowsData, getSelectedRowsData,
refreshData, refreshData,
getFilter, getFilter,
extraFilters,
setExtraFilters,
}) })
const handleDelete = async (e: any) => { const handleDelete = async (e: any) => {

View file

@ -49,7 +49,6 @@ const FormDevExpress = (props: {
caption={formGroupItem.caption} caption={formGroupItem.caption}
> >
{(formGroupItem.items as SimpleItemWithColData[])?.map((formItem, i) => { {(formGroupItem.items as SimpleItemWithColData[])?.map((formItem, i) => {
console.log('formItem', formItem.colData)
return formItem.editorType2 === PlatformEditorTypes.dxTagBox ? ( return formItem.editorType2 === PlatformEditorTypes.dxTagBox ? (
<SimpleItemDx <SimpleItemDx
key={'formItem-' + i} key={'formItem-' + i}

View file

@ -2,10 +2,10 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { EditingFormItemDto, GridDto, PlatformEditorTypes } from '@/proxy/form/models' import { EditingFormItemDto, GridDto, PlatformEditorTypes } from '@/proxy/form/models'
import { captionize } from 'devextreme/core/utils/inflector' import { captionize } from 'devextreme/core/utils/inflector'
import { useListFormCustomDataSource } from '@/shared/useListFormCustomDataSource' import { useListFormCustomDataSource } from '@/shared/useListFormCustomDataSource'
import { Pagination, Select } from '@/components/ui' import { Button, Pagination, Select } from '@/components/ui'
import classNames from 'classnames' import classNames from 'classnames'
import { getList } from '@/services/form.service' import { getList } from '@/services/form.service'
import { FaInbox } from 'react-icons/fa' import { FaInbox, FaSearch } from 'react-icons/fa'
import FormDevExpress from '../form/FormDevExpress' import FormDevExpress from '../form/FormDevExpress'
import { GroupItem } from 'devextreme/ui/form' import { GroupItem } from 'devextreme/ui/form'
import { Form as FormDx } from 'devextreme-react/form' import { Form as FormDx } from 'devextreme-react/form'
@ -17,6 +17,9 @@ import { PermissionResults, SimpleItemWithColData } from '../form/types'
import { usePermission } from '@/utils/hooks/usePermission' import { usePermission } from '@/utils/hooks/usePermission'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { useLookupDataSource } from '../form/useLookupDataSource' import { useLookupDataSource } from '../form/useLookupDataSource'
import { Container, Loading } from '@/components/shared'
import WidgetGroup from '@/components/common/WidgetGroup'
import { GridExtraFilterState } from './Utils'
interface CardProps { interface CardProps {
listFormCode: string listFormCode: string
@ -35,7 +38,7 @@ const CardItem = ({
listFormCode, listFormCode,
dataSource, dataSource,
refreshData, refreshData,
getCachedLookupDataSource, // 🔹 Carddan gelen cache fonksiyonu getCachedLookupDataSource,
}: { }: {
isSubForm?: boolean isSubForm?: boolean
row: any row: any
@ -188,9 +191,10 @@ const Card = ({ listFormCode, searchParams }: CardProps) => {
const [currentPage, setCurrentPage] = useState(1) const [currentPage, setCurrentPage] = useState(1)
const [pageSize, setPageSize] = useState(20) const [pageSize, setPageSize] = useState(20)
const [pageSizeOptions, setPageSizeOptions] = useState<Option[]>([]) const [pageSizeOptions, setPageSizeOptions] = useState<Option[]>([])
const [dataSource, setDataSource] = useState<CustomStore>() const [gridDataSource, setGridDataSource] = useState<CustomStore<any, any>>()
const [extraFilters, setExtraFilters] = useState<GridExtraFilterState[]>([])
const [layoutCount, setLayoutCount] = useState(4)
// ✅ Lookup cache burada (Card seviyesinde, tüm CardItemlar ortak kullanır)
const { getLookupDataSource } = useLookupDataSource({ listFormCode }) const { getLookupDataSource } = useLookupDataSource({ listFormCode })
const lookupCache = useRef<Map<string, any>>(new Map()) const lookupCache = useRef<Map<string, any>>(new Map())
@ -204,7 +208,7 @@ const Card = ({ listFormCode, searchParams }: CardProps) => {
} }
return lookupCache.current.get(key) return lookupCache.current.get(key)
}, },
[getLookupDataSource] [getLookupDataSource],
) )
const onPageSizeSelect = ({ value }: Option) => { const onPageSizeSelect = ({ value }: Option) => {
@ -222,13 +226,13 @@ const Card = ({ listFormCode, searchParams }: CardProps) => {
useEffect(() => { useEffect(() => {
if (gridDto) { if (gridDto) {
const store = createSelectDataSource(gridDto.gridOptions, listFormCode, searchParams) const dataSource = createSelectDataSource(gridDto.gridOptions, listFormCode, searchParams)
setDataSource(store) setGridDataSource(dataSource)
} }
}, [gridDto, listFormCode, searchParams, createSelectDataSource]) }, [gridDto, listFormCode, searchParams, createSelectDataSource])
const loadData = useCallback(() => { const loadData = useCallback(() => {
if (!dataSource) return if (!gridDataSource) return
const loadOptions = { const loadOptions = {
skip: (currentPage - 1) * pageSize, skip: (currentPage - 1) * pageSize,
@ -236,24 +240,23 @@ const Card = ({ listFormCode, searchParams }: CardProps) => {
requireTotalCount: true, requireTotalCount: true,
} }
dataSource.load(loadOptions).then((res: any) => { gridDataSource.load(loadOptions).then((res: any) => {
setData(res.data) setData(res.data)
setTotalCount(res.totalCount || 0) setTotalCount(res.totalCount || 0)
}) })
}, [dataSource, currentPage, pageSize]) }, [gridDataSource, currentPage, pageSize])
useEffect(() => { useEffect(() => {
if (dataSource) { if (gridDataSource) {
loadData() loadData()
} }
}, [dataSource, loadData]) }, [gridDataSource, loadData])
useEffect(() => { useEffect(() => {
if (!gridDto) return if (!gridDto) return
const pagerOptions = gridDto.gridOptions.pagerOptionDto const pagerOptions = gridDto.gridOptions.pagerOptionDto
const allowedSizes = const allowedSizes = pagerOptions?.allowedPageSizes
pagerOptions?.allowedPageSizes
?.split(',') ?.split(',')
.map((s) => Number(s.trim())) .map((s) => Number(s.trim()))
.filter((n) => !isNaN(n) && n > 0) || [10, 20, 50, 100] .filter((n) => !isNaN(n) && n > 0) || [10, 20, 50, 100]
@ -261,25 +264,58 @@ const Card = ({ listFormCode, searchParams }: CardProps) => {
setPageSizeOptions(allowedSizes.map((size) => ({ value: size, label: `${size} page` }))) setPageSizeOptions(allowedSizes.map((size) => ({ value: size, label: `${size} page` })))
}, [gridDto, listFormCode, searchParams]) }, [gridDto, listFormCode, searchParams])
// Data güncellendiğinde sayfanın en üstüne kaydır
useEffect(() => {
if (data.length > 0) {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}, [data])
if (!gridDto) return null if (!gridDto) return null
return ( return (
<> <>
{data.length === 0 && ( <WidgetGroup widgetGroups={gridDto.widgets || []} />
<div className="flex flex-col items-center justify-center p-10 bg-gray-50 dark:bg-neutral-800/50 rounded-md border-2 border-dashed border-gray-200 dark:border-neutral-700">
<div className="text-center"> <Container>
<FaInbox className="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" /> <div className="bg-white border border-solid border-1 dark:bg-neutral-800 dark:border-neutral-700 flex justify-end items-center">
<p className="mt-4 text-lg font-semibold text-gray-700 dark:text-gray-300"> <div className="relative py-1 px-2 flex gap-1">
Kayıt Bulunamadı <FaSearch className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm" />
</p> <input
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1"> type="text"
Görüntülenecek herhangi bir veri yok. placeholder="Search..."
</p> className="p-1 pl-6 pr-2 border border-1 outline-none text-xs text-gray-700 dark:text-gray-200 placeholder-gray-400 rounded"
/>
<Button
size="xs"
variant={layoutCount === 3 ? 'solid' : 'default'}
className="text-sm"
onClick={() => setLayoutCount(3)}
>
3
</Button>
<Button
size="xs"
variant={layoutCount === 4 ? 'solid' : 'default'}
className="text-sm"
onClick={() => setLayoutCount(4)}
>
4
</Button>
<Button
size="xs"
variant={layoutCount === 5 ? 'solid' : 'default'}
className="text-sm"
onClick={() => setLayoutCount(5)}
>
5
</Button>
</div> </div>
</div> </div>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-4 gap-4"> <div className={`grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-${layoutCount} gap-4`}>
{dataSource && {gridDataSource &&
data.map((row, idx) => { data.map((row, idx) => {
const keyField = gridDto.gridOptions.keyFieldName const keyField = gridDto.gridOptions.keyFieldName
const rowId = row[keyField!] const rowId = row[keyField!]
@ -291,9 +327,9 @@ const Card = ({ listFormCode, searchParams }: CardProps) => {
row={row} row={row}
gridDto={gridDto} gridDto={gridDto}
listFormCode={listFormCode} listFormCode={listFormCode}
dataSource={dataSource} dataSource={gridDataSource}
refreshData={loadData} refreshData={loadData}
getCachedLookupDataSource={getCachedLookupDataSource} // 🔹 Prop olarak veriliyor getCachedLookupDataSource={getCachedLookupDataSource}
/> />
) )
})} })}
@ -321,6 +357,22 @@ const Card = ({ listFormCode, searchParams }: CardProps) => {
/> />
</div> </div>
)} )}
{data.length === 0 && (
<Loading loading={!data.length} />
// <div className="flex flex-col items-center justify-center p-10 bg-gray-50 dark:bg-neutral-800/50 rounded-md border-2 border-dashed border-gray-200 dark:border-neutral-700">
// <div className="text-center">
// <FaInbox className="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" />
// <p className="mt-4 text-lg font-semibold text-gray-700 dark:text-gray-300">
// Kayıt Bulunamadı
// </p>
// <p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
// Görüntülenecek herhangi bir veri yok.
// </p>
// </div>
// </div>
)}
</Container>
</> </>
) )
} }

View file

@ -7,7 +7,6 @@ import {
GridDto, GridDto,
ListFormCustomizationTypeEnum, ListFormCustomizationTypeEnum,
PlatformEditorTypes, PlatformEditorTypes,
WidgetGroupDto,
} from '@/proxy/form/models' } from '@/proxy/form/models'
import { getList } from '@/services/form.service' import { getList } from '@/services/form.service'
import { import {
@ -93,7 +92,6 @@ const Grid = (props: GridProps) => {
const [columnData, setColumnData] = useState<GridColumnData[]>() const [columnData, setColumnData] = useState<GridColumnData[]>()
const [formData, setFormData] = useState<any>() const [formData, setFormData] = useState<any>()
const [mode, setMode] = useState<RowMode>('view') const [mode, setMode] = useState<RowMode>('view')
const [widgetGroups, setWidgetGroups] = useState<WidgetGroupDto[]>([])
const [extraFilters, setExtraFilters] = useState<GridExtraFilterState[]>([]) const [extraFilters, setExtraFilters] = useState<GridExtraFilterState[]>([])
const preloadExportLibs = () => { const preloadExportLibs = () => {
@ -120,8 +118,6 @@ const Grid = (props: GridProps) => {
getSelectedRowsData, getSelectedRowsData,
refreshData, refreshData,
getFilter, getFilter,
extraFilters,
setExtraFilters,
}) })
const { filterToolbarData, ...filterData } = useFilters({ const { filterToolbarData, ...filterData } = useFilters({
@ -403,10 +399,6 @@ 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 () => {
@ -416,6 +408,10 @@ const Grid = (props: GridProps) => {
if (refListFormCode.current !== listFormCode) { if (refListFormCode.current !== listFormCode) {
initializeGrid() initializeGrid()
//bir önceki gridin filtreleri kalmasın
setExtraFilters([])
defaultSearchParamsFilter.current = null
} }
}, [listFormCode]) }, [listFormCode])
@ -438,6 +434,7 @@ const Grid = (props: GridProps) => {
} }
if (gridDto?.gridOptions.extraFilterDto) { if (gridDto?.gridOptions.extraFilterDto) {
console.log('extraFilterDto:', gridDto.gridOptions.extraFilterDto)
setExtraFilters( setExtraFilters(
gridDto.gridOptions.extraFilterDto.map((f) => ({ gridDto.gridOptions.extraFilterDto.map((f) => ({
fieldName: f.fieldName, fieldName: f.fieldName,
@ -466,34 +463,50 @@ const Grid = (props: GridProps) => {
useEffect(() => { useEffect(() => {
const activeFilters = extraFilters.filter((f) => f.value) const activeFilters = extraFilters.filter((f) => f.value)
let filter: any = null let base: any = null
if (defaultSearchParamsFilter.current) {
base = JSON.parse(defaultSearchParamsFilter.current)
}
if (activeFilters.length === 1) { // Default filter tripletlerini çıkar
filter = [activeFilters[0].fieldName, activeFilters[0].operator, activeFilters[0].value] const baseTriplets = extractSearchParamsFields(base)
} else if (activeFilters.length > 1) {
filter = activeFilters.reduce((acc, f, idx) => { // Extra filter tripletleri hazırla
if (idx === 0) return [f.fieldName, f.operator, f.value] const extraTriplets = activeFilters.map(
return [acc, 'and', [f.fieldName, f.operator, f.value]] (f) => [f.fieldName, f.operator, f.value] as [string, string, any],
)
// Tripletleri birleştir, aynı field+op varsa sonuncuyu al
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 // varsa üzerine yaz
} else {
acc.push(cur)
}
return acc
},
[] as [string, string, any][],
)
// Tek filtre varsa direkt yaz
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) }, null as any)
} }
if (filter) { 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)) searchParams?.set('filter', JSON.stringify(filter))
} else {
// sadece default filter kalsın
if (defaultSearchParamsFilter.current) {
searchParams?.set('filter', defaultSearchParamsFilter.current)
} else { } else {
searchParams?.delete('filter') searchParams?.delete('filter')
} }
}
//console.log('Filter Durumu:', filter, decodeURIComponent(searchParams?.toString()))
gridRef.current?.instance.refresh() gridRef.current?.instance.refresh()
}, [extraFilters]) }, [extraFilters])
@ -594,7 +607,7 @@ const Grid = (props: GridProps) => {
return ( return (
<> <>
<WidgetGroup widgetGroups={widgetGroups} /> <WidgetGroup widgetGroups={gridDto?.widgets ?? []} />
<Container className={DX_CLASSNAMES}> <Container className={DX_CLASSNAMES}>
{!isSubForm && ( {!isSubForm && (
@ -629,15 +642,6 @@ 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}

View file

@ -24,8 +24,6 @@ const useToolbar = ({
getSelectedRowsData, getSelectedRowsData,
refreshData, refreshData,
getFilter, getFilter,
extraFilters,
setExtraFilters,
}: { }: {
gridDto?: GridDto gridDto?: GridDto
listFormCode: string listFormCode: string
@ -33,8 +31,6 @@ const useToolbar = ({
getSelectedRowsData: () => any getSelectedRowsData: () => any
refreshData: () => void refreshData: () => void
getFilter: () => void getFilter: () => void
extraFilters: GridExtraFilterState[]
setExtraFilters: React.Dispatch<React.SetStateAction<GridExtraFilterState[]>>
}): { }): {
toolbarData: ToolbarItem[] toolbarData: ToolbarItem[]
toolbarModalData: ToolbarModalData | undefined toolbarModalData: ToolbarModalData | undefined