Chart düzenlemesi
This commit is contained in:
parent
ee0b8d8421
commit
55aaad3d31
7 changed files with 557 additions and 318 deletions
|
|
@ -684,7 +684,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
|
||||||
var sqlSub = new StringBuilder();
|
var sqlSub = new StringBuilder();
|
||||||
sqlSub.Append("SELECT ");
|
sqlSub.Append("SELECT ");
|
||||||
sqlSub.Append(string.Join(',', GroupTuples.Select((a) => $"{a.Field} AS \"{a.SelectExpr}\"")));
|
sqlSub.Append(string.Join(',', GroupTuples.Select((a) => $"{a.Field} AS \"{a.SelectExpr}\"")));
|
||||||
sqlSub.Append(string.Join(',', GroupSummaryTuples.Select((a) => $",{a.SummaryType}({a.Field}) AS \"{a.SelectExpr}\"")));
|
sqlSub.Append(string.Join("", GroupSummaryTuples.Select((a) => $",{a.SummaryType}({a.Field}) AS \"{a.SelectExpr}\"")));
|
||||||
sqlSub.Append(",COUNT(1) AS \"Summary_Count\"");
|
sqlSub.Append(",COUNT(1) AS \"Summary_Count\"");
|
||||||
sqlSub.Append($" FROM \"{From}\" AS \"{TableName}\"");
|
sqlSub.Append($" FROM \"{From}\" AS \"{TableName}\"");
|
||||||
sqlSub.Append(GetJoinString());
|
sqlSub.Append(GetJoinString());
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import {
|
||||||
GridsEditMode,
|
GridsEditMode,
|
||||||
GridsEditRefreshMode,
|
GridsEditRefreshMode,
|
||||||
NewRowPosition,
|
NewRowPosition,
|
||||||
PagerDisplayMode,
|
|
||||||
SelectionColumnDisplayMode,
|
SelectionColumnDisplayMode,
|
||||||
StartEditAction,
|
StartEditAction,
|
||||||
StateStoreType,
|
StateStoreType,
|
||||||
|
|
@ -19,7 +18,6 @@ import {
|
||||||
import { FormItemComponent } from 'devextreme/ui/form'
|
import { FormItemComponent } from 'devextreme/ui/form'
|
||||||
import { AuditedEntityDto } from '../abp'
|
import { AuditedEntityDto } from '../abp'
|
||||||
import { EditorType2, RowMode } from '../../views/form/types'
|
import { EditorType2, RowMode } from '../../views/form/types'
|
||||||
import { bool } from 'yup'
|
|
||||||
import {
|
import {
|
||||||
ChartCommonDto,
|
ChartCommonDto,
|
||||||
ChartAdaptivelayoutDto,
|
ChartAdaptivelayoutDto,
|
||||||
|
|
@ -45,6 +43,7 @@ import {
|
||||||
} from '../admin/charts/models'
|
} from '../admin/charts/models'
|
||||||
import { ListViewLayoutType } from '@/views/admin/listForm/edit/types'
|
import { ListViewLayoutType } from '@/views/admin/listForm/edit/types'
|
||||||
import { SeriesType } from 'devextreme/common/charts'
|
import { SeriesType } from 'devextreme/common/charts'
|
||||||
|
import { PagerDisplayMode } from 'devextreme/common/grids'
|
||||||
|
|
||||||
//1
|
//1
|
||||||
export interface SelectListItem {
|
export interface SelectListItem {
|
||||||
|
|
|
||||||
|
|
@ -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 { useCallback, useEffect, useRef, 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 { GridDto } from '@/proxy/form/models'
|
import { GridDto } from '@/proxy/form/models'
|
||||||
|
|
@ -17,7 +17,7 @@ import { buildSeriesDto } from './Utils'
|
||||||
import { ChartSeriesDto } from '@/proxy/admin/charts/models'
|
import { ChartSeriesDto } from '@/proxy/admin/charts/models'
|
||||||
import { SelectBoxOption } from '@/types/shared'
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
import ChartSeriesDialog from './ChartSeriesDialog'
|
import ChartDrawer from './ChartDrawer'
|
||||||
import { getListFormFields } from '@/services/admin/list-form-field.service'
|
import { getListFormFields } from '@/services/admin/list-form-field.service'
|
||||||
import { groupBy } from 'lodash'
|
import { groupBy } from 'lodash'
|
||||||
import { ListFormJsonRowDto } from '@/proxy/admin/list-form/models'
|
import { ListFormJsonRowDto } from '@/proxy/admin/list-form/models'
|
||||||
|
|
@ -48,7 +48,7 @@ const Chart = (props: ChartProps) => {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
const { checkPermission } = usePermission()
|
const { checkPermission } = usePermission()
|
||||||
const isPwaMode = usePWA()
|
const isPwaMode = usePWA()
|
||||||
const initialized = useRef(false)
|
const [initialized, setInitialized] = useState(false)
|
||||||
|
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
const [chartOptions, setChartOptions] = useState<any>()
|
const [chartOptions, setChartOptions] = useState<any>()
|
||||||
|
|
@ -57,8 +57,14 @@ const Chart = (props: ChartProps) => {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const _listFormCode = props?.listFormCode ?? params?.listFormCode ?? ''
|
const _listFormCode = props?.listFormCode ?? params?.listFormCode ?? ''
|
||||||
|
|
||||||
const [openDialog, setOpenDialog] = useState(false)
|
const [openDrawer, setOpenDrawer] = useState(false)
|
||||||
const [fieldList, setFieldList] = useState<SelectBoxOption[]>([])
|
const [fieldList, setFieldList] = useState<SelectBoxOption[]>([])
|
||||||
|
|
||||||
|
// Ana state - Chart bu state'e göre render edilir
|
||||||
|
const [currentSeries, setCurrentSeries] = useState<ChartSeriesDto[]>([])
|
||||||
|
|
||||||
|
// Veritabanından gelen orijinal seriler (kaydetme işlemi için)
|
||||||
|
const [savedSeries, setSavedSeries] = useState<ChartSeriesDto[]>([])
|
||||||
|
|
||||||
const [searchText, setSearchText] = useState('')
|
const [searchText, setSearchText] = useState('')
|
||||||
const [prevValue, setPrevValue] = useState('')
|
const [prevValue, setPrevValue] = useState('')
|
||||||
|
|
@ -68,27 +74,35 @@ const Chart = (props: ChartProps) => {
|
||||||
|
|
||||||
const [allSeries, setAllSeries] = useState<ChartSeriesDto[]>([])
|
const [allSeries, setAllSeries] = useState<ChartSeriesDto[]>([])
|
||||||
|
|
||||||
const [userSeries, setUserSeries] = useState<ChartSeriesDto[]>([])
|
|
||||||
const [oldSeries, setOldSeries] = useState<ChartSeriesDto[]>([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (gridDto && !initialized.current) {
|
if (gridDto) {
|
||||||
const initialSeries = gridDto.gridOptions.seriesDto.map((s, index) => ({ ...s, index }))
|
const initialSeries = gridDto.gridOptions.seriesDto.map((s, index) => ({ ...s, index }))
|
||||||
|
const userSeriesData = initialSeries.filter((s) => s.userId === userName)
|
||||||
|
|
||||||
setAllSeries(initialSeries)
|
setAllSeries(initialSeries)
|
||||||
setUserSeries(initialSeries.filter((s) => s.userId === userName))
|
|
||||||
setOldSeries(initialSeries.filter((s) => s.userId === userName))
|
// Kullanıcının serisi varsa onu kullan, yoksa tüm serileri kullan
|
||||||
|
const seriesToUse = userSeriesData.length > 0 ? userSeriesData : initialSeries
|
||||||
initialized.current = true
|
|
||||||
|
// Sadece ilk yüklemede VEYA refresh sonrası güncelle
|
||||||
|
if (!initialized) {
|
||||||
|
setCurrentSeries(seriesToUse)
|
||||||
|
setSavedSeries(seriesToUse)
|
||||||
|
setInitialized(true)
|
||||||
|
} else {
|
||||||
|
// Refresh sonrası - yeni kaydedilen serileri kullan
|
||||||
|
setCurrentSeries(seriesToUse)
|
||||||
|
setSavedSeries(seriesToUse)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [gridDto])
|
}, [gridDto, userName])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!gridDto) return
|
if (!gridDto) return
|
||||||
if (!allSeries) return
|
if (!initialized) return
|
||||||
if (!initialized.current) return
|
|
||||||
|
|
||||||
const seriesDto = userSeries.length > 0 ? userSeries : allSeries.length > 0 ? allSeries : []
|
// Chart her zaman currentSeries'e göre render edilir
|
||||||
|
const seriesDto = currentSeries
|
||||||
|
|
||||||
const gridOptions = {
|
const gridOptions = {
|
||||||
...gridDto.gridOptions,
|
...gridDto.gridOptions,
|
||||||
|
|
@ -116,8 +130,15 @@ const Chart = (props: ChartProps) => {
|
||||||
title: gridDto.gridOptions.titleDto,
|
title: gridDto.gridOptions.titleDto,
|
||||||
size: gridDto.gridOptions.sizeDto?.useSize
|
size: gridDto.gridOptions.sizeDto?.useSize
|
||||||
? { width: gridDto.gridOptions.sizeDto.width, height: gridDto.gridOptions.sizeDto.height }
|
? { width: gridDto.gridOptions.sizeDto.width, height: gridDto.gridOptions.sizeDto.height }
|
||||||
: { width: '100%', height: window.innerHeight - 210 },
|
: {
|
||||||
legend: gridDto.gridOptions.legendDto,
|
width: openDrawer ? window.innerWidth - 550 : '100%',
|
||||||
|
height: window.innerHeight - 210
|
||||||
|
},
|
||||||
|
legend: gridDto.gridOptions.legendDto || {
|
||||||
|
visible: true,
|
||||||
|
verticalAlignment: 'bottom',
|
||||||
|
horizontalAlignment: 'center',
|
||||||
|
},
|
||||||
margin: gridDto.gridOptions.marginDto,
|
margin: gridDto.gridOptions.marginDto,
|
||||||
adaptiveLayout: gridDto.gridOptions.adaptivelayoutDto,
|
adaptiveLayout: gridDto.gridOptions.adaptivelayoutDto,
|
||||||
defaultPane: gridDto.gridOptions.commonDto?.defaultPane,
|
defaultPane: gridDto.gridOptions.commonDto?.defaultPane,
|
||||||
|
|
@ -129,7 +150,11 @@ const Chart = (props: ChartProps) => {
|
||||||
crosshair: gridDto.gridOptions.crosshairDto,
|
crosshair: gridDto.gridOptions.crosshairDto,
|
||||||
argumentAxis: gridDto.gridOptions.argumentAxisDto,
|
argumentAxis: gridDto.gridOptions.argumentAxisDto,
|
||||||
valueAxis: gridDto.gridOptions.valueAxisDto,
|
valueAxis: gridDto.gridOptions.valueAxisDto,
|
||||||
tooltip: gridDto.gridOptions.tooltipDto,
|
tooltip: gridDto.gridOptions.tooltipDto || {
|
||||||
|
enabled: true,
|
||||||
|
shared: false,
|
||||||
|
format: 'decimal',
|
||||||
|
},
|
||||||
|
|
||||||
series: buildSeriesDto(seriesDto),
|
series: buildSeriesDto(seriesDto),
|
||||||
|
|
||||||
|
|
@ -146,7 +171,7 @@ const Chart = (props: ChartProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setChartOptions(options)
|
setChartOptions(options)
|
||||||
}, [gridDto, allSeries, initialized.current, searchParams, urlSearchParams])
|
}, [gridDto, currentSeries, initialized, searchParams, urlSearchParams, openDrawer])
|
||||||
|
|
||||||
const onFilter = useCallback(
|
const onFilter = useCallback(
|
||||||
(value?: string) => {
|
(value?: string) => {
|
||||||
|
|
@ -220,12 +245,23 @@ const Chart = (props: ChartProps) => {
|
||||||
if (props.listFormCode) getFields()
|
if (props.listFormCode) getFields()
|
||||||
}, [props.listFormCode])
|
}, [props.listFormCode])
|
||||||
|
|
||||||
|
const handlePreviewChange = (series: ChartSeriesDto[]) => {
|
||||||
|
// Preview değişikliklerini anında chart'a yansıt
|
||||||
|
setCurrentSeries(series)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDrawerClose = () => {
|
||||||
|
setOpenDrawer(false)
|
||||||
|
// İptal - kaydedilmiş serilere geri dön
|
||||||
|
setCurrentSeries(savedSeries)
|
||||||
|
}
|
||||||
|
|
||||||
const onSave = async (newSeries: ChartSeriesDto[]) => {
|
const onSave = async (newSeries: ChartSeriesDto[]) => {
|
||||||
// 1. Silinecek serileri bul (oldSeries var ama newSeries yok)
|
// 1. Silinecek serileri bul (savedSeries var ama newSeries yok)
|
||||||
const toDelete = oldSeries.filter((old) => !newSeries.some((s) => s.index === old.index))
|
const toDelete = savedSeries.filter((old: ChartSeriesDto) => !newSeries.some((s) => s.index === old.index))
|
||||||
|
|
||||||
// Index kaymasını önlemek için büyükten küçüğe sırala
|
// Index kaymasını önlemek için büyükten küçüğe sırala
|
||||||
toDelete.sort((a, b) => b.index - a.index)
|
toDelete.sort((a: ChartSeriesDto, b: ChartSeriesDto) => b.index - a.index)
|
||||||
|
|
||||||
for (const old of toDelete) {
|
for (const old of toDelete) {
|
||||||
await deleteListFormJsonRow(id, ListFormEditTabs.ChartSeries.GeneralJsonRow, old.index)
|
await deleteListFormJsonRow(id, ListFormEditTabs.ChartSeries.GeneralJsonRow, old.index)
|
||||||
|
|
@ -246,10 +282,15 @@ const Chart = (props: ChartProps) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Yeniden yükle
|
// 3. Yeniden yükle (veritabanından fresh data)
|
||||||
if (props.refreshGridDto) {
|
if (props.refreshGridDto) {
|
||||||
initialized.current = false
|
setInitialized(false)
|
||||||
await props.refreshGridDto()
|
await props.refreshGridDto()
|
||||||
|
// refreshGridDto tamamlandıktan sonra yeni state'ler otomatik setlenecek
|
||||||
|
} else {
|
||||||
|
// refreshGridDto yoksa manuel güncelle
|
||||||
|
setSavedSeries(newSeries)
|
||||||
|
setCurrentSeries(newSeries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,7 +304,7 @@ const Chart = (props: ChartProps) => {
|
||||||
></Helmet>
|
></Helmet>
|
||||||
)}
|
)}
|
||||||
{_listFormCode && chartOptions && (
|
{_listFormCode && chartOptions && (
|
||||||
<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 relative">
|
||||||
<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" />
|
<FaSearch className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 text-sm" />
|
||||||
|
|
@ -296,7 +337,7 @@ const Chart = (props: ChartProps) => {
|
||||||
variant={'default'}
|
variant={'default'}
|
||||||
className="text-sm"
|
className="text-sm"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
initialized.current = false
|
setInitialized(false)
|
||||||
await refreshGridDto?.()
|
await refreshGridDto?.()
|
||||||
}}
|
}}
|
||||||
title="Refresh Data"
|
title="Refresh Data"
|
||||||
|
|
@ -307,7 +348,7 @@ const Chart = (props: ChartProps) => {
|
||||||
size="xs"
|
size="xs"
|
||||||
variant="default"
|
variant="default"
|
||||||
className="text-sm"
|
className="text-sm"
|
||||||
onClick={() => setOpenDialog(true)}
|
onClick={() => setOpenDrawer(true)}
|
||||||
title="Series Özelleştir"
|
title="Series Özelleştir"
|
||||||
>
|
>
|
||||||
<FaCrosshairs className="w-3 h-3" />
|
<FaCrosshairs className="w-3 h-3" />
|
||||||
|
|
@ -334,14 +375,20 @@ const Chart = (props: ChartProps) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DxChart key={'DxChart' + _listFormCode} {...chartOptions}></DxChart>
|
<div
|
||||||
|
className={`transition-all duration-300 ${openDrawer ? 'mr-[500px]' : 'mr-0'}`}
|
||||||
|
style={{ width: openDrawer ? 'calc(100% - 500px)' : '100%' }}
|
||||||
|
>
|
||||||
|
<DxChart key={'DxChart' + _listFormCode} {...chartOptions}></DxChart>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ChartSeriesDialog
|
<ChartDrawer
|
||||||
open={openDialog}
|
open={openDrawer}
|
||||||
onClose={() => setOpenDialog(false)}
|
onClose={handleDrawerClose}
|
||||||
initialSeries={allSeries.filter((s) => s.userId === userName)}
|
initialSeries={currentSeries}
|
||||||
fieldList={fieldList}
|
fieldList={fieldList}
|
||||||
onSave={onSave}
|
onSave={onSave}
|
||||||
|
onPreviewChange={handlePreviewChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
368
ui/src/views/list/ChartDrawer.tsx
Normal file
368
ui/src/views/list/ChartDrawer.tsx
Normal file
|
|
@ -0,0 +1,368 @@
|
||||||
|
import { Button, FormContainer, Input, Notification, Select, toast } from '@/components/ui'
|
||||||
|
import { Field, FieldArray, Form, Formik, FieldProps } from 'formik'
|
||||||
|
import { FaMinus, FaPlus, FaTimes, FaSave } from 'react-icons/fa'
|
||||||
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
|
import { columnSummaryTypeListOptions } from '../admin/listForm/edit/options'
|
||||||
|
import { ChartSeriesDto } from '@/proxy/admin/charts/models'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
import { useStoreState } from '@/store/store'
|
||||||
|
import { object, array, string } from 'yup'
|
||||||
|
import { SummaryTypeEnum } from '@/proxy/form/models'
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
|
interface ChartDrawerProps {
|
||||||
|
open: boolean
|
||||||
|
onClose: () => void
|
||||||
|
initialSeries: ChartSeriesDto[]
|
||||||
|
fieldList: SelectBoxOption[]
|
||||||
|
onSave: (series: ChartSeriesDto[]) => void
|
||||||
|
onPreviewChange: (series: ChartSeriesDto[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = object().shape({
|
||||||
|
series: array().of(
|
||||||
|
object().shape({
|
||||||
|
name: string().required('Name Required'),
|
||||||
|
argumentField: string().required('Argument Field Required'),
|
||||||
|
valueField: string().required('Value Field Required'),
|
||||||
|
summaryType: string().required('Summary Type Required'),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
const ChartDrawer = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
initialSeries,
|
||||||
|
fieldList,
|
||||||
|
onSave,
|
||||||
|
onPreviewChange,
|
||||||
|
}: ChartDrawerProps) => {
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
const { userName } = useStoreState((s) => s.auth.user)
|
||||||
|
const [selectedSeriesIndex, setSelectedSeriesIndex] = useState<number>(-1)
|
||||||
|
|
||||||
|
// Chart type icons
|
||||||
|
const chartTypeIcons = [
|
||||||
|
{ type: 'line', label: 'Line', icon: '📈' },
|
||||||
|
{ type: 'bar', label: 'Bar', icon: '📊' },
|
||||||
|
{ type: 'area', label: 'Area', icon: '🏔️' },
|
||||||
|
{ type: 'scatter', label: 'Scatter', icon: '🔵' },
|
||||||
|
{ type: 'spline', label: 'Spline', icon: '〰️' },
|
||||||
|
{ type: 'stackedbar', label: 'Stacked Bar', icon: '📚' },
|
||||||
|
{ type: 'stackedarea', label: 'Stacked Area', icon: '⛰️' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const newSeriesValue = () => {
|
||||||
|
return {
|
||||||
|
index: -1,
|
||||||
|
type: 'line',
|
||||||
|
name: '',
|
||||||
|
argumentField: '',
|
||||||
|
valueField: '',
|
||||||
|
summaryType: SummaryTypeEnum.Sum,
|
||||||
|
axis: '',
|
||||||
|
barOverlapGroup: '',
|
||||||
|
barPadding: 0,
|
||||||
|
barWidth: 0,
|
||||||
|
color: '',
|
||||||
|
cornerRadius: 0,
|
||||||
|
dashStyle: 'solid',
|
||||||
|
ignoreEmptyPoints: false,
|
||||||
|
pane: '',
|
||||||
|
rangeValue1Field: '',
|
||||||
|
rangeValue2Field: '',
|
||||||
|
selectionMode: 'none',
|
||||||
|
showInLegend: true,
|
||||||
|
visible: true,
|
||||||
|
width: 2,
|
||||||
|
label: {
|
||||||
|
visible: true,
|
||||||
|
backgroundColor: '#f05b41',
|
||||||
|
customizeText: '',
|
||||||
|
format: 'decimal',
|
||||||
|
font: {
|
||||||
|
color: '#FFFFFF',
|
||||||
|
family: '"Segoe UI", "Helvetica Neue", "Trebuchet MS", Verdana, sans-serif',
|
||||||
|
size: 12,
|
||||||
|
weight: 400,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
userId: userName ?? '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!open) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Drawer */}
|
||||||
|
<div
|
||||||
|
className={`fixed right-0 top-0 h-full w-[500px] bg-white shadow-2xl z-50 transform transition-transform duration-300 border-l-2 border-gray-300 ${
|
||||||
|
open ? 'translate-x-0' : 'translate-x-full'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Formik
|
||||||
|
enableReinitialize
|
||||||
|
initialValues={{
|
||||||
|
series: initialSeries && initialSeries.length > 0 ? initialSeries : [newSeriesValue()],
|
||||||
|
}}
|
||||||
|
validationSchema={schema}
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
try {
|
||||||
|
await onSave(values.series)
|
||||||
|
toast.push(<Notification type="success">Chart kaydedildi</Notification>, {
|
||||||
|
placement: 'top-end',
|
||||||
|
})
|
||||||
|
onClose()
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="danger">
|
||||||
|
Hata
|
||||||
|
<code>{error}</code>
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ setFieldValue, values, isSubmitting }) => {
|
||||||
|
// Preview'ı her değişiklikte otomatik güncelle
|
||||||
|
useEffect(() => {
|
||||||
|
const validSeries = values.series.filter(
|
||||||
|
(s) => s.argumentField && s.valueField && s.summaryType,
|
||||||
|
)
|
||||||
|
if (validSeries.length > 0) {
|
||||||
|
onPreviewChange(validSeries)
|
||||||
|
}
|
||||||
|
}, [values.series])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form className="flex flex-col h-full">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between p-4 border-b bg-gray-50">
|
||||||
|
<h2 className="text-lg font-semibold flex items-center gap-2">
|
||||||
|
<span>📊</span>
|
||||||
|
Chart Series
|
||||||
|
</h2>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="p-2 hover:bg-gray-200 rounded-full transition-colors"
|
||||||
|
>
|
||||||
|
<FaTimes />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormContainer size="sm" className="flex flex-col flex-1 overflow-hidden">
|
||||||
|
{/* Add Series Button */}
|
||||||
|
<div className="p-3 border-b">
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setFieldValue('series', [...values.series, newSeriesValue()])
|
||||||
|
}}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center gap-2">
|
||||||
|
<FaPlus /> Yeni Seri Ekle
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scrollable Content */}
|
||||||
|
<div className="flex-1 overflow-y-auto p-3">
|
||||||
|
<FieldArray name="series">
|
||||||
|
{({ remove }) => (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{values.series.map((series, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="border rounded-lg p-3 bg-gray-50 hover:bg-gray-100 transition-colors"
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<span className="font-semibold text-sm">Seri #{index + 1}</span>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="xs"
|
||||||
|
variant="plain"
|
||||||
|
icon={<FaMinus />}
|
||||||
|
className="text-red-500 hover:bg-red-100"
|
||||||
|
onClick={() => {
|
||||||
|
remove(index)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Chart Type Selector */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="text-xs font-medium mb-1 block">Chart Type</label>
|
||||||
|
<Field name={`series[${index}].type`}>
|
||||||
|
{({ field, form }: FieldProps) => (
|
||||||
|
<div className="relative">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setSelectedSeriesIndex(
|
||||||
|
selectedSeriesIndex === index ? -1 : index,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="w-full px-3 py-2 text-left border rounded hover:bg-white flex items-center gap-2 transition-colors"
|
||||||
|
>
|
||||||
|
<span className="text-xl">
|
||||||
|
{chartTypeIcons.find((t) => t.type === field.value)
|
||||||
|
?.icon || '📊'}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{chartTypeIcons.find((t) => t.type === field.value)
|
||||||
|
?.label || field.value}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{selectedSeriesIndex === index && (
|
||||||
|
<div className="absolute z-50 mt-1 w-full bg-white border rounded-lg shadow-xl p-2">
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
{chartTypeIcons.map((chartType) => (
|
||||||
|
<button
|
||||||
|
key={chartType.type}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
form.setFieldValue(field.name, chartType.type)
|
||||||
|
setSelectedSeriesIndex(-1)
|
||||||
|
}}
|
||||||
|
className={`p-3 rounded-lg hover:bg-blue-50 flex flex-col items-center gap-1 transition-all ${
|
||||||
|
field.value === chartType.type
|
||||||
|
? 'bg-blue-100 ring-2 ring-blue-400'
|
||||||
|
: 'bg-gray-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="text-2xl">{chartType.icon}</span>
|
||||||
|
<span className="text-xs font-medium text-center">
|
||||||
|
{chartType.label}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Name */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="text-xs font-medium mb-1 block">Name</label>
|
||||||
|
<Field
|
||||||
|
size="sm"
|
||||||
|
name={`series[${index}].name`}
|
||||||
|
type="text"
|
||||||
|
component={Input}
|
||||||
|
placeholder="Series name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Argument Field */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="text-xs font-medium mb-1 block">
|
||||||
|
Argument Field (X-Axis)
|
||||||
|
</label>
|
||||||
|
<Field name={`series[${index}].argumentField`}>
|
||||||
|
{({ field, form }: FieldProps) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
isClearable={true}
|
||||||
|
options={fieldList}
|
||||||
|
value={fieldList?.find(
|
||||||
|
(option) => option.value === field.value,
|
||||||
|
)}
|
||||||
|
onChange={(option) => {
|
||||||
|
form.setFieldValue(field.name, option?.value)
|
||||||
|
}}
|
||||||
|
placeholder="Select field..."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Value Field */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="text-xs font-medium mb-1 block">
|
||||||
|
Value Field (Y-Axis)
|
||||||
|
</label>
|
||||||
|
<Field name={`series[${index}].valueField`}>
|
||||||
|
{({ field, form }: FieldProps) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
isClearable={true}
|
||||||
|
options={fieldList}
|
||||||
|
value={fieldList?.find(
|
||||||
|
(option) => option.value === field.value,
|
||||||
|
)}
|
||||||
|
onChange={(option) => {
|
||||||
|
form.setFieldValue(field.name, option?.value)
|
||||||
|
}}
|
||||||
|
placeholder="Select field..."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Summary Type */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="text-xs font-medium mb-1 block">
|
||||||
|
Summary Type
|
||||||
|
</label>
|
||||||
|
<Field name={`series[${index}].summaryType`}>
|
||||||
|
{({ field, form }: FieldProps) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
isClearable={true}
|
||||||
|
options={columnSummaryTypeListOptions}
|
||||||
|
value={columnSummaryTypeListOptions.find(
|
||||||
|
(option) => option.value === field.value,
|
||||||
|
)}
|
||||||
|
onChange={(option) => {
|
||||||
|
form.setFieldValue(field.name, option?.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</FieldArray>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div className="p-4 border-t bg-gray-50 flex gap-2">
|
||||||
|
<Button type="button" variant="plain" onClick={onClose} className="flex-1">
|
||||||
|
İptal
|
||||||
|
</Button>
|
||||||
|
<Button variant="solid" loading={isSubmitting} type="submit" className="flex-1">
|
||||||
|
<div className="flex items-center justify-center gap-2">
|
||||||
|
<FaSave />
|
||||||
|
{isSubmitting ? translate('::SavingWithThreeDot') : 'Kaydet'}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</FormContainer>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Formik>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChartDrawer
|
||||||
|
|
@ -1,279 +0,0 @@
|
||||||
import { Button, FormContainer, Input, Notification, Select, Dialog, toast } from '@/components/ui'
|
|
||||||
import { Field, FieldArray, Form, Formik, FieldProps } from 'formik'
|
|
||||||
import { FaMinus, FaPlus } from 'react-icons/fa'
|
|
||||||
import { SelectBoxOption } from '@/types/shared'
|
|
||||||
import {
|
|
||||||
chartSeriesTypeOptions,
|
|
||||||
columnSummaryTypeListOptions,
|
|
||||||
} from '../admin/listForm/edit/options'
|
|
||||||
import { ChartSeriesDto } from '@/proxy/admin/charts/models'
|
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
||||||
import { useStoreState } from '@/store/store'
|
|
||||||
import { object, array, string } from 'yup'
|
|
||||||
import { SummaryTypeEnum } from '@/proxy/form/models'
|
|
||||||
|
|
||||||
interface ChartSeriesDialogProps {
|
|
||||||
open: boolean
|
|
||||||
onClose: () => void
|
|
||||||
initialSeries: ChartSeriesDto[]
|
|
||||||
fieldList: SelectBoxOption[]
|
|
||||||
onSave: (series: ChartSeriesDto[]) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = object().shape({
|
|
||||||
series: array().of(
|
|
||||||
object().shape({
|
|
||||||
name: string().required('Name Required'),
|
|
||||||
argumentField: string().required('Argument Field Required'),
|
|
||||||
valueField: string().required('Value Field Required'),
|
|
||||||
summaryType: string().required('Summary Type Required'),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
const ChartSeriesDialog = ({
|
|
||||||
open,
|
|
||||||
onClose,
|
|
||||||
initialSeries,
|
|
||||||
fieldList,
|
|
||||||
onSave,
|
|
||||||
}: ChartSeriesDialogProps) => {
|
|
||||||
const { translate } = useLocalization()
|
|
||||||
|
|
||||||
// State UserId güncellemesi için
|
|
||||||
const { userName } = useStoreState((s) => s.auth.user)
|
|
||||||
|
|
||||||
const newSeriesValue = () => {
|
|
||||||
return {
|
|
||||||
index: -1,
|
|
||||||
type: 'line',
|
|
||||||
name: '',
|
|
||||||
argumentField: '',
|
|
||||||
valueField: '',
|
|
||||||
summaryType: SummaryTypeEnum.Sum,
|
|
||||||
axis: '',
|
|
||||||
barOverlapGroup: '',
|
|
||||||
barPadding: 0,
|
|
||||||
barWidth: 0,
|
|
||||||
color: '',
|
|
||||||
cornerRadius: 0,
|
|
||||||
dashStyle: 'solid',
|
|
||||||
ignoreEmptyPoints: false,
|
|
||||||
pane: '',
|
|
||||||
rangeValue1Field: '',
|
|
||||||
rangeValue2Field: '',
|
|
||||||
selectionMode: 'none',
|
|
||||||
showInLegend: true,
|
|
||||||
visible: true,
|
|
||||||
width: 2,
|
|
||||||
label: {
|
|
||||||
visible: true,
|
|
||||||
backgroundColor: '#f05b41',
|
|
||||||
customizeText: '',
|
|
||||||
format: 'decimal',
|
|
||||||
font: {
|
|
||||||
color: '#FFFFFF',
|
|
||||||
family: '"Segoe UI", "Helvetica Neue", "Trebuchet MS", Verdana, sans-serif',
|
|
||||||
size: 12,
|
|
||||||
weight: 400,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
userId: userName ?? '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog isOpen={open} onClose={onClose} width={1200}>
|
|
||||||
<div className="flex flex-col bg-white p-4 h-[600px]">
|
|
||||||
<Formik
|
|
||||||
enableReinitialize
|
|
||||||
initialValues={{
|
|
||||||
series: initialSeries && initialSeries.length > 0 ? initialSeries : [newSeriesValue()],
|
|
||||||
}}
|
|
||||||
validationSchema={schema}
|
|
||||||
onSubmit={(values, { setSubmitting }) => {
|
|
||||||
try {
|
|
||||||
onSave(values.series)
|
|
||||||
toast.push(<Notification type="success">{'Chart güncellendi'}</Notification>, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
onClose()
|
|
||||||
} catch (error: any) {
|
|
||||||
toast.push(
|
|
||||||
<Notification type="danger">
|
|
||||||
Hata
|
|
||||||
<code>{error}</code>
|
|
||||||
</Notification>,
|
|
||||||
{ placement: 'top-end' },
|
|
||||||
)
|
|
||||||
} finally {
|
|
||||||
setSubmitting(false)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ setFieldValue, values, isSubmitting }) => (
|
|
||||||
<Form className="flex flex-col h-full">
|
|
||||||
<FormContainer size="sm" className="flex flex-col h-full">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="mb-2 pb-2 border-b flex items-center justify-between">
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
shape="circle"
|
|
||||||
type="button"
|
|
||||||
size="xs"
|
|
||||||
onClick={() => setFieldValue('series', [...values.series, newSeriesValue()])}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<FaPlus /> Seri Ekle
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{/* Kaydırılabilir içerik */}
|
|
||||||
<div className="flex-1 overflow-y-auto p-1">
|
|
||||||
<FieldArray name="series">
|
|
||||||
{({ remove }) => (
|
|
||||||
<div>
|
|
||||||
<div className="grid grid-cols-12 gap-2 font-semibold text-xs py-2">
|
|
||||||
<div className="text-center col-span-1">#</div>
|
|
||||||
<div className="text-center col-span-2">Type</div>
|
|
||||||
<div className="text-center col-span-2">Name</div>
|
|
||||||
<div className="text-center col-span-2">Argument Field</div>
|
|
||||||
<div className="text-center col-span-2">Value Field</div>
|
|
||||||
<div className="text-center col-span-2">Summary Type</div>
|
|
||||||
<div className="text-center col-span-1">#</div>
|
|
||||||
</div>
|
|
||||||
{values.series.map((_, index) => (
|
|
||||||
<div key={index} className="border-b py-1">
|
|
||||||
<div className="grid grid-cols-12 gap-1 items-center">
|
|
||||||
<div className="text-center text-xs col-span-1">
|
|
||||||
{values.series[index].index}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-xs col-span-2">
|
|
||||||
<Field name={`series[${index}].type`}>
|
|
||||||
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
|
||||||
<Select
|
|
||||||
field={field}
|
|
||||||
form={form}
|
|
||||||
options={chartSeriesTypeOptions}
|
|
||||||
isClearable
|
|
||||||
value={chartSeriesTypeOptions.find(
|
|
||||||
(option) => option.value === field.value,
|
|
||||||
)}
|
|
||||||
onChange={(option) =>
|
|
||||||
form.setFieldValue(field.name, option?.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-xs col-span-2">
|
|
||||||
<Field
|
|
||||||
size="sm"
|
|
||||||
name={`series[${index}].name`}
|
|
||||||
type="text"
|
|
||||||
component={Input}
|
|
||||||
className="text-xs px-1 py-0 grid-cols-2"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-xs col-span-2">
|
|
||||||
<Field type="text" name={`series[${index}].argumentField`}>
|
|
||||||
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
|
||||||
<Select
|
|
||||||
field={field}
|
|
||||||
form={form}
|
|
||||||
isClearable={true}
|
|
||||||
options={fieldList}
|
|
||||||
value={fieldList?.find(
|
|
||||||
(option) => option.value === field.value,
|
|
||||||
)}
|
|
||||||
onChange={(option) =>
|
|
||||||
form.setFieldValue(field.name, option?.value)
|
|
||||||
}
|
|
||||||
menuPlacement="auto"
|
|
||||||
maxMenuHeight={150}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-xs col-span-2">
|
|
||||||
<Field type="text" name={`series[${index}].valueField`}>
|
|
||||||
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
|
||||||
<Select
|
|
||||||
field={field}
|
|
||||||
form={form}
|
|
||||||
isClearable={true}
|
|
||||||
options={fieldList}
|
|
||||||
value={fieldList?.find(
|
|
||||||
(option) => option.value === field.value,
|
|
||||||
)}
|
|
||||||
onChange={(option) =>
|
|
||||||
form.setFieldValue(field.name, option?.value)
|
|
||||||
}
|
|
||||||
menuPlacement="auto"
|
|
||||||
maxMenuHeight={150}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-xs col-span-2">
|
|
||||||
<Field type="text" name={`series[${index}].summaryType`}>
|
|
||||||
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
|
||||||
<Select
|
|
||||||
field={field}
|
|
||||||
form={form}
|
|
||||||
isClearable={true}
|
|
||||||
options={columnSummaryTypeListOptions}
|
|
||||||
value={columnSummaryTypeListOptions.find(
|
|
||||||
(option) => option.value === field.value,
|
|
||||||
)}
|
|
||||||
onChange={(option) =>
|
|
||||||
form.setFieldValue(field.name, option?.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-center gap-1 col-span-1">
|
|
||||||
<Button
|
|
||||||
shape="circle"
|
|
||||||
type="button"
|
|
||||||
size="xs"
|
|
||||||
icon={<FaMinus />}
|
|
||||||
className="bg-slate-100 hover:bg-red-100"
|
|
||||||
onClick={() => remove(index)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</FieldArray>
|
|
||||||
</div>
|
|
||||||
{/* Footer */}
|
|
||||||
<div className="flex gap-2 mt-auto pt-2 border-t text-right justify-end">
|
|
||||||
<Button
|
|
||||||
variant="solid"
|
|
||||||
loading={isSubmitting}
|
|
||||||
type="submit"
|
|
||||||
className="ml-auto px-4 py-1 text-sm rounded"
|
|
||||||
>
|
|
||||||
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</FormContainer>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</Formik>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ChartSeriesDialog
|
|
||||||
|
|
@ -260,9 +260,26 @@ export function buildSeriesDto(seriesList: ChartSeriesDto[]) {
|
||||||
|
|
||||||
// her seri için dto oluştur
|
// her seri için dto oluştur
|
||||||
return seriesList.map((s) => ({
|
return seriesList.map((s) => ({
|
||||||
...s,
|
type: s.type,
|
||||||
argumentField: 'ArgumentField',
|
argumentField: s.argumentField,
|
||||||
valueField: s.name,
|
valueField: s.valueField,
|
||||||
|
name: s.name || s.valueField,
|
||||||
|
axis: s.axis,
|
||||||
|
color: s.color,
|
||||||
|
dashStyle: s.dashStyle,
|
||||||
|
width: s.width,
|
||||||
|
visible: s.visible,
|
||||||
|
showInLegend: s.showInLegend,
|
||||||
|
label: {
|
||||||
|
visible: s.label?.visible || false,
|
||||||
|
backgroundColor: s.label?.backgroundColor || 'transparent',
|
||||||
|
customizeText: s.label?.customizeText || undefined,
|
||||||
|
format: s.label?.format || undefined,
|
||||||
|
font: s.label?.font || {
|
||||||
|
color: '#000000',
|
||||||
|
size: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,45 @@ const useListFormCustomDataSource = ({
|
||||||
createDeleteQuery: searchParams?.get('createDeleteQuery'),
|
createDeleteQuery: searchParams?.get('createDeleteQuery'),
|
||||||
chart: layout === layoutTypes.chart,
|
chart: layout === layoutTypes.chart,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Chart için group ve groupSummary parametreleri ekle
|
||||||
|
if (layout === layoutTypes.chart && gridOptions.seriesDto && gridOptions.seriesDto.length > 0) {
|
||||||
|
// Tüm series'lerin unique argumentField'larını topla
|
||||||
|
const allArgumentFields = [...new Set(gridOptions.seriesDto.map(s => s.argumentField).filter(Boolean))] as string[]
|
||||||
|
|
||||||
|
// İlk argumentField üzerinden group yap (chart tek bir X ekseni kullanır)
|
||||||
|
if (allArgumentFields.length > 0) {
|
||||||
|
loadOptions.group = allArgumentFields.map(field => ({
|
||||||
|
selector: field as string,
|
||||||
|
isExpanded: false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tüm series'lerin valueField'ları için summary hesapla
|
||||||
|
const groupSummaries: any[] = []
|
||||||
|
gridOptions.seriesDto.forEach(series => {
|
||||||
|
if (series.valueField && series.summaryType) {
|
||||||
|
groupSummaries.push({
|
||||||
|
selector: series.valueField,
|
||||||
|
summaryType: series.summaryType as any
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (groupSummaries.length > 0) {
|
||||||
|
loadOptions.groupSummary = groupSummaries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parametreleri tekrar oluştur
|
||||||
|
const chartParameters = getLoadOptions(loadOptions, {
|
||||||
|
listFormCode,
|
||||||
|
filter: '',
|
||||||
|
createDeleteQuery: searchParams?.get('createDeleteQuery'),
|
||||||
|
chart: layout === layoutTypes.chart,
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.assign(parameters, chartParameters)
|
||||||
|
}
|
||||||
// 1. Default filter'ı al
|
// 1. Default filter'ı al
|
||||||
const defaultFilter = searchParams?.get('filter')
|
const defaultFilter = searchParams?.get('filter')
|
||||||
? JSON.parse(searchParams.get('filter')!)
|
? JSON.parse(searchParams.get('filter')!)
|
||||||
|
|
@ -113,6 +152,54 @@ 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)
|
||||||
|
|
||||||
|
// Chart için grouped data'yı chart formatına çevir
|
||||||
|
if (layout === layoutTypes.chart && Array.isArray(response.data.data)) {
|
||||||
|
const flattenGroupedData = (items: any[], parentKeys: any = {}): any[] => {
|
||||||
|
const result: any[] = []
|
||||||
|
|
||||||
|
items.forEach((item: any) => {
|
||||||
|
if (item.items && item.items.length > 0) {
|
||||||
|
// Alt grup var, recursive olarak işle
|
||||||
|
const currentKeys = { ...parentKeys }
|
||||||
|
|
||||||
|
// Bu level'daki key'i ilgili argumentField'a map'le
|
||||||
|
gridOptions.seriesDto?.forEach(series => {
|
||||||
|
if (series.argumentField && !currentKeys[series.argumentField]) {
|
||||||
|
currentKeys[series.argumentField] = item.key
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
result.push(...flattenGroupedData(item.items, currentKeys))
|
||||||
|
} else {
|
||||||
|
// Leaf node - gerçek data
|
||||||
|
const transformed: any = { ...parentKeys }
|
||||||
|
|
||||||
|
// Son level'daki key'i ekle
|
||||||
|
gridOptions.seriesDto?.forEach(series => {
|
||||||
|
if (series.argumentField && item.key !== undefined && !transformed[series.argumentField]) {
|
||||||
|
transformed[series.argumentField] = item.key
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Summary değerlerini valueField'lara map'le
|
||||||
|
if (Array.isArray(item.summary)) {
|
||||||
|
gridOptions.seriesDto?.forEach((series, index) => {
|
||||||
|
if (series.valueField && item.summary[index] !== undefined) {
|
||||||
|
transformed[series.valueField] = item.summary[index]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(transformed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
response.data.data = flattenGroupedData(response.data.data)
|
||||||
|
}
|
||||||
|
|
||||||
// Column format multiValue ise, gelen stringi array yapmaliyiz
|
// Column format multiValue ise, gelen stringi array yapmaliyiz
|
||||||
if (columns) {
|
if (columns) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue