Listeleri mükemmelleştirme ikon vs.

This commit is contained in:
Sedat ÖZTÜRK 2025-09-29 11:33:51 +03:00
parent 9ea283712e
commit 1d36fc8225
7 changed files with 151 additions and 27 deletions

View file

@ -22763,7 +22763,7 @@
"accountHolder": "Özlem Öztürk", "accountHolder": "Özlem Öztürk",
"branch": "03663 / Enpara", "branch": "03663 / Enpara",
"accountNumber": "73941177", "accountNumber": "73941177",
"iban": "TR65 0011 1000 0000 0073 9411 77" "iban": "TR11 0015 7000 0000 0073 9411 77"
}, },
"workHour": { "workHour": {
"weekday": "Public.contact.workHours.weekday", "weekday": "Public.contact.workHours.weekday",

View file

@ -806,6 +806,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
var sql = $@" var sql = $@"
SELECT {string.Join(", ", selectParts)} SELECT {string.Join(", ", selectParts)}
FROM [{listform.SelectCommand}] FROM [{listform.SelectCommand}]
{GetWhereString()}
GROUP BY {argumentExpression}"; GROUP BY {argumentExpression}";
return sql; return sql;

View file

@ -288,11 +288,23 @@ const useListFormColumns = ({
} }
if (hasUpdate) { if (hasUpdate) {
column.buttons.push('edit') // column.buttons.push('edit')
column.buttons.push({
icon: 'edit',
hint: translate('::Edit'),
name: 'edit',
text: translate('::Edit'),
})
} }
if (hasDelete) { if (hasDelete) {
column.buttons.push('delete') // column.buttons.push('delete')
column.buttons.push({
icon: 'trash',
hint: translate('::Delete'),
name: 'delete',
text: translate('::Delete'),
})
} }
gridDto.gridOptions.commandColumnDto.forEach((action) => { gridDto.gridOptions.commandColumnDto.forEach((action) => {

View file

@ -4,12 +4,12 @@ import { captionize } from 'devextreme/core/utils/inflector'
import { useListFormCustomDataSource } from '@/shared/useListFormCustomDataSource' import { useListFormCustomDataSource } from '@/shared/useListFormCustomDataSource'
import { Button, Pagination, Select } from '@/components/ui' import { Button, Pagination, Select } from '@/components/ui'
import classNames from 'classnames' import classNames from 'classnames'
import { FaSearch } from 'react-icons/fa' import { FaCog, 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'
import FormButtons from '../form/FormButtons' import FormButtons from '../form/FormButtons'
import { Link, useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import CustomStore from 'devextreme/data/custom_store' import CustomStore from 'devextreme/data/custom_store'
import { PermissionResults, SimpleItemWithColData } from '../form/types' import { PermissionResults, SimpleItemWithColData } from '../form/types'
@ -20,6 +20,7 @@ import { Container, Loading } from '@/components/shared'
import WidgetGroup from '@/components/common/WidgetGroup' import WidgetGroup from '@/components/common/WidgetGroup'
import { GridExtraFilterState } from './Utils' import { GridExtraFilterState } from './Utils'
import { useStoreActions, useStoreState } from '@/store/store' import { useStoreActions, useStoreState } from '@/store/store'
import { usePWA } from '@/utils/hooks/usePWA'
const CardItem = ({ const CardItem = ({
isSubForm, isSubForm,
@ -41,8 +42,9 @@ const CardItem = ({
const [formData, setFormData] = useState(row) const [formData, setFormData] = useState(row)
const refForm = useRef<FormDx>(null) const refForm = useRef<FormDx>(null)
const navigate = useNavigate() const navigate = useNavigate()
const { checkPermission } = usePermission()
const { translate } = useLocalization() const { translate } = useLocalization()
const { checkPermission } = usePermission()
const isPwaMode = usePWA()
const keyField = gridDto.gridOptions.keyFieldName const keyField = gridDto.gridOptions.keyFieldName
const rowId = row[keyField!] const rowId = row[keyField!]
@ -204,6 +206,8 @@ const Card = (props: CardProps) => {
const [searchText, setSearchText] = useState('') const [searchText, setSearchText] = useState('')
const [prevValue, setPrevValue] = useState('') const [prevValue, setPrevValue] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const { checkPermission } = usePermission()
const isPwaMode = usePWA()
const [extraFilters, setExtraFilters] = useState<GridExtraFilterState[]>([]) const [extraFilters, setExtraFilters] = useState<GridExtraFilterState[]>([])
const { states } = useStoreState((state) => state.base.lists) const { states } = useStoreState((state) => state.base.lists)
@ -355,7 +359,7 @@ const Card = (props: CardProps) => {
<WidgetGroup widgetGroups={gridDto.widgets || []} /> <WidgetGroup widgetGroups={gridDto.widgets || []} />
<Container> <Container>
<div className="p-1 bg-white dark:bg-neutral-800 dark:border-neutral-700 "> <div className="bg-white dark:bg-neutral-800 dark:border-neutral-700 ">
<div className="flex justify-end items-center"> <div className="flex justify-end items-center">
<div className="relative py-1 flex gap-1 border-b-1"> <div className="relative py-1 flex gap-1 border-b-1">
<FaSearch className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 text-sm" /> <FaSearch className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 text-sm" />
@ -444,6 +448,26 @@ const Card = (props: CardProps) => {
> >
5 5
</Button> </Button>
{checkPermission(gridDto?.gridOptions.permissionDto.u) && (
<Button
size="xs"
variant={'default'}
className="text-sm"
onClick={() => {
window.open(
ROUTES_ENUM.protected.saas.listFormManagement.edit.replace(
':listFormCode',
listFormCode,
),
isPwaMode ? '_self' : '_blank',
)
}}
title="Form Manager"
>
<FaCog className="w-3 h-3" />
</Button>
)}
</div> </div>
</div> </div>

View file

@ -4,7 +4,7 @@ import { Container } from '@/components/shared'
import { DX_CLASSNAMES } from '@/constants/app.constant' import { DX_CLASSNAMES } from '@/constants/app.constant'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import DxChart from 'devextreme-react/chart' import DxChart from 'devextreme-react/chart'
import { useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { useParams, useSearchParams } from 'react-router-dom' import { useParams, useSearchParams } from 'react-router-dom'
import { useListFormCustomDataSource } from '@/shared/useListFormCustomDataSource' import { useListFormCustomDataSource } from '@/shared/useListFormCustomDataSource'
@ -13,7 +13,7 @@ import { usePermission } from '@/utils/hooks/usePermission'
import { Button } from '@/components/ui' import { Button } from '@/components/ui'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { usePWA } from '@/utils/hooks/usePWA' import { usePWA } from '@/utils/hooks/usePWA'
import { FaInfoCircle, FaSyncAlt } from 'react-icons/fa' import { FaCog, FaSearch, FaSyncAlt } from 'react-icons/fa'
import { buildSeriesDto } from './Utils' import { buildSeriesDto } from './Utils'
interface ChartProps extends CommonProps, Meta { interface ChartProps extends CommonProps, Meta {
@ -39,13 +39,19 @@ const Chart = (props: ChartProps) => {
const params = useParams() const params = useParams()
const _listFormCode = props?.listFormCode ?? params?.listFormCode ?? '' const _listFormCode = props?.listFormCode ?? params?.listFormCode ?? ''
const [searchText, setSearchText] = useState('')
const [prevValue, setPrevValue] = useState('')
const [urlSearchParams, setUrlSearchParams] = useState<URLSearchParams>(
searchParams ? new URLSearchParams(searchParams) : new URLSearchParams(),
)
useEffect(() => { useEffect(() => {
if (!gridDto) return if (!gridDto) return
const dataSource = createSelectDataSource( const dataSource = createSelectDataSource(
gridDto.gridOptions, gridDto.gridOptions,
listFormCode, listFormCode,
searchParams, urlSearchParams,
[], [],
true, true,
) )
@ -94,7 +100,53 @@ const Chart = (props: ChartProps) => {
} }
setChartOptions(options) setChartOptions(options)
}, [gridDto, searchParams]) }, [gridDto, searchParams, urlSearchParams])
const onFilter = useCallback(
(value?: string) => {
const text = value !== undefined ? value.trim() : searchText.trim()
if (!gridDto?.columnFormats) return
const newParams = new URLSearchParams(urlSearchParams.toString())
if (!text) {
newParams.delete('filter')
setUrlSearchParams(newParams)
return
}
const merged = gridDto.columnFormats
.filter(
(col) =>
col.dataType === 'string' &&
col.visible &&
col.width &&
col.allowSearch &&
col.width > 0,
)
.map((col) => [col.fieldName, 'contains', text])
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, 'or', f]
}, null as any)
}
if (filter) {
newParams.set('filter', JSON.stringify(filter))
} else {
newParams.delete('filter')
}
setUrlSearchParams(newParams)
},
[gridDto, urlSearchParams, searchText],
)
return ( return (
<Container className={DX_CLASSNAMES}> <Container className={DX_CLASSNAMES}>
@ -109,6 +161,32 @@ const Chart = (props: ChartProps) => {
<div className="p-1 bg-white dark:bg-neutral-800 dark:border-neutral-700 h-full"> <div className="p-1 bg-white dark:bg-neutral-800 dark:border-neutral-700 h-full">
<div className="flex justify-end items-center h-full"> <div className="flex justify-end items-center h-full">
<div className="relative pb-1 flex gap-1 border-b-1"> <div className="relative pb-1 flex gap-1 border-b-1">
<FaSearch className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 text-sm" />
<input
type="text"
placeholder="Search..."
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
onFilter(e.currentTarget.value)
setPrevValue(e.currentTarget.value.trim()) // Enter ile tetiklenirse güncelle
}
}}
onBlur={(e) => {
const newValue = e.currentTarget.value.trim()
// 1. Değer değişmemişse => hiçbir şey yapma
if (newValue === prevValue) return
// 2. Yeni değer boş, ama eskiden değer vardı => filtre temizle
// 3. Yeni değer dolu ve eskisinden farklı => filtre uygula
onFilter(newValue)
setPrevValue(newValue)
}}
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 <Button
size="xs" size="xs"
variant={'default'} variant={'default'}
@ -116,7 +194,7 @@ const Chart = (props: ChartProps) => {
onClick={async () => { onClick={async () => {
await props.refreshGridDto() await props.refreshGridDto()
}} }}
title="Reset Grid State" title="Refresh Data"
> >
<FaSyncAlt className="w-3 h-3" /> <FaSyncAlt className="w-3 h-3" />
</Button> </Button>
@ -136,7 +214,7 @@ const Chart = (props: ChartProps) => {
}} }}
title="Form Manager" title="Form Manager"
> >
<FaInfoCircle className="w-3 h-3" /> <FaCog className="w-3 h-3" />
</Button> </Button>
)} )}
</div> </div>

View file

@ -34,7 +34,15 @@ import {
import { useFilters } from './useFilters' import { useFilters } from './useFilters'
import WidgetGroup from '@/components/common/WidgetGroup' import WidgetGroup from '@/components/common/WidgetGroup'
import { Button } from '@/components/ui' import { Button } from '@/components/ui'
import { FaInfoCircle, FaSyncAlt, FaTrash, FaTrashAlt } from 'react-icons/fa' import {
FaCog,
FaInfoCircle,
FaSyncAlt,
FaTimes,
FaTrash,
FaTrashAlt,
FaUndo,
} from 'react-icons/fa'
import { usePermission } from '@/utils/hooks/usePermission' import { usePermission } from '@/utils/hooks/usePermission'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { usePWA } from '@/utils/hooks/usePWA' import { usePWA } from '@/utils/hooks/usePWA'
@ -317,7 +325,7 @@ const Pivot = (props: PivotProps) => {
onClick={clearPivotFilters} onClick={clearPivotFilters}
title="Remove Filter" title="Remove Filter"
> >
<FaTrash className="w-3 h-3" /> <FaTimes className="w-3 h-3" />
</Button> </Button>
<Button <Button
@ -327,7 +335,7 @@ const Pivot = (props: PivotProps) => {
onClick={resetPivotGridState} onClick={resetPivotGridState}
title="Reset Grid State" title="Reset Grid State"
> >
<FaSyncAlt className="w-3 h-3" /> <FaUndo className="w-3 h-3" />
</Button> </Button>
{checkPermission(gridDto?.gridOptions.permissionDto.u) && ( {checkPermission(gridDto?.gridOptions.permissionDto.u) && (
@ -346,7 +354,7 @@ const Pivot = (props: PivotProps) => {
}} }}
title="Form Manager" title="Form Manager"
> >
<FaInfoCircle className="w-3 h-3" /> <FaCog className="w-3 h-3" />
</Button> </Button>
)} )}
</div> </div>

View file

@ -129,15 +129,7 @@ const useFilters = ({
menus.push({ menus.push({
text: translate('::ListForms.ListForm.ResetGridState'), text: translate('::ListForms.ListForm.ResetGridState'),
id: 'resetGridState', id: 'resetGridState',
icon: 'refresh', icon: 'revert',
})
}
if (checkPermission(gridDto?.gridOptions.permissionDto.u)) {
menus.push({
text: translate('::ListForms.ListForm.Manage'),
id: 'openManage',
icon: 'preferences',
}) })
} }
@ -149,6 +141,14 @@ const useFilters = ({
}) })
} }
if (checkPermission(gridDto?.gridOptions.permissionDto.u)) {
menus.push({
text: translate('::ListForms.ListForm.Manage'),
id: 'openManage',
icon: 'preferences',
})
}
if (checkPermission(gridDto?.gridOptions.permissionDto.e)) { if (checkPermission(gridDto?.gridOptions.permissionDto.e)) {
items.push({ items.push({
locateInMenu: 'auto', locateInMenu: 'auto',
@ -228,6 +228,7 @@ const useFilters = ({
{ {
text: translate('::ListForms.ListForm.GridMenu'), text: translate('::ListForms.ListForm.GridMenu'),
items: menus, items: menus,
icon: 'overflow',
}, },
], ],
}, },