CardView komponenti

This commit is contained in:
Sedat ÖZTÜRK 2026-07-16 11:19:17 +03:00
parent d38e4f851a
commit 412ad236cc
20 changed files with 1446 additions and 83 deletions

View file

@ -3,6 +3,7 @@
public class LayoutDto public class LayoutDto
{ {
public bool Grid { get; set; } = true; public bool Grid { get; set; } = true;
public bool Card { get; set; } = true;
public bool Pivot { get; set; } = true; public bool Pivot { get; set; } = true;
public bool Chart { get; set; } = true; public bool Chart { get; set; } = true;
public bool Tree { get; set; } = true; public bool Tree { get; set; } = true;

View file

@ -22,6 +22,7 @@ public class ListFormWizardDto
public string DefaultLayout { get; set; } public string DefaultLayout { get; set; }
public bool Grid { get; set; } public bool Grid { get; set; }
public bool Card { get; set; }
public bool Pivot { get; set; } public bool Pivot { get; set; }
public bool Tree { get; set; } public bool Tree { get; set; }
public bool Chart { get; set; } public bool Chart { get; set; }

View file

@ -281,7 +281,7 @@ public class ListFormWizardAppService(
ExportJson = WizardConsts.DefaultExportJson, ExportJson = WizardConsts.DefaultExportJson,
IsSubForm = false, IsSubForm = false,
ShowNote = input.SubForms.Count > 0 || input.WorkflowCriteria.Count > 0, ShowNote = input.SubForms.Count > 0 || input.WorkflowCriteria.Count > 0,
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler), LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Card, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler),
CultureName = LanguageCodes.En, CultureName = LanguageCodes.En,
ListFormCode = input.ListFormCode, ListFormCode = input.ListFormCode,
Name = nameLangKey, Name = nameLangKey,

View file

@ -4680,6 +4680,12 @@
"en": "Grid Layout", "en": "Grid Layout",
"tr": "Liste Düzeni" "tr": "Liste Düzeni"
}, },
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.DetailsLayoutDto.CardLayout",
"en": "Card Layout",
"tr": "Kart Düzeni"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout", "key": "ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout",

View file

@ -67,6 +67,7 @@ public static class ListFormSeeder_DefaultJsons
public static string DefaultLayoutJson(string DefaultLayout = "grid") => JsonSerializer.Serialize(new LayoutDto() public static string DefaultLayoutJson(string DefaultLayout = "grid") => JsonSerializer.Serialize(new LayoutDto()
{ {
Grid = true, Grid = true,
Card = true,
Pivot = true, Pivot = true,
Chart = true, Chart = true,
Tree = true, Tree = true,

View file

@ -393,7 +393,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
ExportJson = WizardConsts.DefaultExportJson, ExportJson = WizardConsts.DefaultExportJson,
IsSubForm = false, IsSubForm = false,
ShowNote = input.SubForms.Count > 0 || input.WorkflowCriteria.Count > 0, ShowNote = input.SubForms.Count > 0 || input.WorkflowCriteria.Count > 0,
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler), LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Card, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler),
CultureName = LanguageCodes.En, CultureName = LanguageCodes.En,
ListFormCode = input.ListFormCode, ListFormCode = input.ListFormCode,
Name = nameLangKey, Name = nameLangKey,

View file

@ -84,9 +84,10 @@ public static class WizardConsts
Margin = 10 Margin = 10
}); });
public static string DefaultLayoutJson(string DefaultLayout = "grid", bool Grid = true, bool Pivot = true, bool Chart = true, bool Tree = true, bool Gantt = true, bool Scheduler = true) => JsonSerializer.Serialize(new public static string DefaultLayoutJson(string DefaultLayout = "grid", bool Grid = true, bool Card = true, bool Pivot = true, bool Chart = true, bool Tree = true, bool Gantt = true, bool Scheduler = true) => JsonSerializer.Serialize(new
{ {
Grid = Grid, Grid = Grid,
Card = Card,
Pivot = Pivot, Pivot = Pivot,
Chart = Chart, Chart = Chart,
Tree = Tree, Tree = Tree,

View file

@ -4,6 +4,22 @@ body {
font-size: 14px; /* bodyye font-size uygulanmaz */ font-size: 14px; /* bodyye font-size uygulanmaz */
} }
.list-card-view.dx-cardview {
padding: 0;
border-radius: 0;
background-color: transparent;
}
.list-card-view .dx-cardview-card,
.list-card-view .dx-cardview-card-header .dx-toolbar,
.list-card-view .dx-cardview-card-footer {
border-radius: 0 !important;
}
.list-card-view .dx-toolbar .dx-button-text {
text-transform: none !important;
}
.dx-form-group-with-caption > .dx-form-group-content { .dx-form-group-with-caption > .dx-form-group-content {
padding-top: 2px !important; padding-top: 2px !important;
} }

View file

@ -1,9 +1,9 @@
import { SelectCommandTypeEnum } from '@/proxy/form/models'
import { import {
ListFormWorkflowCriteriaDto, ListFormWorkflowCriteriaDto,
SubFormDto, SubFormDto,
WidgetEditDto, WidgetEditDto,
WorkflowDto, WorkflowDto,
SelectCommandTypeEnum
} from '@/proxy/form/models' } from '@/proxy/form/models'
import { ListViewLayoutType } from '@/views/admin/listForm/edit/types' import { ListViewLayoutType } from '@/views/admin/listForm/edit/types'
@ -39,6 +39,7 @@ export interface ListFormWizardDto {
defaultLayout: ListViewLayoutType defaultLayout: ListViewLayoutType
grid: boolean grid: boolean
card: boolean
pivot: boolean pivot: boolean
tree: boolean tree: boolean
chart: boolean chart: boolean

View file

@ -951,6 +951,7 @@ export interface ListFormWorkflowCriteriaDto {
export interface LayoutDto { export interface LayoutDto {
grid: boolean grid: boolean
card: boolean
pivot: boolean pivot: boolean
tree: boolean tree: boolean
chart: boolean chart: boolean

View file

@ -26,6 +26,7 @@ const schema = Yup.object().shape({
defaultLayout: Yup.string(), defaultLayout: Yup.string(),
layoutDto: Yup.object().shape({ layoutDto: Yup.object().shape({
grid: Yup.boolean(), grid: Yup.boolean(),
card: Yup.boolean(),
pivot: Yup.boolean(), pivot: Yup.boolean(),
chart: Yup.boolean(), chart: Yup.boolean(),
tree: Yup.boolean(), tree: Yup.boolean(),
@ -312,6 +313,22 @@ function FormTabDetails(
/> />
</FormItem> </FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.CardLayout')}
invalid={errors.layoutDto?.card && touched.layoutDto?.card}
errorMessage={errors.layoutDto?.card}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.card"
placeholder={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.CardLayout',
)}
component={Checkbox}
/>
</FormItem>
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')} label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')}
invalid={errors.layoutDto?.pivot && touched.layoutDto?.pivot} invalid={errors.layoutDto?.pivot && touched.layoutDto?.pivot}

View file

@ -224,6 +224,7 @@ export const listFormTypeOptions = [
export const listFormDefaultLayoutOptions = [ export const listFormDefaultLayoutOptions = [
{ value: 'grid', label: 'Grid' }, { value: 'grid', label: 'Grid' },
{ value: 'card', label: 'Card' },
{ value: 'pivot', label: 'Pivot' }, { value: 'pivot', label: 'Pivot' },
{ value: 'chart', label: 'Chart' }, { value: 'chart', label: 'Chart' },
{ value: 'tree', label: 'Tree' }, { value: 'tree', label: 'Tree' },

View file

@ -1,9 +1,17 @@
export type ChartOperation = '' | 'select' | 'insert' | 'update' | 'delete' export type ChartOperation = '' | 'select' | 'insert' | 'update' | 'delete'
export type ChartDialogType = '' | 'pane' | 'serie' | 'annotation' | 'axis' export type ChartDialogType = '' | 'pane' | 'serie' | 'annotation' | 'axis'
export type ListViewLayoutType = 'grid' | 'pivot' | 'tree' | 'chart' | 'gantt' | 'scheduler' export type ListViewLayoutType =
| 'grid'
| 'card'
| 'pivot'
| 'tree'
| 'chart'
| 'gantt'
| 'scheduler'
export const layoutTypes = { export const layoutTypes = {
grid: 'Grid', grid: 'Grid',
card: 'Card',
pivot: 'Pivot', pivot: 'Pivot',
tree: 'Tree', tree: 'Tree',
chart: 'Chart', chart: 'Chart',

View file

@ -516,6 +516,20 @@ const WizardStep2 = ({
/> />
</FormItem> </FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.CardLayout')}
invalid={errors?.card && touched?.card}
errorMessage={errors?.card}
>
<Field
className="w-20"
autoComplete="off"
name="card"
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.CardLayout')}
component={Checkbox}
/>
</FormItem>
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')} label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')}
invalid={errors?.pivot && touched?.pivot} invalid={errors?.pivot && touched?.pivot}

View file

@ -3,6 +3,40 @@ import React from 'react'
const NO_IMAGE = '/img/others/no-image.png' const NO_IMAGE = '/img/others/no-image.png'
let previewElement: HTMLDivElement | null = null let previewElement: HTMLDivElement | null = null
let previewTarget: HTMLElement | null = null
let previewListenersBound = false
const hidePreviewElement = () => {
previewTarget = null
if (previewElement) {
previewElement.style.opacity = '0'
previewElement.style.display = 'none'
}
}
const bindPreviewDismissListeners = () => {
if (previewListenersBound) return
previewListenersBound = true
document.addEventListener(
'pointermove',
(event) => {
const target = event.target as Node | null
if (
previewTarget &&
(!previewTarget.isConnected || !target || !previewTarget.contains(target))
) {
hidePreviewElement()
}
},
{ passive: true },
)
document.addEventListener('scroll', hidePreviewElement, true)
window.addEventListener('blur', hidePreviewElement)
document.addEventListener('visibilitychange', () => {
if (document.hidden) hidePreviewElement()
})
}
const getPreviewElement = () => { const getPreviewElement = () => {
if (!previewElement) { if (!previewElement) {
@ -31,6 +65,7 @@ const getPreviewElement = () => {
element.appendChild(image) element.appendChild(image)
document.body.appendChild(element) document.body.appendChild(element)
previewElement = element previewElement = element
bindPreviewDismissListeners()
} }
return previewElement return previewElement
@ -38,6 +73,7 @@ const getPreviewElement = () => {
export const showImageHoverPreview = (src: string, event: React.MouseEvent<HTMLElement>) => { export const showImageHoverPreview = (src: string, event: React.MouseEvent<HTMLElement>) => {
const element = getPreviewElement() const element = getPreviewElement()
previewTarget = event.currentTarget
const image = element.querySelector('img') as HTMLImageElement const image = element.querySelector('img') as HTMLImageElement
image.onerror = () => { image.onerror = () => {
image.onerror = null image.onerror = null
@ -65,13 +101,11 @@ export const showImageHoverPreview = (src: string, event: React.MouseEvent<HTMLE
} }
export const hideImageHoverPreview = () => { export const hideImageHoverPreview = () => {
if (previewElement) { hidePreviewElement()
previewElement.style.opacity = '0'
previewElement.style.display = 'none'
}
} }
export const openImageInNewTab = (url: string) => { export const openImageInNewTab = (url: string) => {
hidePreviewElement()
if (!url.startsWith('data:image/')) { if (!url.startsWith('data:image/')) {
window.open(url, '_blank', 'noopener,noreferrer') window.open(url, '_blank', 'noopener,noreferrer')
return return

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,7 @@ import { ListViewLayoutType } from '../admin/listForm/edit/types'
import { import {
FaChartArea, FaChartArea,
FaThLarge,
FaList, FaList,
FaSitemap, FaSitemap,
FaTable, FaTable,
@ -26,6 +27,7 @@ import {
🔥 LAZY VIEW IMPORTS 🔥 LAZY VIEW IMPORTS
======================= */ ======================= */
const Grid = React.lazy(() => import('./Grid')) const Grid = React.lazy(() => import('./Grid'))
const CardView = React.lazy(() => import('./CardView'))
const Pivot = React.lazy(() => import('./Pivot')) const Pivot = React.lazy(() => import('./Pivot'))
const Tree = React.lazy(() => import('./Tree')) const Tree = React.lazy(() => import('./Tree'))
const Chart = React.lazy(() => import('./Chart')) const Chart = React.lazy(() => import('./Chart'))
@ -34,6 +36,7 @@ const SchedulerView = React.lazy(() => import('./SchedulerView'))
function isLayoutValid(dto: GridDto, layout: ListViewLayoutType | undefined): boolean { function isLayoutValid(dto: GridDto, layout: ListViewLayoutType | undefined): boolean {
if (!layout || layout === 'grid') return true if (!layout || layout === 'grid') return true
if (layout === 'card') return true
if (layout === 'pivot') return !!dto.gridOptions?.layoutDto?.pivot if (layout === 'pivot') return !!dto.gridOptions?.layoutDto?.pivot
if (layout === 'chart') return !!dto.gridOptions?.layoutDto?.chart if (layout === 'chart') return !!dto.gridOptions?.layoutDto?.chart
if (layout === 'tree') if (layout === 'tree')
@ -125,6 +128,7 @@ const List: React.FC = () => {
const preload = useMemo( const preload = useMemo(
() => ({ () => ({
grid: () => import('./Grid'), grid: () => import('./Grid'),
card: () => import('./CardView'),
pivot: () => import('./Pivot'), pivot: () => import('./Pivot'),
tree: () => import('./Tree'), tree: () => import('./Tree'),
chart: () => import('./Chart'), chart: () => import('./Chart'),
@ -200,6 +204,16 @@ const List: React.FC = () => {
onMouseEnter={() => preload.grid()} onMouseEnter={() => preload.grid()}
></Button> ></Button>
{gridDto.gridOptions.layoutDto.card && (
<Button
size="sm"
icon={<FaThLarge />}
variant={viewMode === 'card' ? 'solid' : 'default'}
onClick={() => setLayout('card')}
onMouseEnter={() => preload.card()}
></Button>
)}
{gridDto.gridOptions.layoutDto.pivot && ( {gridDto.gridOptions.layoutDto.pivot && (
<Button <Button
size="sm" size="sm"
@ -235,6 +249,15 @@ const List: React.FC = () => {
/> />
)} )}
{viewMode === 'card' && (
<CardView
listFormCode={listFormCode}
searchParams={searchParams}
isSubForm={false}
gridDto={gridDto}
/>
)}
{viewMode === 'pivot' && ( {viewMode === 'pivot' && (
<Pivot <Pivot
listFormCode={listFormCode} listFormCode={listFormCode}

View file

@ -6,7 +6,7 @@ import {
GridDto, GridDto,
PlatformEditorTypes, PlatformEditorTypes,
UiLookupDataSourceTypeEnum, UiLookupDataSourceTypeEnum,
EditingFormItemDto EditingFormItemDto,
} from '@/proxy/form/models' } from '@/proxy/form/models'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import Scheduler, { import Scheduler, {
@ -307,7 +307,9 @@ const SchedulerView = (props: SchedulerViewProps) => {
if (i.editorOptions) { if (i.editorOptions) {
editorOptions = JSON.parse(i.editorOptions) editorOptions = JSON.parse(i.editorOptions)
} }
} catch {} } catch (err) {
console.log(err)
}
const fieldName = i.dataField.split(':')[0] const fieldName = i.dataField.split(':')[0]
const listFormField = gridDto.columnFormats.find((x: any) => x.fieldName === fieldName) const listFormField = gridDto.columnFormats.find((x: any) => x.fieldName === fieldName)

View file

@ -14,12 +14,14 @@ import dxDataGrid from 'devextreme/ui/data_grid'
import dxPivotGrid from 'devextreme/ui/pivot_grid' import dxPivotGrid from 'devextreme/ui/pivot_grid'
import dxTreeList from 'devextreme/ui/tree_list' import dxTreeList from 'devextreme/ui/tree_list'
import dxGantt from 'devextreme/ui/gantt' import dxGantt from 'devextreme/ui/gantt'
import dxCardView from 'devextreme/ui/card_view'
import { Dispatch, MutableRefObject, SetStateAction, useEffect, useState } from 'react' import { Dispatch, MutableRefObject, SetStateAction, useEffect, useState } from 'react'
import { setGridPanelColor } from './Utils' import { setGridPanelColor } from './Utils'
import { usePermission } from '@/utils/hooks/usePermission' import { usePermission } from '@/utils/hooks/usePermission'
import { usePWA } from '@/utils/hooks/usePWA' import { usePWA } from '@/utils/hooks/usePWA'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import type { GanttRef } from 'devextreme-react/cjs/gantt' import type { GanttRef } from 'devextreme-react/cjs/gantt'
import type { CardViewRef } from 'devextreme-react/card-view'
import { useStoreState } from '@/store' import { useStoreState } from '@/store'
import { MULTIVALUE_DELIMITER } from '@/constants/app.constant' import { MULTIVALUE_DELIMITER } from '@/constants/app.constant'
import { createReportQueryString } from '../report/reportRouteParams' import { createReportQueryString } from '../report/reportRouteParams'
@ -29,7 +31,7 @@ export interface ISelectBoxData {
label?: string label?: string
} }
type GridInstance = dxDataGrid | dxPivotGrid | dxTreeList | dxGantt type GridInstance = dxDataGrid | dxPivotGrid | dxTreeList | dxGantt | dxCardView
type FilterToolbarActionId = type FilterToolbarActionId =
| 'openDynamicGridReport' | 'openDynamicGridReport'
| 'saveActiveFilter' | 'saveActiveFilter'
@ -50,68 +52,46 @@ type FilterToolbarAction = {
// Grid tipini kontrol eden yardımcı fonksiyonlar // Grid tipini kontrol eden yardımcı fonksiyonlar
const isDataGrid = (grid: GridInstance): grid is dxDataGrid => { const isDataGrid = (grid: GridInstance): grid is dxDataGrid => {
return 'clearFilter' in grid return 'state' in grid && 'getVisibleColumns' in grid && !('getRootNode' in grid)
} }
const isTreeList = (grid: GridInstance): grid is dxTreeList => { const isTreeList = (grid: GridInstance): grid is dxTreeList => {
return 'getRootNode' in grid return 'getRootNode' in grid
} }
const isCardView = (grid: GridInstance): grid is dxCardView => {
return 'getSelectedCardKeys' in grid
}
const supportsState = (grid: GridInstance): grid is dxDataGrid | dxTreeList => { const supportsState = (grid: GridInstance): grid is dxDataGrid | dxTreeList => {
return 'state' in grid && typeof (grid as any).state === 'function' return 'state' in grid && typeof (grid as any).state === 'function'
} }
const setToolbarItemValue = (grid: GridInstance, itemName: string, value: any) => { const setToolbarItemValue = (grid: GridInstance, itemName: string, value: any) => {
if (isDataGrid(grid)) { const component = grid as any
const toolbarOptions = grid.option('toolbar') const toolbarOptions = component.option('toolbar')
if (toolbarOptions?.items) { if (toolbarOptions?.items) {
const index = toolbarOptions.items.findIndex((item: any) => item.name === itemName) const index = toolbarOptions.items.findIndex((item: any) => item.name === itemName)
if (index > -1 && toolbarOptions.items[index]) { if (index > -1 && toolbarOptions.items[index]) {
const item = toolbarOptions.items[index] as any const item = toolbarOptions.items[index] as any
if (item.options) { if (item.options) {
item.options.value = value item.options.value = value
grid.option('toolbar', toolbarOptions) component.option('toolbar', toolbarOptions)
}
}
}
} 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: GridInstance, itemName: string, items: any[]) => { const setToolbarItemItems = (grid: GridInstance, itemName: string, items: any[]) => {
if (isDataGrid(grid)) { const component = grid as any
const toolbarOptions = grid.option('toolbar') const toolbarOptions = component.option('toolbar')
if (toolbarOptions?.items) { if (toolbarOptions?.items) {
const index = toolbarOptions.items.findIndex((item: any) => item.name === itemName) const index = toolbarOptions.items.findIndex((item: any) => item.name === itemName)
if (index > -1 && toolbarOptions.items[index]) { if (index > -1 && toolbarOptions.items[index]) {
const item = toolbarOptions.items[index] as any const item = toolbarOptions.items[index] as any
if (item.options) { if (item.options) {
item.options.items = items item.options.items = items
grid.option('toolbar', toolbarOptions) component.option('toolbar', toolbarOptions)
}
}
}
} 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)
}
} }
} }
} }
@ -140,34 +120,23 @@ const fitColumns = (grid: GridInstance) => {
} }
const clearGridFilter = (grid: GridInstance) => { const clearGridFilter = (grid: GridInstance) => {
if (isDataGrid(grid)) { ;(grid as any).clearFilter?.()
grid.clearFilter()
} else if (isTreeList(grid)) {
grid.clearFilter()
}
} }
const setFilterPanelVisible = (grid: GridInstance, visible: boolean) => { const setFilterPanelVisible = (grid: GridInstance, visible: boolean) => {
if (isDataGrid(grid)) { ;(grid as any).option('filterPanel', { visible })
grid.option('filterPanel', { visible })
} else if (isTreeList(grid)) {
grid.option('filterPanel', { visible })
}
} }
const setFilterValue = (grid: GridInstance, value: any) => { const setFilterValue = (grid: GridInstance, value: any) => {
if (isDataGrid(grid)) { ;(grid as any).option('filterValue', value)
grid.option('filterValue', value)
} else if (isTreeList(grid)) {
grid.option('filterValue', value)
}
} }
const resetGridState = (grid: GridInstance) => { const resetGridState = (grid: GridInstance) => {
if (isDataGrid(grid)) { if (supportsState(grid)) {
grid.state(null)
} else if (isTreeList(grid)) {
grid.state(null) grid.state(null)
} else if (isCardView(grid)) {
grid.option('filterValue', undefined)
grid.option('selectedCardKeys', [])
} }
} }
@ -305,6 +274,7 @@ const useFilters = ({
| MutableRefObject<PivotGridRef | undefined> | MutableRefObject<PivotGridRef | undefined>
| MutableRefObject<TreeListRef<any, any> | undefined> | MutableRefObject<TreeListRef<any, any> | undefined>
| MutableRefObject<GanttRef | undefined> | MutableRefObject<GanttRef | undefined>
| MutableRefObject<CardViewRef | undefined>
listFormCode: string listFormCode: string
saveGridState?: (state: any) => Promise<void> saveGridState?: (state: any) => Promise<void>
}): { }): {

View file

@ -68,6 +68,40 @@ const cellTemplateMultiValue = (
// Hover preview overlay — singleton, tüm grid hücreleri tarafından paylaşılır // Hover preview overlay — singleton, tüm grid hücreleri tarafından paylaşılır
let __imgPreviewEl: HTMLDivElement | null = null let __imgPreviewEl: HTMLDivElement | null = null
let __imgPreviewTarget: HTMLElement | null = null
let __imgPreviewListenersBound = false
function hideImgPreview() {
__imgPreviewTarget = null
if (__imgPreviewEl) {
__imgPreviewEl.style.opacity = '0'
__imgPreviewEl.style.display = 'none'
}
}
function bindImgPreviewDismissListeners() {
if (__imgPreviewListenersBound) return
__imgPreviewListenersBound = true
document.addEventListener(
'pointermove',
(event) => {
const target = event.target as Node | null
if (
__imgPreviewTarget &&
(!__imgPreviewTarget.isConnected || !target || !__imgPreviewTarget.contains(target))
) {
hideImgPreview()
}
},
{ passive: true },
)
document.addEventListener('scroll', hideImgPreview, true)
window.addEventListener('blur', hideImgPreview)
document.addEventListener('visibilitychange', () => {
if (document.hidden) hideImgPreview()
})
}
function getImgPreview(): HTMLDivElement { function getImgPreview(): HTMLDivElement {
if (!__imgPreviewEl) { if (!__imgPreviewEl) {
@ -97,12 +131,14 @@ function getImgPreview(): HTMLDivElement {
el.appendChild(img) el.appendChild(img)
document.body.appendChild(el) document.body.appendChild(el)
__imgPreviewEl = el __imgPreviewEl = el
bindImgPreviewDismissListeners()
} }
return __imgPreviewEl return __imgPreviewEl
} }
function showImgPreview(src: string, e: MouseEvent) { function showImgPreview(src: string, e: MouseEvent) {
const el = getImgPreview() const el = getImgPreview()
__imgPreviewTarget = e.currentTarget instanceof HTMLElement ? e.currentTarget : null
const imgEl = el.querySelector('img') as HTMLImageElement const imgEl = el.querySelector('img') as HTMLImageElement
imgEl.onerror = () => { imgEl.onerror = () => {
imgEl.onerror = null imgEl.onerror = null
@ -132,13 +168,6 @@ function showImgPreview(src: string, e: MouseEvent) {
el.style.opacity = '1' el.style.opacity = '1'
} }
function hideImgPreview() {
if (__imgPreviewEl) {
__imgPreviewEl.style.opacity = '0'
__imgPreviewEl.style.display = 'none'
}
}
const cellTemplateImage = ( const cellTemplateImage = (
cellElement: HTMLElement, cellElement: HTMLElement,
cellInfo: DataGridTypes.ColumnCellTemplateData<any, any>, cellInfo: DataGridTypes.ColumnCellTemplateData<any, any>,
@ -166,7 +195,10 @@ const cellTemplateImage = (
img.addEventListener('mouseenter', (e) => showImgPreview(url, e as MouseEvent)) img.addEventListener('mouseenter', (e) => showImgPreview(url, e as MouseEvent))
img.addEventListener('mousemove', (e) => showImgPreview(url, e as MouseEvent)) img.addEventListener('mousemove', (e) => showImgPreview(url, e as MouseEvent))
img.addEventListener('mouseleave', hideImgPreview) img.addEventListener('mouseleave', hideImgPreview)
img.addEventListener('click', () => openImageSource(url)) img.addEventListener('click', () => {
hideImgPreview()
openImageSource(url)
})
cellElement.appendChild(img) cellElement.appendChild(img)
}) })