Grid, Pivot ve Tree Exporting

This commit is contained in:
Sedat ÖZTÜRK 2026-02-05 11:32:04 +03:00
parent 809bfb7129
commit 12bac78cb8
6 changed files with 533 additions and 510 deletions

View file

@ -1,5 +1,5 @@
{ {
"commit": "270e50e0", "commit": "809bfb71",
"releases": [ "releases": [
{ {
"version": "1.0.40", "version": "1.0.40",

View file

@ -1,5 +1,5 @@
import { Container } from '@/components/shared' import { Container } from '@/components/shared'
import { Button, Checkbox, FormContainer, FormItem, Input, Select } from '@/components/ui' import { Button, Card, Checkbox, FormContainer, FormItem, Input, Select } from '@/components/ui'
import { ListFormEditTabs } from '@/proxy/admin/list-form/options' import { ListFormEditTabs } from '@/proxy/admin/list-form/options'
import { LanguageInfo } from '@/proxy/config/models' import { LanguageInfo } from '@/proxy/config/models'
import { SelectBoxOption } from '@/types/shared' import { SelectBoxOption } from '@/types/shared'
@ -63,370 +63,386 @@ function FormTabDetails(
}, [languages]) }, [languages])
return ( return (
<Container className="grid xl:grid-cols-2"> <Formik
<Formik initialValues={listFormValues}
initialValues={listFormValues} validationSchema={schema}
validationSchema={schema} onSubmit={async (values, formikHelpers) => {
onSubmit={async (values, formikHelpers) => { await props.onSubmit(ListFormEditTabs.DetailsForm, values, formikHelpers)
await props.onSubmit(ListFormEditTabs.DetailsForm, values, formikHelpers) }}
}} >
> {({ touched, errors, isSubmitting, values }) => (
{({ touched, errors, isSubmitting, values }) => ( <Form>
<Form> <FormContainer size="sm">
<FormContainer size="sm"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<FormItem <Card className="my-2" header="General">
label={translate('::ListForms.ListFormEdit.DetailsCultureName')}
invalid={errors.cultureName && touched.cultureName}
errorMessage={errors.cultureName}
>
<Field
type="text"
autoComplete="off"
name="cultureName"
placeholder={translate('::ListForms.ListFormEdit.DetailsCultureName')}
>
{({ field, form }: FieldProps<LanguageInfo>) => (
<Select
field={field}
form={form}
isClearable={true}
options={langOptions}
value={langOptions?.filter((option) => option.value === values.cultureName)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.ListFormType')}
invalid={errors.listFormType && touched.listFormType}
errorMessage={errors.listFormType}
>
<Field
type="text"
autoComplete="off"
name="listFormType"
placeholder={translate('::ListForms.ListFormEdit.ListFormType')}
>
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select
field={field}
form={form}
isClearable={true}
options={listFormTypeOptions}
value={listFormTypeOptions?.filter(
(option) => option.value === values.listFormType,
)}
onChange={(option) => {
form.setFieldValue(field.name, option?.value)
props.onFormTypeChange(option?.value || 'List')
}}
/>
)}
</Field>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsTitle')}
invalid={errors.title && touched.title}
errorMessage={errors.title}
>
<Field
type="text"
autoComplete="off"
name="title"
placeholder={translate('::ListForms.ListFormEdit.DetailsTitle')}
component={Input}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsName')}
invalid={errors.name && touched.name}
errorMessage={errors.name}
>
<Field
type="text"
autoComplete="off"
name="name"
placeholder={translate('::ListForms.ListFormEdit.DetailsName')}
component={Input}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsDescription')}
invalid={errors.description && touched.description}
errorMessage={errors.description}
>
<Field
type="text"
autoComplete="off"
name="description"
placeholder={translate('::ListForms.ListFormEdit.DetailsDescription')}
component={Input}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.IsSub')}
invalid={errors.isSubForm && touched.isSubForm}
errorMessage={errors.isSubForm}
>
<Field
name="isSubForm"
placeholder={translate('::ListForms.ListFormEdit.IsSub')}
component={Checkbox}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.SubFormsListFormType')}
invalid={errors.subFormsListFormType && touched.subFormsListFormType}
errorMessage={errors.subFormsListFormType}
>
<Field
type="text"
autoComplete="off"
name="subFormsListFormType"
placeholder={translate('::ListForms.ListFormEdit.SubFormsListFormType')}
>
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select
field={field}
form={form}
isClearable={true}
options={listFormTypeOptions}
value={listFormTypeOptions?.filter(
(option) => option.value === values.subFormsListFormType,
)}
onChange={(option) => {
form.setFieldValue(field.name, option?.value)
props.onFormTypeChange(option?.value || 'List')
}}
/>
)}
</Field>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.ShowNote')}
invalid={errors.showNote && touched.showNote}
errorMessage={errors.showNote}
>
<Field
name="showNote"
placeholder={translate('::ListForms.ListFormEdit.ShowNote')}
component={Checkbox}
/>
</FormItem>
<div className="flex gap-2">
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.DetailsWidth')} label={translate('::ListForms.ListFormEdit.DetailsCultureName')}
invalid={errors.width && touched.width} invalid={errors.cultureName && touched.cultureName}
errorMessage={errors.width} errorMessage={errors.cultureName}
> >
<Field <Field
className="w-20" type="text"
type="number"
autoComplete="off" autoComplete="off"
name="width" name="cultureName"
placeholder={translate('::ListForms.ListFormEdit.DetailsWidth')} placeholder={translate('::ListForms.ListFormEdit.DetailsCultureName')}
>
{({ field, form }: FieldProps<LanguageInfo>) => (
<Select
field={field}
form={form}
isClearable={true}
options={langOptions}
value={langOptions?.filter((option) => option.value === values.cultureName)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.ListFormType')}
invalid={errors.listFormType && touched.listFormType}
errorMessage={errors.listFormType}
>
<Field
type="text"
autoComplete="off"
name="listFormType"
placeholder={translate('::ListForms.ListFormEdit.ListFormType')}
>
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select
field={field}
form={form}
isClearable={true}
options={listFormTypeOptions}
value={listFormTypeOptions?.filter(
(option) => option.value === values.listFormType,
)}
onChange={(option) => {
form.setFieldValue(field.name, option?.value)
props.onFormTypeChange(option?.value || 'List')
}}
/>
)}
</Field>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsTitle')}
invalid={errors.title && touched.title}
errorMessage={errors.title}
>
<Field
type="text"
autoComplete="off"
name="title"
placeholder={translate('::ListForms.ListFormEdit.DetailsTitle')}
component={Input} component={Input}
/> />
</FormItem> </FormItem>
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.DetailsHeight')} label={translate('::ListForms.ListFormEdit.DetailsName')}
invalid={errors.width && touched.width} invalid={errors.name && touched.name}
errorMessage={errors.width} errorMessage={errors.name}
> >
<Field <Field
className="w-20" type="text"
type="number"
autoComplete="off" autoComplete="off"
name="height" name="name"
placeholder={translate('::ListForms.ListFormEdit.DetailsHeight')} placeholder={translate('::ListForms.ListFormEdit.DetailsName')}
component={Input} component={Input}
/> />
</FormItem> </FormItem>
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.DetailsFullHeight')} label={translate('::ListForms.ListFormEdit.DetailsDescription')}
invalid={errors.fullHeight && touched.fullHeight} invalid={errors.description && touched.description}
errorMessage={errors.fullHeight} errorMessage={errors.description}
> >
<Field <Field
name="fullHeight" type="text"
placeholder={translate('::ListForms.ListFormEdit.DetailsFullHeight')} autoComplete="off"
name="description"
placeholder={translate('::ListForms.ListFormEdit.DetailsDescription')}
component={Input}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.ShowNote')}
invalid={errors.showNote && touched.showNote}
errorMessage={errors.showNote}
>
<Field
name="showNote"
placeholder={translate('::ListForms.ListFormEdit.ShowNote')}
component={Checkbox} component={Checkbox}
/> />
</FormItem> </FormItem>
</div> </Card>
<Card className="my-2" header="Layout & Popup">
{values.listFormType === 'List' && ( <div className="flex gap-2">
<>
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.DefaultLayout')} label={translate('::ListForms.ListFormEdit.DetailsWidth')}
invalid={errors.layoutDto?.defaultLayout && touched.layoutDto?.defaultLayout} invalid={errors.width && touched.width}
errorMessage={errors.layoutDto?.defaultLayout} errorMessage={errors.width}
> >
<Field <Field
type="text" className="w-20"
type="number"
autoComplete="off" autoComplete="off"
name="layoutDto.defaultLayout" name="width"
placeholder={translate( placeholder={translate('::ListForms.ListFormEdit.DetailsWidth')}
'::ListForms.ListFormEdit.DetailsLayoutDto.DefaultLayout', component={Input}
)} />
>
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select
field={field}
form={form}
isClearable={true}
options={listFormDefaultLayoutOptions}
value={listFormDefaultLayoutOptions?.filter(
(option) => option.value === values.layoutDto.defaultLayout,
)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem> </FormItem>
</> <FormItem
)} label={translate('::ListForms.ListFormEdit.DetailsHeight')}
invalid={errors.width && touched.width}
<div className="flex gap-2"> errorMessage={errors.width}
>
<Field
className="w-20"
type="number"
autoComplete="off"
name="height"
placeholder={translate('::ListForms.ListFormEdit.DetailsHeight')}
component={Input}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsFullHeight')}
invalid={errors.fullHeight && touched.fullHeight}
errorMessage={errors.fullHeight}
>
<Field
name="fullHeight"
placeholder={translate('::ListForms.ListFormEdit.DetailsFullHeight')}
component={Checkbox}
/>
</FormItem>
</div>
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GridLayout')} label={translate('::ListForms.ListFormEdit.IsSub')}
invalid={errors.layoutDto?.grid && touched.layoutDto?.grid} invalid={errors.isSubForm && touched.isSubForm}
errorMessage={errors.layoutDto?.grid} errorMessage={errors.isSubForm}
> >
<Field <Field
className="w-20" name="isSubForm"
autoComplete="off" placeholder={translate('::ListForms.ListFormEdit.IsSub')}
name="layoutDto.grid"
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GridLayout')}
component={Checkbox} component={Checkbox}
/> />
</FormItem> </FormItem>
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')} label={translate('::ListForms.ListFormEdit.SubFormsListFormType')}
invalid={errors.layoutDto?.pivot && touched.layoutDto?.pivot} invalid={errors.subFormsListFormType && touched.subFormsListFormType}
errorMessage={errors.layoutDto?.pivot} errorMessage={errors.subFormsListFormType}
> >
<Field <Field
className="w-20" type="text"
autoComplete="off" autoComplete="off"
name="layoutDto.pivot" name="subFormsListFormType"
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')} placeholder={translate('::ListForms.ListFormEdit.SubFormsListFormType')}
component={Checkbox} >
/> {({ field, form }: FieldProps<SelectBoxOption>) => (
</FormItem> <Select
field={field}
<FormItem form={form}
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.ChartLayout')} isClearable={true}
invalid={errors.layoutDto?.chart && touched.layoutDto?.chart} options={listFormTypeOptions}
errorMessage={errors.layoutDto?.chart} value={listFormTypeOptions?.filter(
> (option) => option.value === values.subFormsListFormType,
<Field )}
className="w-20" onChange={(option) => {
autoComplete="off" form.setFieldValue(field.name, option?.value)
name="layoutDto.chart" }}
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.ChartLayout')} />
component={Checkbox}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.TreeLayout')}
invalid={errors.layoutDto?.tree && touched.layoutDto?.tree}
errorMessage={errors.layoutDto?.tree}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.tree"
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.TreeLayout')}
component={Checkbox}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout')}
invalid={errors.layoutDto?.gantt && touched.layoutDto?.gantt}
errorMessage={errors.layoutDto?.gantt}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.gantt"
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout')}
component={Checkbox}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.SchedulerLayout')}
invalid={errors.layoutDto?.scheduler && touched.layoutDto?.scheduler}
errorMessage={errors.layoutDto?.scheduler}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.scheduler"
placeholder={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.SchedulerLayout',
)} )}
component={Checkbox} </Field>
/>
</FormItem> </FormItem>
</div>
<FormItem {values.listFormType === 'List' && (
label="Role" <>
invalid={errors.roleId && touched.roleId} <FormItem
errorMessage={errors.roleId} label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.DefaultLayout')}
> invalid={errors.layoutDto?.defaultLayout && touched.layoutDto?.defaultLayout}
<Field type="text" name="roleId"> errorMessage={errors.layoutDto?.defaultLayout}
{({ field, form }: FieldProps<IdentityRoleDto>) => ( >
<Select <Field
field={field} type="text"
form={form} autoComplete="off"
options={props.roleList} name="layoutDto.defaultLayout"
isClearable={true} placeholder={translate(
value={props.roleList.filter((option) => option.value === values.roleId)} '::ListForms.ListFormEdit.DetailsLayoutDto.DefaultLayout',
onChange={(option) => form.setFieldValue(field.name, option?.value)} )}
/> >
)} {({ field, form }: FieldProps<SelectBoxOption>) => (
</Field> <Select
</FormItem> field={field}
form={form}
isClearable={true}
options={listFormDefaultLayoutOptions}
value={listFormDefaultLayoutOptions?.filter(
(option) => option.value === values.layoutDto.defaultLayout,
)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
<FormItem <div className="flex gap-2">
label="User" <FormItem
invalid={errors.userId && touched.userId} label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GridLayout')}
errorMessage={errors.userId} invalid={errors.layoutDto?.grid && touched.layoutDto?.grid}
> errorMessage={errors.layoutDto?.grid}
<Field type="text" name="userId"> >
{({ field, form }: FieldProps<IdentityUserDto>) => ( <Field
<Select className="w-20"
field={field} autoComplete="off"
form={form} name="layoutDto.grid"
options={props.userList} placeholder={translate(
isClearable={true} '::ListForms.ListFormEdit.DetailsLayoutDto.GridLayout',
value={props.userList.filter((option) => option.value === values.userId)} )}
onChange={(option) => form.setFieldValue(field.name, option?.value)} component={Checkbox}
/> />
)} </FormItem>
</Field>
</FormItem>
<Button block variant="solid" loading={isSubmitting} type="submit"> <FormItem
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')} label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')}
</Button> invalid={errors.layoutDto?.pivot && touched.layoutDto?.pivot}
</FormContainer> errorMessage={errors.layoutDto?.pivot}
</Form> >
)} <Field
</Formik> className="w-20"
</Container> autoComplete="off"
name="layoutDto.pivot"
placeholder={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout',
)}
component={Checkbox}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.ChartLayout')}
invalid={errors.layoutDto?.chart && touched.layoutDto?.chart}
errorMessage={errors.layoutDto?.chart}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.chart"
placeholder={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.ChartLayout',
)}
component={Checkbox}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.TreeLayout')}
invalid={errors.layoutDto?.tree && touched.layoutDto?.tree}
errorMessage={errors.layoutDto?.tree}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.tree"
placeholder={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.TreeLayout',
)}
component={Checkbox}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout')}
invalid={errors.layoutDto?.gantt && touched.layoutDto?.gantt}
errorMessage={errors.layoutDto?.gantt}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.gantt"
placeholder={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout',
)}
component={Checkbox}
/>
</FormItem>
<FormItem
label={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.SchedulerLayout',
)}
invalid={errors.layoutDto?.scheduler && touched.layoutDto?.scheduler}
errorMessage={errors.layoutDto?.scheduler}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.scheduler"
placeholder={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.SchedulerLayout',
)}
component={Checkbox}
/>
</FormItem>
</div>
</>
)}
<FormItem
label="Role"
invalid={errors.roleId && touched.roleId}
errorMessage={errors.roleId}
>
<Field type="text" name="roleId">
{({ field, form }: FieldProps<IdentityRoleDto>) => (
<Select
field={field}
form={form}
options={props.roleList}
isClearable={true}
value={props.roleList.filter((option) => option.value === values.roleId)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
<FormItem
label="User"
invalid={errors.userId && touched.userId}
errorMessage={errors.userId}
>
<Field type="text" name="userId">
{({ field, form }: FieldProps<IdentityUserDto>) => (
<Select
field={field}
form={form}
options={props.userList}
isClearable={true}
value={props.userList.filter((option) => option.value === values.userId)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
</Card>
</div>
<Button block variant="solid" loading={isSubmitting} type="submit">
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
</FormContainer>
</Form>
)}
</Formik>
) )
} }

View file

@ -29,7 +29,6 @@ import DataGrid, {
GroupItem as GroupItemDx, GroupItem as GroupItemDx,
GroupPanel, GroupPanel,
HeaderFilter, HeaderFilter,
IStateStoringProps,
LoadPanel, LoadPanel,
Pager, Pager,
Paging, Paging,
@ -74,7 +73,6 @@ import { useListFormCustomDataSource } from './useListFormCustomDataSource'
import { useListFormColumns } from './useListFormColumns' import { useListFormColumns } from './useListFormColumns'
import { Loading } from '@/components/shared' import { Loading } from '@/components/shared'
import { useStoreState } from '@/store' import { useStoreState } from '@/store'
import { locale, loadMessages } from 'devextreme/localization'
interface GridProps { interface GridProps {
listFormCode: string listFormCode: string
@ -108,14 +106,6 @@ const Grid = (props: GridProps) => {
const [isPopupFullScreen, setIsPopupFullScreen] = useState(false) const [isPopupFullScreen, setIsPopupFullScreen] = useState(false)
const [widgetGroupHeight, setWidgetGroupHeight] = useState(0) const [widgetGroupHeight, setWidgetGroupHeight] = useState(0)
const preloadExportLibs = () => {
import('exceljs')
import('file-saver')
import('devextreme/excel_exporter')
import('jspdf')
import('devextreme/pdf_exporter')
}
type EditorOptionsWithButtons = { type EditorOptionsWithButtons = {
buttons?: any[] buttons?: any[]
} & Record<string, any> } & Record<string, any>
@ -985,9 +975,9 @@ const Grid = (props: GridProps) => {
if (!grid) return if (!grid) return
try { try {
if (e.format === 'xlsx' || e.format === 'csv') { if (e.format === 'xlsx') {
// exceljs + file-saver + devextreme excel exporter => ihtiyaç anında yükle // Sadece Excel için gerekli kütüphaneleri yükle
const [{ Workbook }, { saveAs }, { exportDataGrid: exportDataExcel }] = await Promise.all([ const [{ Workbook }, { saveAs }, { exportDataGrid }] = await Promise.all([
import('exceljs'), import('exceljs'),
import('file-saver'), import('file-saver'),
import('devextreme/excel_exporter'), import('devextreme/excel_exporter'),
@ -996,25 +986,45 @@ const Grid = (props: GridProps) => {
const workbook = new Workbook() const workbook = new Workbook()
const worksheet = workbook.addWorksheet(`${listFormCode}_sheet`) const worksheet = workbook.addWorksheet(`${listFormCode}_sheet`)
await exportDataExcel({ await exportDataGrid({
component: grid as any, component: grid as any,
worksheet, worksheet,
autoFilterEnabled: true, autoFilterEnabled: true,
}) })
if (e.format === 'xlsx') { const buffer = await workbook.xlsx.writeBuffer()
const buffer = await workbook.xlsx.writeBuffer() saveAs(
saveAs( new Blob([buffer], { type: 'application/octet-stream' }),
new Blob([buffer], { type: 'application/octet-stream' }), `${listFormCode}_export.xlsx`,
`${listFormCode}_export.xlsx`, )
) } else if (e.format === 'csv') {
} else { // Sadece CSV için gerekli kütüphaneleri yükle (exceljs CSV desteği için)
const buffer = await workbook.csv.writeBuffer() const [{ Workbook }, { saveAs }] = await Promise.all([
saveAs( import('exceljs'),
new Blob([buffer], { type: 'application/octet-stream' }), import('file-saver'),
`${listFormCode}_export.csv`, ])
)
} const workbook = new Workbook()
const worksheet = workbook.addWorksheet(`${listFormCode}_sheet`)
// CSV için basit data export
const dataSource = grid.getDataSource()
const items = dataSource?.items() || []
const columns = grid.getVisibleColumns().filter((c: any) => c.dataField)
// Header ekle
worksheet.addRow(columns.map((c: any) => c.caption || c.dataField))
// Data ekle
items.forEach((item: any) => {
worksheet.addRow(columns.map((c: any) => item[c.dataField!]))
})
const buffer = await workbook.csv.writeBuffer()
saveAs(
new Blob([buffer], { type: 'text/csv' }),
`${listFormCode}_export.csv`,
)
} else if (e.format === 'pdf') { } else if (e.format === 'pdf') {
// jspdf + devextreme pdf exporter => ihtiyaç anında yükle // jspdf + devextreme pdf exporter => ihtiyaç anında yükle
const [jspdfMod, { exportDataGrid: exportDataPdf }] = await Promise.all([ const [jspdfMod, { exportDataGrid: exportDataPdf }] = await Promise.all([
@ -1151,7 +1161,7 @@ const Grid = (props: GridProps) => {
<Export <Export
enabled={gridDto.gridOptions.exportDto?.enabled} enabled={gridDto.gridOptions.exportDto?.enabled}
allowExportSelectedData={gridDto.gridOptions.exportDto?.allowExportSelectedData} allowExportSelectedData={gridDto.gridOptions.exportDto?.allowExportSelectedData}
formats={['pdf', 'xlsx', 'csv']} formats={['pdf', 'xlsx']}
/> />
<Editing <Editing
refreshMode={gridDto.gridOptions.editingOptionDto?.refreshMode} refreshMode={gridDto.gridOptions.editingOptionDto?.refreshMode}

View file

@ -6,12 +6,7 @@ import {
postListFormCustomization, postListFormCustomization,
} from '@/services/list-form-customization.service' } from '@/services/list-form-customization.service'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import Chart, { import Chart, { ChartRef, CommonSeriesSettings, Size, Tooltip } from 'devextreme-react/chart'
ChartRef,
CommonSeriesSettings,
Size,
Tooltip,
} from 'devextreme-react/chart'
import PivotGrid, { import PivotGrid, {
Export, Export,
FieldChooser, FieldChooser,
@ -22,10 +17,9 @@ import PivotGrid, {
PivotGridTypes, PivotGridTypes,
Scrolling, Scrolling,
Search, Search,
StateStoring,
} from 'devextreme-react/pivot-grid' } from 'devextreme-react/pivot-grid'
import CustomStore from 'devextreme/data/custom_store' import CustomStore from 'devextreme/data/custom_store'
import PivotGridDataSource, { Field } from 'devextreme/ui/pivot_grid/data_source' import { Field } from 'devextreme/ui/pivot_grid/data_source'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { GridColumnData } from './GridColumnData' import { GridColumnData } from './GridColumnData'
@ -93,39 +87,45 @@ const Pivot = (props: PivotProps) => {
gridRef, gridRef,
}) })
const onCellPrepared = useCallback((e: any) => { const onCellPrepared = useCallback(
const columnFormats = gridDto?.columnFormats (e: any) => {
if (!columnFormats) { const columnFormats = gridDto?.columnFormats
return if (!columnFormats) {
} return
}
// satir, hucre yada header vb. kisimlara conditional style uygulamak icin // satir, hucre yada header vb. kisimlara conditional style uygulamak icin
for (let indxCol = 0; indxCol < columnFormats.length; indxCol++) { for (let indxCol = 0; indxCol < columnFormats.length; indxCol++) {
const colFormat = columnFormats[indxCol] const colFormat = columnFormats[indxCol]
for (let indxStyl = 0; indxStyl < colFormat.columnStylingDto.length; indxStyl++) { for (let indxStyl = 0; indxStyl < colFormat.columnStylingDto.length; indxStyl++) {
const colStyle = colFormat.columnStylingDto[indxStyl] // uygulanacak style const colStyle = colFormat.columnStylingDto[indxStyl] // uygulanacak style
if (e.rowType == colStyle.rowType) { if (e.rowType == colStyle.rowType) {
// header, filter, data, group, summaries ..her birisine style uygulanabilir // header, filter, data, group, summaries ..her birisine style uygulanabilir
// style bütün satıra uygulansın olarak seçili ise yada sadece ilgili field üzerinde ise // style bütün satıra uygulansın olarak seçili ise yada sadece ilgili field üzerinde ise
if (colStyle.useRow || e.column?.dataField == colFormat.fieldName) { if (colStyle.useRow || e.column?.dataField == colFormat.fieldName) {
if ( if (
!colStyle.conditionValue || !colStyle.conditionValue ||
controlStyleCondition(e.data, colFormat.fieldName, colStyle) controlStyleCondition(e.data, colFormat.fieldName, colStyle)
) { ) {
// css sınıf ismi var ise uygula // css sınıf ismi var ise uygula
if (colStyle.cssClassName) { if (colStyle.cssClassName) {
e.cellElement.addClass(colStyle.cssClassName) e.cellElement.addClass(colStyle.cssClassName)
} }
// css inline style var ise uygula // css inline style var ise uygula
if (colStyle.cssStyles) { if (colStyle.cssStyles) {
e.cellElement.attr('style', e.cellElement.attr('style') + ';' + colStyle.cssStyles) e.cellElement.attr(
'style',
e.cellElement.attr('style') + ';' + colStyle.cssStyles,
)
}
} }
} }
} }
} }
} }
} },
}, [gridDto]) [gridDto],
)
const clearPivotFilters = useCallback(() => { const clearPivotFilters = useCallback(() => {
const grid = gridRef.current?.instance() const grid = gridRef.current?.instance()
@ -181,7 +181,7 @@ const Pivot = (props: PivotProps) => {
// Filtreleri temizle // Filtreleri temizle
clearPivotFilters() clearPivotFilters()
setGridPanelColor('transparent') setGridPanelColor('transparent')
toast.push( toast.push(
<Notification type="success" duration={2000}> <Notification type="success" duration={2000}>
{translate('::ListForms.ListForm.GridStateReset')} {translate('::ListForms.ListForm.GridStateReset')}
@ -191,43 +191,46 @@ const Pivot = (props: PivotProps) => {
} }
}, [listFormCode, storageKey, clearPivotFilters, translate]) }, [listFormCode, storageKey, clearPivotFilters, translate])
const onExporting = useCallback(async (e: PivotGridTypes.ExportingEvent) => { const onExporting = useCallback(
e.cancel = true async (e: PivotGridTypes.ExportingEvent) => {
e.cancel = true
const pivot = gridRef?.current?.instance() const pivot = gridRef?.current?.instance()
if (!pivot) return if (!pivot) return
try { try {
// PivotGrid sadece Excel export destekliyor // PivotGrid sadece Excel export destekliyor
const [{ Workbook }, { saveAs }, { exportPivotGrid }] = await Promise.all([ const [{ Workbook }, { saveAs }, { exportPivotGrid }] = await Promise.all([
import('exceljs'), import('exceljs'),
import('file-saver'), import('file-saver'),
import('devextreme/excel_exporter'), import('devextreme/excel_exporter'),
]) ])
const workbook = new Workbook() const workbook = new Workbook()
const worksheet = workbook.addWorksheet(`${listFormCode}_pivot`) const worksheet = workbook.addWorksheet(`${listFormCode}_pivot`)
await exportPivotGrid({ await exportPivotGrid({
component: pivot as any, component: pivot as any,
worksheet, worksheet,
}) })
const buffer = await workbook.xlsx.writeBuffer() const buffer = await workbook.xlsx.writeBuffer()
saveAs( saveAs(
new Blob([buffer], { type: 'application/octet-stream' }), new Blob([buffer], { type: 'application/octet-stream' }),
`${listFormCode}_pivot_export.xlsx`, `${listFormCode}_pivot_export.xlsx`,
) )
} catch (err) { } catch (err) {
console.error('Pivot export error:', err) console.error('Pivot export error:', err)
toast.push( toast.push(
<Notification type="danger" duration={2500}> <Notification type="danger" duration={2500}>
{translate('::App.Common.ExportError') ?? 'Dışa aktarma sırasında hata oluştu.'} {translate('::App.Common.ExportError') ?? 'Dışa aktarma sırasında hata oluştu.'}
</Notification>, </Notification>,
{ placement: 'top-end' }, { placement: 'top-end' },
) )
} }
}, [listFormCode, translate]) },
[listFormCode, translate],
)
// StateStoring fonksiyonlarını ref'e kaydet // StateStoring fonksiyonlarını ref'e kaydet
const customSaveState = useCallback( const customSaveState = useCallback(
@ -244,22 +247,19 @@ const Pivot = (props: PivotProps) => {
[listFormCode, storageKey], [listFormCode, storageKey],
) )
const customLoadState = useCallback( const customLoadState = useCallback(() => {
() => { return getListFormCustomization(
return getListFormCustomization( listFormCode,
listFormCode, ListFormCustomizationTypeEnum.GridState,
ListFormCustomizationTypeEnum.GridState, `pivot-${storageKey}`,
`pivot-${storageKey}`, ).then((response: any) => {
).then((response: any) => { if (response.data?.length > 0) {
if (response.data?.length > 0) { setGridPanelColor(statedGridPanelColor)
setGridPanelColor(statedGridPanelColor) return JSON.parse(response.data[0].customizationData)
return JSON.parse(response.data[0].customizationData) }
} return null
return null })
}) }, [listFormCode, storageKey])
},
[listFormCode, storageKey],
)
useEffect(() => { useEffect(() => {
refListFormCode.current = listFormCode refListFormCode.current = listFormCode
@ -373,16 +373,18 @@ const Pivot = (props: PivotProps) => {
const instance = gridRef?.current?.instance() const instance = gridRef?.current?.instance()
if (instance) { if (instance) {
customLoadState().then((state) => { customLoadState()
if (state) { .then((state) => {
const ds = instance.getDataSource() if (state) {
if (ds && typeof ds.state === 'function') { const ds = instance.getDataSource()
ds.state(state) if (ds && typeof ds.state === 'function') {
ds.state(state)
}
} }
} })
}).catch((err) => { .catch((err) => {
console.error('Pivot state load error:', err) console.error('Pivot state load error:', err)
}) })
} }
}, [gridDto, columnData, gridDataSource, customLoadState]) }, [gridDto, columnData, gridDataSource, customLoadState])
@ -416,7 +418,6 @@ const Pivot = (props: PivotProps) => {
<div className="p-1 bg-white dark:bg-neutral-800 dark:border-neutral-700 "> <div className="p-1 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 pb-1 flex gap-1 border-b-1"> <div className="relative pb-1 flex gap-1 border-b-1">
<Button <Button
size="xs" size="xs"
variant={'default'} variant={'default'}
@ -437,21 +438,23 @@ const Pivot = (props: PivotProps) => {
const ds = instance.getDataSource() const ds = instance.getDataSource()
if (ds && typeof ds.state === 'function') { if (ds && typeof ds.state === 'function') {
const currentState = ds.state() const currentState = ds.state()
customSaveState(currentState).then(() => { customSaveState(currentState)
toast.push( .then(() => {
<Notification type="success" duration={2000}> toast.push(
{translate('::ListForms.ListForm.GridStateSaved')} <Notification type="success" duration={2000}>
</Notification>, {translate('::ListForms.ListForm.GridStateSaved')}
{ placement: 'top-end' }, </Notification>,
) { placement: 'top-end' },
}).catch(() => { )
toast.push( })
<Notification type="danger" duration={2500}> .catch(() => {
{translate('::ListForms.ListForm.GridStateSaveError')} toast.push(
</Notification>, <Notification type="danger" duration={2500}>
{ placement: 'top-end' }, {translate('::ListForms.ListForm.GridStateSaveError')}
) </Notification>,
}) { placement: 'top-end' },
)
})
} }
} }
}} }}
@ -541,7 +544,9 @@ const Pivot = (props: PivotProps) => {
height={500} height={500}
/> />
<LoadPanel <LoadPanel
enabled={gridDto.gridOptions.pagerOptionDto?.loadPanelEnabled as boolean | undefined} enabled={
gridDto.gridOptions.pagerOptionDto?.loadPanelEnabled as boolean | undefined
}
text={gridDto.gridOptions.pagerOptionDto?.loadPanelText} text={gridDto.gridOptions.pagerOptionDto?.loadPanelText}
/> />
<Scrolling mode={gridDto.gridOptions.pagerOptionDto.scrollingMode} /> <Scrolling mode={gridDto.gridOptions.pagerOptionDto.scrollingMode} />

View file

@ -101,14 +101,6 @@ const Tree = (props: TreeProps) => {
const [expandedRowKeys, setExpandedRowKeys] = useState<any[]>([]) const [expandedRowKeys, setExpandedRowKeys] = useState<any[]>([])
const config = useStoreState((state) => state.abpConfig.config) const config = useStoreState((state) => state.abpConfig.config)
const preloadExportLibs = () => {
import('exceljs')
import('file-saver')
import('devextreme/excel_exporter')
import('jspdf')
import('devextreme/pdf_exporter')
}
type EditorOptionsWithButtons = { type EditorOptionsWithButtons = {
buttons?: any[] buttons?: any[]
} & Record<string, any> } & Record<string, any>

View file

@ -275,50 +275,50 @@ const useListFormCustomDataSource = ({
return null return null
} }
}, },
// totalCount: async (loadOptions) => { totalCount: async (loadOptions) => {
// const parameters = getLoadOptions(loadOptions, { const parameters = getLoadOptions(loadOptions, {
// listFormCode, listFormCode,
// filter: '', filter: '',
// createDeleteQuery: searchParams?.get('createDeleteQuery'), createDeleteQuery: searchParams?.get('createDeleteQuery'),
// group: '', group: '',
// }) })
// // 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')!)
// : null : null
// let combinedFilter: any = parameters.filter let combinedFilter: any = parameters.filter
// // 2. Eğer hem default hem de grid filter varsa merge et // 2. Eğer hem default hem de grid filter varsa merge et
// if (defaultFilter && combinedFilter) { if (defaultFilter && combinedFilter) {
// combinedFilter = [defaultFilter, 'and', combinedFilter] combinedFilter = [defaultFilter, 'and', combinedFilter]
// } else if (defaultFilter) { } else if (defaultFilter) {
// combinedFilter = defaultFilter combinedFilter = defaultFilter
// } }
// if (combinedFilter && combinedFilter.length > 0) { if (combinedFilter && combinedFilter.length > 0) {
// parameters.filter = JSON.stringify(combinedFilter) parameters.filter = JSON.stringify(combinedFilter)
// } else { } else {
// delete parameters.filter // hiç göndermesin delete parameters.filter // hiç göndermesin
// } }
// try { try {
// const response = await dynamicFetch('list-form-select/select', 'GET', parameters) const response = await dynamicFetch('list-form-select/select', 'GET', parameters)
// return response.data.totalCount return response.data.totalCount
// } catch (error: any) { } catch (error: any) {
// // toast.push( // toast.push(
// // <Notification type="danger" duration={2000}> // <Notification type="danger" duration={2000}>
// // TotalCount error // TotalCount error
// // {error.toString()} // {error.toString()}
// // </Notification>, // </Notification>,
// // { // {
// // placement: 'top-end', // placement: 'top-end',
// // }, // },
// // ) // )
// return null return null
// } }
// }, },
byKey: async (key) => { byKey: async (key) => {
const parameters = getLoadOptions( const parameters = getLoadOptions(
{ key }, { key },