Optimizasyon güncellemeleri
This commit is contained in:
parent
d982a3ce42
commit
7051a84b4b
19 changed files with 525 additions and 465 deletions
|
|
@ -5,6 +5,7 @@ using Sozsoft.Settings.Localization;
|
|||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Localization;
|
||||
using Volo.Abp.Settings;
|
||||
using Volo.Abp.Threading;
|
||||
using SettingDefinition = Sozsoft.Settings.Entities.SettingDefinition;
|
||||
|
||||
namespace Sozsoft.Settings;
|
||||
|
|
@ -22,8 +23,7 @@ public class SettingsDefinitionProvider : SettingDefinitionProvider
|
|||
public override void Define(ISettingDefinitionContext context)
|
||||
{
|
||||
repository.DisableTracking();
|
||||
var settingDefinitions = repository.GetListAsync().Result;
|
||||
//var settingDefinitions = AsyncHelper.RunSync(() => repository.ToListAsync());
|
||||
var settingDefinitions = AsyncHelper.RunSync(() => repository.GetListAsync());
|
||||
|
||||
foreach (var item in settingDefinitions.OrderBy(a => a.Order))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Authorization;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
|
|
@ -272,16 +273,28 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
|||
|
||||
public async Task<ForumTopicDto> GetTopicAsync(Guid id)
|
||||
{
|
||||
var topic = await _topicRepository.GetAsync(id);
|
||||
var queryable = await _topicRepository.GetQueryableAsync();
|
||||
var affectedRows = await queryable
|
||||
.Where(t => t.Id == id)
|
||||
.ExecuteUpdateAsync(setters =>
|
||||
setters.SetProperty(t => t.ViewCount, t => t.ViewCount + 1)
|
||||
);
|
||||
|
||||
// View count artırma işlemi arka planda yapılmalı (performans için)
|
||||
// Her okumada update yapmak performans sorununa neden olur
|
||||
_ = Task.Run(async () =>
|
||||
if (affectedRows == 0)
|
||||
{
|
||||
var t = await _topicRepository.GetAsync(id);
|
||||
t.ViewCount++;
|
||||
await _topicRepository.UpdateAsync(t);
|
||||
});
|
||||
throw new EntityNotFoundException(typeof(ForumTopic), id);
|
||||
}
|
||||
|
||||
var topic = await AsyncExecuter.FirstOrDefaultAsync(
|
||||
queryable
|
||||
.AsNoTracking()
|
||||
.Where(t => t.Id == id)
|
||||
);
|
||||
|
||||
if (topic == null)
|
||||
{
|
||||
throw new EntityNotFoundException(typeof(ForumTopic), id);
|
||||
}
|
||||
|
||||
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
||||
}
|
||||
|
|
@ -454,14 +467,13 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
|||
topic.ReplyCount = Math.Max(0, topic.ReplyCount - 1);
|
||||
category.PostCount = Math.Max(0, category.PostCount ?? 0 - 1);
|
||||
|
||||
// 🔠Last post değişti mi kontrol et
|
||||
var latestPost = await _postRepository
|
||||
.GetQueryableAsync()
|
||||
.ContinueWith(q => q.Result
|
||||
// Last post değişti mi kontrol et
|
||||
var postsQueryable = await _postRepository.GetQueryableAsync();
|
||||
var latestPost = await AsyncExecuter.FirstOrDefaultAsync(
|
||||
postsQueryable
|
||||
.Where(p => p.TopicId == topic.Id)
|
||||
.OrderByDescending(p => p.CreationTime)
|
||||
.FirstOrDefault()
|
||||
);
|
||||
);
|
||||
|
||||
if (latestPost != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ public class ListFormQueryPreviewAppService : PlatformAppService
|
|||
|
||||
var (_, _, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
||||
|
||||
selectQueryManager.PrepareQueries(listForm, readableFields, dataSourceType, customizations);
|
||||
await selectQueryManager.PrepareQueriesAsync(listForm, readableFields, dataSourceType, customizations);
|
||||
|
||||
return selectQueryManager.SelectQuery;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,12 +55,12 @@ public class ListFormDataAppService : PlatformAppService
|
|||
|
||||
var queryParameters = httpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
object filter = new object[] { input.Data[0], "=", input.Keys[0] };
|
||||
object[] filter = [input.Data[0], "=", input.Keys[0]];
|
||||
|
||||
var selectRequest = new SelectRequestDto
|
||||
{
|
||||
ListFormCode = input.ListFormCode,
|
||||
Filter = filter.ToString(),
|
||||
Filter = JsonSerializer.Serialize(filter),
|
||||
Skip = 0,
|
||||
Take = 1,
|
||||
RequireTotalCount = false,
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
|||
|
||||
var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
||||
|
||||
selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams);
|
||||
await selectQueryManager.PrepareQueriesAsync(listForm, fields, dataSourceType, customizations, queryParams);
|
||||
var param = selectQueryManager.SelectQueryParameters;
|
||||
|
||||
if (!string.IsNullOrEmpty(queryParams.Group) && !string.IsNullOrEmpty(selectQueryManager.GroupQuery))
|
||||
|
|
@ -293,11 +293,6 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
|||
//kullaniciya ait ListFormField verilerini al
|
||||
var fields = await listFormFieldManager.GetUserListFormFields(ListFormCode);
|
||||
|
||||
// selectQueryManager.PrepareQueries(listForm, fields);
|
||||
// fields = fields.Where(c => selectQueryManager.SelectFields.Any(sc => sc.FieldName == c.FieldName))
|
||||
// .OrderBy(c => c.ListOrderNo)
|
||||
// .ThenBy(c => c.CaptionName)
|
||||
// .ToList();
|
||||
fields = fields.OrderBy(c => c.ListOrderNo)
|
||||
.ThenBy(c => c.CaptionName)
|
||||
.ToList();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Data;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Sozsoft.Platform.Entities;
|
||||
using Sozsoft.Platform.Enums;
|
||||
using Sozsoft.Platform.Extensions;
|
||||
|
|
@ -32,11 +33,11 @@ public interface ISelectQueryManager
|
|||
List<string> SummaryQueries { get; }
|
||||
public bool IsAppliedGridFilter { get; }
|
||||
public bool IsAppliedServerFilter { get; }
|
||||
void PrepareQueries(ListForm listform,
|
||||
List<ListFormField> listFormFields,
|
||||
DataSourceTypeEnum dataSourceType,
|
||||
List<ListFormCustomization> listFormCustomizations = null,
|
||||
QueryParameters queryParams = null);
|
||||
Task PrepareQueriesAsync(ListForm listform,
|
||||
List<ListFormField> listFormFields,
|
||||
DataSourceTypeEnum dataSourceType,
|
||||
List<ListFormCustomization> listFormCustomizations = null,
|
||||
QueryParameters queryParams = null);
|
||||
}
|
||||
|
||||
public class SelectField
|
||||
|
|
@ -129,11 +130,11 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
|
|||
DefaultValueHelper = defaultValueHelper;
|
||||
}
|
||||
|
||||
public void PrepareQueries(ListForm listform,
|
||||
List<ListFormField> listFormFields,
|
||||
DataSourceTypeEnum dataSourceType,
|
||||
List<ListFormCustomization> listFormCustomizations = null,
|
||||
QueryParameters queryParams = null)
|
||||
public async Task PrepareQueriesAsync(ListForm listform,
|
||||
List<ListFormField> listFormFields,
|
||||
DataSourceTypeEnum dataSourceType,
|
||||
List<ListFormCustomization> listFormCustomizations = null,
|
||||
QueryParameters queryParams = null)
|
||||
{
|
||||
ResetQueryState();
|
||||
|
||||
|
|
@ -170,7 +171,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
|
|||
var joinParts = SelectFields.Where(c => !string.IsNullOrEmpty(c.JoinSql)).Select(c => c.JoinSql).Distinct();
|
||||
JoinParts.AddRange(joinParts);
|
||||
|
||||
List<string> whereFields = GetWhereFields(listform, listFormFields, queryParams);
|
||||
List<string> whereFields = await GetWhereFieldsAsync(listform, listFormFields, queryParams);
|
||||
WhereParts.AddRange(whereFields);
|
||||
|
||||
List<string> sortFields = GetSortFields(listform, listFormFields, queryParams?.Sort);
|
||||
|
|
@ -343,7 +344,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
|
|||
}
|
||||
}
|
||||
|
||||
private List<string> GetWhereFields(ListForm listform, List<ListFormField> listFormFields, QueryParameters queryParams = null)
|
||||
private async Task<List<string>> GetWhereFieldsAsync(ListForm listform, List<ListFormField> listFormFields, QueryParameters queryParams = null)
|
||||
{
|
||||
var whereParts = new List<string>();
|
||||
|
||||
|
|
@ -436,7 +437,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
|
|||
whereParts.Add("AND");
|
||||
}
|
||||
|
||||
var ids = BranchUsersRepository.GetListAsync((a) => a.UserId == CurrentUser.Id.Value).Result;
|
||||
var ids = await BranchUsersRepository.GetListAsync((a) => a.UserId == CurrentUser.Id.Value);
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
whereParts.Add($"\"BranchId\" IN ({string.Join(",", ids.Select(a => $"'{a.BranchId}'"))})");
|
||||
|
|
@ -454,7 +455,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
|
|||
whereParts.Add("AND");
|
||||
}
|
||||
|
||||
var ids = OuRepository.GetOrganizationUnitIdsWithChildren(CurrentUser.Id.Value).Result;
|
||||
var ids = await OuRepository.GetOrganizationUnitIdsWithChildren(CurrentUser.Id.Value);
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
whereParts.Add($"\"OrganizationUnitId\" IN ({string.Join(",", ids.Select(a => $"'{a}'"))})");
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ public class DynamicFormReport : XtraReport
|
|||
Skip = 0
|
||||
};
|
||||
|
||||
selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams);
|
||||
await selectQueryManager.PrepareQueriesAsync(listForm, fields, dataSourceType, customizations, queryParams);
|
||||
|
||||
var reportFields = GetReportFields(listForm, fields, selectQueryManager, defaultValueHelper);
|
||||
var selectQuery = DynamicReportImageHelper.StripReportPaging(DynamicReportImageHelper.ApplyQueryParameters(
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ public class DynamicGridReport : XtraReport
|
|||
Skip = 0
|
||||
};
|
||||
|
||||
selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams);
|
||||
await selectQueryManager.PrepareQueriesAsync(listForm, fields, dataSourceType, customizations, queryParams);
|
||||
var selectFieldsByName = DynamicReportImageHelper.CreateSelectFieldMap(selectQueryManager.SelectFields);
|
||||
var editorTypesByField = DynamicReportImageHelper.CreateEditorTypeMap(listForm);
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public class DynamicTreeReport : XtraReport
|
|||
Skip = 0
|
||||
};
|
||||
|
||||
selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams);
|
||||
await selectQueryManager.PrepareQueriesAsync(listForm, fields, dataSourceType, customizations, queryParams);
|
||||
var selectFieldsByName = DynamicReportImageHelper.CreateSelectFieldMap(selectQueryManager.SelectFields);
|
||||
var editorTypesByField = DynamicReportImageHelper.CreateEditorTypeMap(listForm);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { AUTH_API_NAME, DEFAULT_API_NAME } from '@/constants/app.constant'
|
||||
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import authApiService from './authApi.service'
|
||||
import platformApiService from './platformApi.service'
|
||||
|
||||
|
|
@ -11,29 +11,18 @@ export default {
|
|||
fetchData<Response = unknown, Request = Record<string, unknown>>(
|
||||
param: AxiosRequestConfig<Request>,
|
||||
config?: Config,
|
||||
) {
|
||||
return new Promise<AxiosResponse<Response>>((resolve, reject) => {
|
||||
if (!config || config?.apiName === DEFAULT_API_NAME) {
|
||||
platformApiService(param)
|
||||
.then((response: AxiosResponse<Response>) => {
|
||||
resolve(response)
|
||||
})
|
||||
.catch((errors: AxiosError) => {
|
||||
// console.error(errors)
|
||||
reject(errors)
|
||||
})
|
||||
} else if (config?.apiName === AUTH_API_NAME) {
|
||||
authApiService(param)
|
||||
.then((response: AxiosResponse<Response>) => {
|
||||
resolve(response)
|
||||
})
|
||||
.catch((errors: AxiosError) => {
|
||||
console.error(errors)
|
||||
reject(errors)
|
||||
})
|
||||
} else {
|
||||
reject(new Error('NO API DEFINED'))
|
||||
}
|
||||
})
|
||||
): Promise<AxiosResponse<Response>> {
|
||||
if (!config || config?.apiName === DEFAULT_API_NAME) {
|
||||
return platformApiService(param) as Promise<AxiosResponse<Response>>
|
||||
}
|
||||
|
||||
if (config?.apiName === AUTH_API_NAME) {
|
||||
return authApiService(param).catch((errors) => {
|
||||
console.error(errors)
|
||||
throw errors
|
||||
}) as Promise<AxiosResponse<Response>>
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('NO API DEFINED'))
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import { ColumnFormatDto, GridBoxOptionsDto } from '@/proxy/form/models'
|
||||
import { Button } from 'devextreme-react/button'
|
||||
import DataGrid, { DataGridRef } from 'devextreme-react/data-grid'
|
||||
import DropDownBox from 'devextreme-react/drop-down-box'
|
||||
import { ReactElement, useRef } from 'react'
|
||||
import { ReactElement } from 'react'
|
||||
import { SharedGridBoxEditor } from '@/views/shared/SharedGridBoxEditor'
|
||||
|
||||
const GridBoxEditorComponent = ({
|
||||
value,
|
||||
values,
|
||||
options,
|
||||
onValueChanged,
|
||||
col,
|
||||
|
|
@ -19,99 +16,17 @@ const GridBoxEditorComponent = ({
|
|||
col?: ColumnFormatDto
|
||||
editorOptions: any
|
||||
}): ReactElement => {
|
||||
const gridRef = useRef<DataGridRef>(null)
|
||||
const val = Array.isArray(value) ? value : [value]
|
||||
const lookupDto = col?.lookupDto
|
||||
|
||||
// const dataSource: any =
|
||||
// typeof lookupDto?.dataSource === 'function'
|
||||
// ? lookupDto.dataSource(values)
|
||||
// : (lookupDto?.dataSource ?? [])
|
||||
|
||||
return (
|
||||
<DropDownBox
|
||||
{...editorOptions}
|
||||
value={val}
|
||||
<SharedGridBoxEditor
|
||||
value={value}
|
||||
dataSource={editorOptions?.dataSource}
|
||||
valueExpr={lookupDto?.valueExpr}
|
||||
displayExpr={lookupDto?.displayExpr}
|
||||
showClearButton={options?.showClearButton}
|
||||
acceptCustomValue={options?.acceptCustomValue}
|
||||
onValueChanged={(e) => {
|
||||
onValueChanged?.(typeof e.value === 'string' ? e.value?.trim() : e.value)
|
||||
|
||||
//Eğer clear button kullanılırsa altında grid bilgileride otomatik deselect olmalı ve dropbox kapanmalı
|
||||
if (e.value === null) {
|
||||
e.component.close()
|
||||
gridRef.current?.instance().deselectAll()
|
||||
}
|
||||
}}
|
||||
contentRender={(e) => (
|
||||
<>
|
||||
<DataGrid
|
||||
ref={gridRef}
|
||||
dataSource={editorOptions?.dataSource}
|
||||
key={lookupDto?.valueExpr}
|
||||
filterRow={{
|
||||
visible: options?.filterRowVisible,
|
||||
applyFilter: 'auto',
|
||||
}}
|
||||
columns={options?.columns.map((c: string) => c.toLowerCase())} //AliBaba -> alibaba
|
||||
hoverStateEnabled={true}
|
||||
height={options?.height}
|
||||
selection={{
|
||||
mode: options?.selectionMode,
|
||||
}}
|
||||
onRowClick={(event) => {
|
||||
//Single seçimlerde otomatik kapanmalı ve seçtiği değer yazılmalı
|
||||
if (options?.selectionMode === 'single') {
|
||||
e.component.option('value', event.data.key)
|
||||
e.component.close()
|
||||
}
|
||||
}}
|
||||
onEditorPrepared={(event) => {
|
||||
const getListValues = async () => {
|
||||
const data = await editorOptions?.dataSource?.load()
|
||||
|
||||
const listValues = data.map((a: any) => ({
|
||||
key: a.key,
|
||||
name: a.name,
|
||||
}))
|
||||
|
||||
event.component.option(
|
||||
'selectedRowKeys',
|
||||
listValues.filter((a: any) => val.includes(a.key)),
|
||||
)
|
||||
}
|
||||
|
||||
getListValues()
|
||||
}}
|
||||
></DataGrid>
|
||||
{options?.selectionMode === 'multiple' && (
|
||||
<div className="text-center">
|
||||
<Button
|
||||
text="OK"
|
||||
className="mr-2 min-w-[100px]"
|
||||
onClick={() => {
|
||||
e.component.option(
|
||||
'value',
|
||||
gridRef.current?.instance().getSelectedRowKeys().map((a: any) => a.key),
|
||||
)
|
||||
e.component.close()
|
||||
}}
|
||||
></Button>
|
||||
<Button
|
||||
text="Cancel"
|
||||
className="min-w-[100px]"
|
||||
onClick={() => {
|
||||
e.component.close()
|
||||
}}
|
||||
></Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
></DropDownBox>
|
||||
lookup={lookupDto}
|
||||
options={options}
|
||||
editorOptions={editorOptions}
|
||||
onValueChanged={onValueChanged}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,9 @@ import {
|
|||
EditingFormItemDto,
|
||||
FieldCustomValueTypeEnum,
|
||||
GridDto,
|
||||
ListFormCustomizationTypeEnum,
|
||||
PlatformEditorTypes,
|
||||
SubFormTabTypeEnum,
|
||||
} from '@/proxy/form/models'
|
||||
import {
|
||||
getListFormCustomization,
|
||||
postListFormCustomization,
|
||||
} from '@/services/list-form-customization.service'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { executeEditorScript } from '@/utils/editorScriptRuntime'
|
||||
import { Template } from 'devextreme-react/core/template'
|
||||
|
|
@ -58,8 +53,8 @@ import {
|
|||
controlStyleCondition,
|
||||
extractSearchParamsFields,
|
||||
GridExtraFilterState,
|
||||
safeJsonParse,
|
||||
setFormEditingExtraItemValues,
|
||||
setGridPanelColor,
|
||||
} from './Utils'
|
||||
import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
|
||||
import { ImageUploadEditorComponent } from './editors/ImageUploadEditorComponent'
|
||||
|
|
@ -79,6 +74,7 @@ import { Loading } from '@/components/shared'
|
|||
import { useStoreState } from '@/store'
|
||||
import { workflowService } from '@/services/workflow.service'
|
||||
import { NotePanel } from '../form/notes/NotePanel'
|
||||
import { useListFormStateStoring } from './useListFormStateStoring'
|
||||
|
||||
interface GridProps {
|
||||
listFormCode: string
|
||||
|
|
@ -89,8 +85,6 @@ interface GridProps {
|
|||
gridDto?: GridDto
|
||||
}
|
||||
|
||||
const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)' // kullanici tanimli gridState ile islem gormus gridin paneline ait renk
|
||||
|
||||
const isTemporaryDxKey = (key: unknown) => typeof key === 'string' && key.startsWith('_DX_KEY_')
|
||||
|
||||
const getPersistedInsertedKey = (
|
||||
|
|
@ -322,47 +316,12 @@ const Grid = (props: GridProps) => {
|
|||
}
|
||||
}, [searchParams])
|
||||
|
||||
// StateStoring için storageKey'i memoize et
|
||||
const storageKey = useMemo(() => {
|
||||
return gridDto?.gridOptions.stateStoringDto?.storageKey ?? ''
|
||||
}, [gridDto?.gridOptions.stateStoringDto?.storageKey])
|
||||
|
||||
const customSaveState = useCallback(
|
||||
(state: any) => {
|
||||
if (isEditingRef.current) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return postListFormCustomization({
|
||||
listFormCode: listFormCode,
|
||||
customizationType: ListFormCustomizationTypeEnum.GridState,
|
||||
filterName: `list-${storageKey}`,
|
||||
customizationData: JSON.stringify(state),
|
||||
})
|
||||
.then(() => {
|
||||
setGridPanelColor(statedGridPanelColor)
|
||||
toast.push(
|
||||
<Notification type="success" duration={2000}>
|
||||
{translate('::ListForms.ListForm.GridStateSaved')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
},
|
||||
)
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2500}>
|
||||
{translate('::ListForms.ListForm.GridStateSaveError')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
},
|
||||
)
|
||||
throw err
|
||||
})
|
||||
},
|
||||
[listFormCode, storageKey, translate],
|
||||
)
|
||||
const { customSaveState, customLoadState } = useListFormStateStoring({
|
||||
listFormCode,
|
||||
storageKey: gridDto?.gridOptions.stateStoringDto?.storageKey,
|
||||
filterPrefix: 'list',
|
||||
skipSaveRef: isEditingRef,
|
||||
})
|
||||
|
||||
const { filterToolbarData, ...filterData } = useFilters({
|
||||
gridDto,
|
||||
|
|
@ -839,7 +798,9 @@ const Grid = (props: GridProps) => {
|
|||
} else {
|
||||
childEditor?.option('disabled', shouldDisable)
|
||||
}
|
||||
} catch {}
|
||||
} catch (err) {
|
||||
console.log(`Error occurred while updating child editor for field "${childFieldName}":`, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -928,21 +889,6 @@ const Grid = (props: GridProps) => {
|
|||
[gridDto, cascadeFieldsMap, parentToChildrenMap, mode],
|
||||
)
|
||||
|
||||
const customLoadState = useCallback(() => {
|
||||
return getListFormCustomization(
|
||||
listFormCode,
|
||||
ListFormCustomizationTypeEnum.GridState,
|
||||
`list-${storageKey}`,
|
||||
).then((response: any) => {
|
||||
if (response.data?.length > 0) {
|
||||
setGridPanelColor(statedGridPanelColor)
|
||||
return JSON.parse(response.data[0].customizationData)
|
||||
}
|
||||
// Veri yoksa null dön (DevExtreme bunu default state olarak algılar)
|
||||
return null
|
||||
})
|
||||
}, [listFormCode, storageKey])
|
||||
|
||||
useEffect(() => {
|
||||
// React state'i sıfırla - eski değerlerin customLoadState'i erken tetiklemesini önle
|
||||
setGridDataSource(undefined)
|
||||
|
|
@ -1005,7 +951,7 @@ const Grid = (props: GridProps) => {
|
|||
|
||||
let base: any = null
|
||||
if (defaultSearchParams.current) {
|
||||
base = JSON.parse(defaultSearchParams.current)
|
||||
base = safeJsonParse(defaultSearchParams.current, null, 'Search filter parse error:')
|
||||
}
|
||||
|
||||
// Default filter tripletlerini çıkar
|
||||
|
|
@ -1168,7 +1114,7 @@ const Grid = (props: GridProps) => {
|
|||
instance.option('remoteOperations', remoteOps)
|
||||
instance.option('dataSource', gridDataSource)
|
||||
})
|
||||
}, [gridDto, gridDataSource, columnData])
|
||||
}, [gridDto, gridDataSource, columnData, customLoadState])
|
||||
|
||||
// StateStoring'i devre dışı bırak - manuel kaydetme kullanılacak
|
||||
useEffect(() => {
|
||||
|
|
@ -1201,7 +1147,7 @@ const Grid = (props: GridProps) => {
|
|||
const parsed = JSON.parse(rawFilter)
|
||||
const filters = extractSearchParamsFields(parsed)
|
||||
|
||||
const hasFilter = filters.some(([field, op, val]) => field === i.dataField)
|
||||
const hasFilter = filters.some(([field]) => field === i.dataField)
|
||||
|
||||
if (hasFilter) {
|
||||
const existsInExtra = extraFilters.some((f) => f.fieldName === i.dataField && !!f.value)
|
||||
|
|
@ -1211,7 +1157,9 @@ const Grid = (props: GridProps) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
} catch (err) {
|
||||
console.error('EditorOptions parse error:', i.dataField, err)
|
||||
}
|
||||
|
||||
const fieldName = i.dataField.split(':')[0]
|
||||
const listFormField = gridDto?.columnFormats.find((x: any) => x.fieldName === fieldName)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import Container from '@/components/shared/Container'
|
||||
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
|
||||
import { GridDto, ListFormCustomizationTypeEnum } from '@/proxy/form/models'
|
||||
import {
|
||||
getListFormCustomization,
|
||||
postListFormCustomization,
|
||||
} from '@/services/list-form-customization.service'
|
||||
import { postListFormCustomization } from '@/services/list-form-customization.service'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import Chart, { ChartRef, CommonSeriesSettings, Size, Tooltip } from 'devextreme-react/chart'
|
||||
import PivotGrid, {
|
||||
|
|
@ -41,6 +38,7 @@ import { useListFormColumns } from './useListFormColumns'
|
|||
import { useStoreState } from '@/store'
|
||||
import Checkbox from '@/components/ui/Checkbox'
|
||||
import Toolbar, { Item } from 'devextreme-react/toolbar'
|
||||
import { useListFormStateStoring } from './useListFormStateStoring'
|
||||
|
||||
interface PivotProps {
|
||||
listFormCode: string
|
||||
|
|
@ -52,8 +50,6 @@ interface PivotProps {
|
|||
refreshGridDto: () => Promise<void>
|
||||
}
|
||||
|
||||
const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)' // kullanici tanimli gridState ile islem gormus gridin paneline ait renk
|
||||
|
||||
type LookupDisplayValues = Record<string, Map<string, string>>
|
||||
|
||||
const Pivot = (props: PivotProps) => {
|
||||
|
|
@ -72,11 +68,12 @@ const Pivot = (props: PivotProps) => {
|
|||
const [lookupDisplayValues, setLookupDisplayValues] = useState<LookupDisplayValues>({})
|
||||
const [showChart, setShowChart] = useState(false)
|
||||
|
||||
// StateStoring için storageKey'i memoize et
|
||||
const storageKey = useMemo(() => {
|
||||
return gridDto?.gridOptions.stateStoringDto?.storageKey ?? ''
|
||||
}, [gridDto?.gridOptions.stateStoringDto?.storageKey])
|
||||
|
||||
const { customSaveState, customLoadState, storageKey } = useListFormStateStoring({
|
||||
listFormCode,
|
||||
storageKey: gridDto?.gridOptions.stateStoringDto?.storageKey,
|
||||
filterPrefix: 'pivot',
|
||||
showSaveToast: false,
|
||||
})
|
||||
|
||||
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
|
||||
const { getBandedColumns, loadLookupDisplayValues } = useListFormColumns({
|
||||
|
|
@ -212,35 +209,6 @@ const Pivot = (props: PivotProps) => {
|
|||
[listFormCode, translate],
|
||||
)
|
||||
|
||||
// StateStoring fonksiyonlarını ref'e kaydet
|
||||
const customSaveState = useCallback(
|
||||
(state: any) => {
|
||||
return postListFormCustomization({
|
||||
listFormCode: listFormCode,
|
||||
customizationType: ListFormCustomizationTypeEnum.GridState,
|
||||
filterName: `pivot-${storageKey}`,
|
||||
customizationData: JSON.stringify(state),
|
||||
}).then(() => {
|
||||
setGridPanelColor(statedGridPanelColor)
|
||||
})
|
||||
},
|
||||
[listFormCode, storageKey],
|
||||
)
|
||||
|
||||
const customLoadState = useCallback(() => {
|
||||
return getListFormCustomization(
|
||||
listFormCode,
|
||||
ListFormCustomizationTypeEnum.GridState,
|
||||
`pivot-${storageKey}`,
|
||||
).then((response: any) => {
|
||||
if (response.data?.length > 0) {
|
||||
setGridPanelColor(statedGridPanelColor)
|
||||
return JSON.parse(response.data[0].customizationData)
|
||||
}
|
||||
return null
|
||||
})
|
||||
}, [listFormCode, storageKey])
|
||||
|
||||
const savePivotGridState = useCallback(() => {
|
||||
const instance = gridRef.current?.instance()
|
||||
if (!instance) {
|
||||
|
|
|
|||
|
|
@ -6,14 +6,9 @@ import {
|
|||
DbTypeEnum,
|
||||
EditingFormItemDto,
|
||||
GridDto,
|
||||
ListFormCustomizationTypeEnum,
|
||||
PlatformEditorTypes,
|
||||
SubFormTabTypeEnum,
|
||||
} from '@/proxy/form/models'
|
||||
import {
|
||||
getListFormCustomization,
|
||||
postListFormCustomization,
|
||||
} from '@/services/list-form-customization.service'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { captionize } from 'devextreme/core/utils/inflector'
|
||||
import { Template } from 'devextreme-react/core/template'
|
||||
|
|
@ -49,8 +44,8 @@ import {
|
|||
autoNumber,
|
||||
controlStyleCondition,
|
||||
GridExtraFilterState,
|
||||
safeJsonParse,
|
||||
setFormEditingExtraItemValues,
|
||||
setGridPanelColor,
|
||||
} from './Utils'
|
||||
import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
|
||||
import { ImageViewerEditorComponent } from './editors/ImageViewerEditorComponent'
|
||||
|
|
@ -70,6 +65,7 @@ import SubForms from '../form/SubForms'
|
|||
import { ImportDashboard } from '@/components/importManager/ImportDashboard'
|
||||
import { workflowService } from '@/services/workflow.service'
|
||||
import { NotePanel } from '../form/notes/NotePanel'
|
||||
import { useListFormStateStoring } from './useListFormStateStoring'
|
||||
|
||||
interface TreeProps {
|
||||
listFormCode: string
|
||||
|
|
@ -80,8 +76,6 @@ interface TreeProps {
|
|||
gridDto?: GridDto
|
||||
}
|
||||
|
||||
const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)'
|
||||
|
||||
const isTemporaryDxKey = (key: unknown) => typeof key === 'string' && key.startsWith('_DX_KEY_')
|
||||
|
||||
const getPersistedInsertedKey = (e: any, keyFieldName?: string) => {
|
||||
|
|
@ -312,62 +306,12 @@ const Tree = (props: TreeProps) => {
|
|||
}
|
||||
}, [searchParams])
|
||||
|
||||
// StateStoring için storageKey'i memoize et
|
||||
const storageKey = useMemo(() => {
|
||||
return gridDto?.gridOptions.stateStoringDto?.storageKey ?? ''
|
||||
}, [gridDto?.gridOptions.stateStoringDto?.storageKey])
|
||||
|
||||
const customSaveState = useCallback(
|
||||
(state: any) => {
|
||||
if (isEditingRef.current) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return postListFormCustomization({
|
||||
listFormCode: listFormCode,
|
||||
customizationType: ListFormCustomizationTypeEnum.GridState,
|
||||
filterName: `tree-${storageKey}`,
|
||||
customizationData: JSON.stringify(state),
|
||||
})
|
||||
.then(() => {
|
||||
setGridPanelColor(statedGridPanelColor)
|
||||
toast.push(
|
||||
<Notification type="success" duration={2000}>
|
||||
{translate('::ListForms.ListForm.GridStateSaved')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
},
|
||||
)
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2500}>
|
||||
{translate('::ListForms.ListForm.GridStateSaveError')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
},
|
||||
)
|
||||
throw err
|
||||
})
|
||||
},
|
||||
[listFormCode, storageKey, translate],
|
||||
)
|
||||
|
||||
const customLoadState = useCallback(() => {
|
||||
return getListFormCustomization(
|
||||
listFormCode,
|
||||
ListFormCustomizationTypeEnum.GridState,
|
||||
`tree-${storageKey}`,
|
||||
).then((response: any) => {
|
||||
if (response.data?.length > 0) {
|
||||
setGridPanelColor(statedGridPanelColor)
|
||||
return JSON.parse(response.data[0].customizationData)
|
||||
}
|
||||
// Veri yoksa null dön (DevExtreme bunu default state olarak algılar)
|
||||
return null
|
||||
})
|
||||
}, [listFormCode, storageKey])
|
||||
const { customSaveState, customLoadState } = useListFormStateStoring({
|
||||
listFormCode,
|
||||
storageKey: gridDto?.gridOptions.stateStoringDto?.storageKey,
|
||||
filterPrefix: 'tree',
|
||||
skipSaveRef: isEditingRef,
|
||||
})
|
||||
|
||||
const { filterToolbarData, ...filterData } = useFilters({
|
||||
gridDto,
|
||||
|
|
@ -593,7 +537,7 @@ const Tree = (props: TreeProps) => {
|
|||
typeof colFormat.defaultValue === 'string' &&
|
||||
colFormat.defaultValue === '@AUTONUMBER'
|
||||
) {
|
||||
e.data[colFormat.fieldName] = autoNumber
|
||||
e.data[colFormat.fieldName] = autoNumber()
|
||||
} else {
|
||||
e.data[colFormat.fieldName] = colFormat.defaultValue
|
||||
}
|
||||
|
|
@ -631,8 +575,12 @@ const Tree = (props: TreeProps) => {
|
|||
case 'object':
|
||||
try {
|
||||
e.data[colFormat.fieldName] = JSON.parse(val)
|
||||
}
|
||||
catch {}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`Error occurred while parsing JSON for field "${colFormat.fieldName}":`,
|
||||
err,
|
||||
)
|
||||
}
|
||||
break
|
||||
default:
|
||||
e.data[colFormat.fieldName] = val
|
||||
|
|
@ -983,7 +931,7 @@ const Tree = (props: TreeProps) => {
|
|||
|
||||
let base: any = null
|
||||
if (defaultSearchParams.current) {
|
||||
base = JSON.parse(defaultSearchParams.current)
|
||||
base = safeJsonParse(defaultSearchParams.current, null, 'Search filter parse error:')
|
||||
}
|
||||
|
||||
const baseTriplets = extractSearchParamsFields(base)
|
||||
|
|
@ -1063,7 +1011,7 @@ const Tree = (props: TreeProps) => {
|
|||
console.error('State load error:', err)
|
||||
})
|
||||
}
|
||||
}, [gridDto, treeListDataSource, columnData])
|
||||
}, [gridDto, treeListDataSource, columnData, customLoadState])
|
||||
|
||||
// StateStoring'i devre dışı bırak - manuel kaydetme kullanılacak
|
||||
useEffect(() => {
|
||||
|
|
@ -1479,7 +1427,9 @@ const Tree = (props: TreeProps) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
} catch (err){
|
||||
console.error('EditorOptions parse error:', i.dataField, err)
|
||||
}
|
||||
|
||||
const fieldName = i.dataField.split(':')[0]
|
||||
const listFormField = gridDto.columnFormats.find(
|
||||
|
|
|
|||
|
|
@ -22,6 +22,22 @@ export function extractSearchParamsFields(filter: any): [string, string, any][]
|
|||
return filter.flatMap((f) => extractSearchParamsFields(f))
|
||||
}
|
||||
|
||||
export function safeJsonParse<T>(value: unknown, fallback: T, errorMessage?: string): T {
|
||||
if (typeof value !== 'string' || value.trim() === '') {
|
||||
return fallback
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value) as T
|
||||
} catch (error) {
|
||||
if (errorMessage) {
|
||||
console.error(errorMessage, error)
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
// sayfaya dinamik olarak css style ekler
|
||||
export function addCss(css: string) {
|
||||
if (css.startsWith('http') || css.startsWith('/')) {
|
||||
|
|
@ -53,42 +69,47 @@ export function controlStyleCondition(data: any, fieldName: any, style: any) {
|
|||
// condition sadece data satırlarında yapılır
|
||||
if (style.rowType != 'data') return true
|
||||
|
||||
const value = data?.[fieldName]
|
||||
const conditionValue = style.conditionValue
|
||||
const stringValue = value == null ? '' : String(value)
|
||||
const stringConditionValue = conditionValue == null ? '' : String(conditionValue)
|
||||
const getConditionValues = () =>
|
||||
safeJsonParse<any[]>(conditionValue, [], 'Style condition parse error:')
|
||||
|
||||
switch (style.condition) {
|
||||
case '=':
|
||||
return style.conditionValue == data[fieldName]
|
||||
return conditionValue == value
|
||||
case '<>':
|
||||
return style.conditionValue != data[fieldName]
|
||||
return conditionValue != value
|
||||
case '<':
|
||||
return style.conditionValue > data[fieldName]
|
||||
return conditionValue > value
|
||||
case '<=':
|
||||
return style.conditionValue >= data[fieldName]
|
||||
return conditionValue >= value
|
||||
case '>':
|
||||
return style.conditionValue < data[fieldName]
|
||||
return conditionValue < value
|
||||
case '>=':
|
||||
return style.conditionValue <= data[fieldName]
|
||||
return conditionValue <= value
|
||||
case 'contains':
|
||||
return data[fieldName].includes(style.conditionValue)
|
||||
return stringValue.includes(stringConditionValue)
|
||||
case 'endswith':
|
||||
return data[fieldName].endsWith(style.conditionValue)
|
||||
return stringValue.endsWith(stringConditionValue)
|
||||
case 'isblank':
|
||||
return data[fieldName] == null
|
||||
return value == null || value === ''
|
||||
case 'isnotblank':
|
||||
return data[fieldName] != null
|
||||
return value != null && value !== ''
|
||||
case 'notcontains':
|
||||
return data[fieldName].indexOf(style.conditionValue) === -1
|
||||
return !stringValue.includes(stringConditionValue)
|
||||
case 'startswith':
|
||||
return data[fieldName].startsWith(style.conditionValue)
|
||||
return stringValue.startsWith(stringConditionValue)
|
||||
case 'between':
|
||||
return false // TODO:
|
||||
case 'anyof': // anyof icin style.conditionValue obje olmalıdır: ['Val1','Val2']
|
||||
return JSON.parse(style.conditionValue).some(data[fieldName])
|
||||
return getConditionValues().some((el) => el == value)
|
||||
case 'noneof': // noneof icin style.conditionValue obje olmalıdır: ['Val1','Val2']
|
||||
return JSON.parse(style.conditionValue).findIndex((el: any) => el == data[fieldName]) < 0
|
||||
|
||||
return getConditionValues().every((el) => el != value)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function pivotFieldConvertDataType(fieldDbType?: DataType): PivotGridDataType {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import Button from 'devextreme-react/button'
|
||||
import DataGrid from 'devextreme-react/data-grid'
|
||||
import DropDownBox from 'devextreme-react/drop-down-box'
|
||||
import dxDataGrid from 'devextreme/ui/data_grid'
|
||||
import { ReactElement } from 'react'
|
||||
import { SharedGridBoxEditor } from '@/views/shared/SharedGridBoxEditor'
|
||||
|
||||
const GridBoxEditorComponent = (cellElement: any): ReactElement => {
|
||||
const col = cellElement.column
|
||||
|
|
@ -10,9 +7,6 @@ const GridBoxEditorComponent = (cellElement: any): ReactElement => {
|
|||
return <></>
|
||||
}
|
||||
const options = col.extras.gridBoxOptions
|
||||
let gridRef: dxDataGrid<any, any>
|
||||
|
||||
let val = Array.isArray(cellElement.value) ? cellElement.value : [cellElement.value]
|
||||
const lookup = col?.lookup
|
||||
const dataSource: any =
|
||||
typeof lookup?.dataSource === 'function'
|
||||
|
|
@ -20,97 +14,16 @@ const GridBoxEditorComponent = (cellElement: any): ReactElement => {
|
|||
: (lookup?.dataSource ?? [])
|
||||
|
||||
return (
|
||||
<DropDownBox
|
||||
{...col.extras.editorOptions}
|
||||
dropDownOptions={{ width: options.width, hideOnOutsideClick: true }}
|
||||
<SharedGridBoxEditor
|
||||
value={cellElement.value}
|
||||
dataSource={dataSource}
|
||||
valueExpr={lookup?.valueExpr}
|
||||
displayExpr={lookup?.displayExpr}
|
||||
showClearButton={options?.showClearButton}
|
||||
acceptCustomValue={options?.acceptCustomValue}
|
||||
onValueChanged={(e) => {
|
||||
if (e.value === null) {
|
||||
val = null
|
||||
cellElement.setValue(val)
|
||||
|
||||
e.component.close()
|
||||
|
||||
if (gridRef) {
|
||||
gridRef.deselectAll()
|
||||
}
|
||||
}
|
||||
}}
|
||||
onContentReady={(e) => {
|
||||
e.component.option('value', val)
|
||||
}}
|
||||
contentRender={(e) => (
|
||||
<>
|
||||
<DataGrid
|
||||
dataSource={dataSource}
|
||||
filterRow={{
|
||||
visible: options?.filterRowVisible,
|
||||
applyFilter: 'auto',
|
||||
}}
|
||||
columns={options?.columns.map((c: string) => c.toLowerCase())} //AliBaba -> alibaba
|
||||
hoverStateEnabled={true}
|
||||
height={options?.height}
|
||||
selection={{
|
||||
mode: options?.selectionMode,
|
||||
}}
|
||||
onContentReady={(event) => {
|
||||
gridRef = event.component as dxDataGrid<any, any>
|
||||
}}
|
||||
onRowClick={(event) => {
|
||||
if (options?.selectionMode === 'single') {
|
||||
e.component.option('value', event.data.key)
|
||||
cellElement.setValue(event.data.key)
|
||||
e.component.close()
|
||||
}
|
||||
}}
|
||||
onEditorPrepared={(event) => {
|
||||
const getListValues = async () => {
|
||||
const data = await dataSource?.load()
|
||||
|
||||
const listValues = data.map((a: any) => ({
|
||||
key: a.key,
|
||||
name: a.name,
|
||||
}))
|
||||
|
||||
event.component.option(
|
||||
'selectedRowKeys',
|
||||
listValues.filter((a: any) => val.includes(a.key)),
|
||||
)
|
||||
}
|
||||
|
||||
getListValues()
|
||||
}}
|
||||
></DataGrid>
|
||||
{options?.selectionMode === 'multiple' && (
|
||||
<div className="text-center">
|
||||
<Button
|
||||
text="OK"
|
||||
className="mr-2 min-w-[100px]"
|
||||
onClick={() => {
|
||||
val = gridRef?.getSelectedRowKeys().map((a: any) => a.key)
|
||||
|
||||
e.component.option('value', val)
|
||||
cellElement.setValue(val)
|
||||
|
||||
e.component.close()
|
||||
}}
|
||||
></Button>
|
||||
<Button
|
||||
text="Cancel"
|
||||
className="min-w-[100px]"
|
||||
onClick={() => {
|
||||
e.component.close()
|
||||
}}
|
||||
></Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
></DropDownBox>
|
||||
lookup={lookup}
|
||||
options={options}
|
||||
editorOptions={col.extras.editorOptions}
|
||||
dropDownOptions={{ width: options?.width, hideOnOutsideClick: true }}
|
||||
onCommit={(nextValue) => cellElement.setValue(nextValue)}
|
||||
setInitialValueOnContentReady
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { PivotGridRef } from 'devextreme-react/pivot-grid'
|
|||
import CustomStore from 'devextreme/data/custom_store'
|
||||
import { MutableRefObject, useCallback } from 'react'
|
||||
import { layoutTypes, ListViewLayoutType } from '@/views/admin/listForm/edit/types'
|
||||
import { getLoadOptions, getServiceAddress, setGridPanelColor } from './Utils'
|
||||
import { getLoadOptions, getServiceAddress, safeJsonParse, setGridPanelColor } from './Utils'
|
||||
import { GridOptionsDto } from '@/proxy/form/models'
|
||||
import { GridColumnData } from './GridColumnData'
|
||||
import { dynamicFetch } from '@/services/form.service'
|
||||
|
|
@ -72,11 +72,7 @@ const useListFormCustomDataSource = ({
|
|||
// URL'den sort parametresini al ve loadOptions'a ekle
|
||||
const urlSort = searchParams?.get('sort')
|
||||
if (urlSort && !loadOptions.sort) {
|
||||
try {
|
||||
loadOptions.sort = JSON.parse(urlSort)
|
||||
} catch (e) {
|
||||
console.error('Sort parse error:', e)
|
||||
}
|
||||
loadOptions.sort = safeJsonParse(urlSort, undefined, 'Sort parse error:')
|
||||
}
|
||||
|
||||
const parameters = getLoadOptions(loadOptions, {
|
||||
|
|
@ -125,9 +121,11 @@ const useListFormCustomDataSource = ({
|
|||
Object.assign(parameters, chartParameters)
|
||||
}
|
||||
// 1. Default filter'ı al
|
||||
const defaultFilter = searchParams?.get('filter')
|
||||
? JSON.parse(searchParams.get('filter')!)
|
||||
: null
|
||||
const defaultFilter = safeJsonParse(
|
||||
searchParams?.get('filter'),
|
||||
null,
|
||||
'Search filter parse error:',
|
||||
)
|
||||
|
||||
let combinedFilter: any = parameters.filter
|
||||
|
||||
|
|
@ -312,9 +310,11 @@ const useListFormCustomDataSource = ({
|
|||
})
|
||||
|
||||
// 1. Default filter'ı al
|
||||
const defaultFilter = searchParams?.get('filter')
|
||||
? JSON.parse(searchParams.get('filter')!)
|
||||
: null
|
||||
const defaultFilter = safeJsonParse(
|
||||
searchParams?.get('filter'),
|
||||
null,
|
||||
'Search filter parse error:',
|
||||
)
|
||||
|
||||
let combinedFilter: any = parameters.filter
|
||||
|
||||
|
|
|
|||
188
ui/src/views/list/useListFormStateStoring.tsx
Normal file
188
ui/src/views/list/useListFormStateStoring.tsx
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
import { Notification, toast } from '@/components/ui'
|
||||
import { ListFormCustomizationTypeEnum } from '@/proxy/form/models'
|
||||
import {
|
||||
getListFormCustomization,
|
||||
postListFormCustomization,
|
||||
} from '@/services/list-form-customization.service'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { MutableRefObject, useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { setGridPanelColor } from './Utils'
|
||||
|
||||
const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)'
|
||||
const defaultSaveDebounceMs = 500
|
||||
|
||||
type PendingSave = {
|
||||
promise: Promise<void>
|
||||
resolve: () => void
|
||||
reject: (reason?: unknown) => void
|
||||
}
|
||||
|
||||
type UseListFormStateStoringParams = {
|
||||
listFormCode: string
|
||||
storageKey?: string | null
|
||||
filterPrefix: 'list' | 'tree' | 'pivot'
|
||||
skipSaveRef?: MutableRefObject<boolean>
|
||||
showSaveToast?: boolean
|
||||
saveDebounceMs?: number
|
||||
}
|
||||
|
||||
export const useListFormStateStoring = ({
|
||||
listFormCode,
|
||||
storageKey,
|
||||
filterPrefix,
|
||||
skipSaveRef,
|
||||
showSaveToast = true,
|
||||
saveDebounceMs = defaultSaveDebounceMs,
|
||||
}: UseListFormStateStoringParams) => {
|
||||
const { translate } = useLocalization()
|
||||
const normalizedStorageKey = useMemo(() => storageKey?.trim() ?? '', [storageKey])
|
||||
const filterName = useMemo(
|
||||
() => (normalizedStorageKey ? `${filterPrefix}-${normalizedStorageKey}` : ''),
|
||||
[filterPrefix, normalizedStorageKey],
|
||||
)
|
||||
|
||||
const saveTimerRef = useRef<ReturnType<typeof setTimeout>>()
|
||||
const pendingStateRef = useRef<any>()
|
||||
const pendingSaveRef = useRef<PendingSave>()
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (saveTimerRef.current) {
|
||||
clearTimeout(saveTimerRef.current)
|
||||
}
|
||||
pendingSaveRef.current?.resolve()
|
||||
saveTimerRef.current = undefined
|
||||
pendingSaveRef.current = undefined
|
||||
pendingStateRef.current = undefined
|
||||
}
|
||||
}, [filterName, listFormCode])
|
||||
|
||||
const showSaveSuccessToast = useCallback(() => {
|
||||
if (!showSaveToast) {
|
||||
return
|
||||
}
|
||||
|
||||
toast.push(
|
||||
<Notification type="success" duration={2000}>
|
||||
{translate('::ListForms.ListForm.GridStateSaved')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
},
|
||||
)
|
||||
}, [showSaveToast, translate])
|
||||
|
||||
const showSaveErrorToast = useCallback(() => {
|
||||
if (!showSaveToast) {
|
||||
return
|
||||
}
|
||||
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2500}>
|
||||
{translate('::ListForms.ListForm.GridStateSaveError')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
},
|
||||
)
|
||||
}, [showSaveToast, translate])
|
||||
|
||||
const customSaveState = useCallback(
|
||||
(state: any) => {
|
||||
if (skipSaveRef?.current || !listFormCode || !filterName) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
pendingStateRef.current = state
|
||||
|
||||
if (!pendingSaveRef.current) {
|
||||
let resolveSave!: () => void
|
||||
let rejectSave!: (reason?: unknown) => void
|
||||
|
||||
const promise = new Promise<void>((resolve, reject) => {
|
||||
resolveSave = resolve
|
||||
rejectSave = reject
|
||||
})
|
||||
|
||||
pendingSaveRef.current = {
|
||||
promise,
|
||||
resolve: resolveSave,
|
||||
reject: rejectSave,
|
||||
}
|
||||
}
|
||||
|
||||
if (saveTimerRef.current) {
|
||||
clearTimeout(saveTimerRef.current)
|
||||
}
|
||||
|
||||
saveTimerRef.current = setTimeout(() => {
|
||||
const pendingSave = pendingSaveRef.current
|
||||
const pendingState = pendingStateRef.current
|
||||
|
||||
saveTimerRef.current = undefined
|
||||
pendingSaveRef.current = undefined
|
||||
pendingStateRef.current = undefined
|
||||
|
||||
let customizationData = ''
|
||||
try {
|
||||
customizationData = JSON.stringify(pendingState)
|
||||
} catch (err) {
|
||||
showSaveErrorToast()
|
||||
pendingSave?.reject(err)
|
||||
return
|
||||
}
|
||||
|
||||
postListFormCustomization({
|
||||
listFormCode,
|
||||
customizationType: ListFormCustomizationTypeEnum.GridState,
|
||||
filterName,
|
||||
customizationData,
|
||||
})
|
||||
.then(() => {
|
||||
setGridPanelColor(statedGridPanelColor)
|
||||
showSaveSuccessToast()
|
||||
pendingSave?.resolve()
|
||||
})
|
||||
.catch((err) => {
|
||||
showSaveErrorToast()
|
||||
pendingSave?.reject(err)
|
||||
})
|
||||
}, saveDebounceMs)
|
||||
|
||||
return pendingSaveRef.current.promise
|
||||
},
|
||||
[filterName, listFormCode, saveDebounceMs, showSaveErrorToast, showSaveSuccessToast, skipSaveRef],
|
||||
)
|
||||
|
||||
const customLoadState = useCallback(() => {
|
||||
if (!listFormCode || !filterName) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
|
||||
return getListFormCustomization(
|
||||
listFormCode,
|
||||
ListFormCustomizationTypeEnum.GridState,
|
||||
filterName,
|
||||
).then((response: any) => {
|
||||
const customizationData = response.data?.[0]?.customizationData
|
||||
if (!customizationData) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const state = JSON.parse(customizationData)
|
||||
setGridPanelColor(statedGridPanelColor)
|
||||
return state
|
||||
} catch (err) {
|
||||
console.error('Grid state parse error:', err)
|
||||
return null
|
||||
}
|
||||
})
|
||||
}, [filterName, listFormCode])
|
||||
|
||||
return {
|
||||
customSaveState,
|
||||
customLoadState,
|
||||
storageKey: normalizedStorageKey,
|
||||
}
|
||||
}
|
||||
160
ui/src/views/shared/SharedGridBoxEditor.tsx
Normal file
160
ui/src/views/shared/SharedGridBoxEditor.tsx
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import Button from 'devextreme-react/button'
|
||||
import DataGrid, { DataGridRef } from 'devextreme-react/data-grid'
|
||||
import DropDownBox from 'devextreme-react/drop-down-box'
|
||||
import { ReactElement, useRef } from 'react'
|
||||
import { GridBoxOptionsDto } from '@/proxy/form/models'
|
||||
|
||||
type GridBoxLookup = {
|
||||
valueExpr?: string
|
||||
displayExpr?: string
|
||||
}
|
||||
|
||||
type SharedGridBoxEditorProps = {
|
||||
value: any
|
||||
dataSource: any
|
||||
lookup?: GridBoxLookup
|
||||
options?: GridBoxOptionsDto
|
||||
editorOptions?: any
|
||||
dropDownOptions?: any
|
||||
onValueChanged?: (value: any) => void
|
||||
onCommit?: (value: any) => void
|
||||
setInitialValueOnContentReady?: boolean
|
||||
}
|
||||
|
||||
const normalizeValue = (value: any) => (Array.isArray(value) ? value : [value])
|
||||
|
||||
const includesValue = (value: any, itemKey: any) =>
|
||||
Array.isArray(value) ? value.includes(itemKey) : value === itemKey
|
||||
|
||||
const loadDataSourceItems = async (dataSource: any) => {
|
||||
if (typeof dataSource?.load === 'function') {
|
||||
return await dataSource.load()
|
||||
}
|
||||
|
||||
if (Array.isArray(dataSource)) {
|
||||
return dataSource
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
const SharedGridBoxEditor = ({
|
||||
value,
|
||||
dataSource,
|
||||
lookup,
|
||||
options,
|
||||
editorOptions,
|
||||
dropDownOptions,
|
||||
onValueChanged,
|
||||
onCommit,
|
||||
setInitialValueOnContentReady,
|
||||
}: SharedGridBoxEditorProps): ReactElement => {
|
||||
const gridRef = useRef<DataGridRef>(null)
|
||||
const currentValueRef = useRef<any>(normalizeValue(value))
|
||||
currentValueRef.current = normalizeValue(value)
|
||||
|
||||
const commitValue = (nextValue: any) => {
|
||||
currentValueRef.current = nextValue
|
||||
onCommit?.(nextValue)
|
||||
}
|
||||
|
||||
return (
|
||||
<DropDownBox
|
||||
{...editorOptions}
|
||||
value={currentValueRef.current}
|
||||
dataSource={dataSource}
|
||||
valueExpr={lookup?.valueExpr}
|
||||
displayExpr={lookup?.displayExpr}
|
||||
dropDownOptions={dropDownOptions}
|
||||
showClearButton={options?.showClearButton}
|
||||
acceptCustomValue={options?.acceptCustomValue}
|
||||
onContentReady={(e) => {
|
||||
if (setInitialValueOnContentReady) {
|
||||
e.component.option('value', currentValueRef.current)
|
||||
}
|
||||
|
||||
editorOptions?.onContentReady?.(e)
|
||||
}}
|
||||
onValueChanged={(e) => {
|
||||
const nextValue = typeof e.value === 'string' ? e.value.trim() : e.value
|
||||
currentValueRef.current = nextValue
|
||||
onValueChanged?.(nextValue)
|
||||
|
||||
if (e.value === null) {
|
||||
commitValue(null)
|
||||
e.component.close()
|
||||
gridRef.current?.instance().deselectAll()
|
||||
}
|
||||
}}
|
||||
contentRender={(dropDown) => (
|
||||
<>
|
||||
<DataGrid
|
||||
ref={gridRef}
|
||||
dataSource={dataSource}
|
||||
key={lookup?.valueExpr}
|
||||
filterRow={{
|
||||
visible: options?.filterRowVisible,
|
||||
applyFilter: 'auto',
|
||||
}}
|
||||
columns={options?.columns?.map((column: string) => column.toLowerCase())}
|
||||
hoverStateEnabled={true}
|
||||
height={options?.height}
|
||||
selection={{
|
||||
mode: options?.selectionMode,
|
||||
}}
|
||||
onRowClick={(event) => {
|
||||
if (options?.selectionMode === 'single') {
|
||||
const nextValue = event.data.key
|
||||
dropDown.component.option('value', nextValue)
|
||||
commitValue(nextValue)
|
||||
dropDown.component.close()
|
||||
}
|
||||
}}
|
||||
onEditorPrepared={(event) => {
|
||||
const selectCurrentRows = async () => {
|
||||
const data = await loadDataSourceItems(dataSource)
|
||||
const selectedRows = data
|
||||
.map((item: any) => ({
|
||||
key: item.key,
|
||||
name: item.name,
|
||||
}))
|
||||
.filter((item: any) => includesValue(currentValueRef.current, item.key))
|
||||
|
||||
event.component.option('selectedRowKeys', selectedRows)
|
||||
}
|
||||
|
||||
selectCurrentRows()
|
||||
}}
|
||||
/>
|
||||
{options?.selectionMode === 'multiple' && (
|
||||
<div className="text-center">
|
||||
<Button
|
||||
text="OK"
|
||||
className="mr-2 min-w-[100px]"
|
||||
onClick={() => {
|
||||
const selectedValue = gridRef.current
|
||||
?.instance()
|
||||
.getSelectedRowKeys()
|
||||
.map((item: any) => item.key)
|
||||
|
||||
dropDown.component.option('value', selectedValue)
|
||||
commitValue(selectedValue)
|
||||
dropDown.component.close()
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
text="Cancel"
|
||||
className="min-w-[100px]"
|
||||
onClick={() => {
|
||||
dropDown.component.close()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { SharedGridBoxEditor }
|
||||
Loading…
Reference in a new issue