IsLookupSelfSourced ve Widget tazelendi
This commit is contained in:
parent
6d5071237a
commit
c62e2c8586
15 changed files with 271 additions and 39 deletions
|
|
@ -428,6 +428,10 @@ Sorgu ekranın bağlantısında (`IsTenant` + `DataSourceCode` ile çözülen, `
|
|||
ve isteğe bağlı `Value` ifadesi; alt başlık ekranın adıdır, değer tip çakışmasın diye metne
|
||||
çevrilir. Gruplamalı sorgu için `DefaultWidgetsJson(sql, kartSayısı)`. Genel kart başlıkları
|
||||
`App.Widget.*` dil anahtarlarındadır.
|
||||
- Kartlar GridDto ile gelir; Grid, Tree, Card, Gantt ve Scheduler görünümünde Yenile düğmesi ve
|
||||
satır ekleme/güncelleme/silme kartları yeniden çeker (`useListFormWidgets`). Yalnızca widget
|
||||
listesi değişir, GridDto değiştirilmez — görünüm durumu (sayfa, seçim) korunur. Kart sorgusu
|
||||
ekranın filtresine bağlı olmadığı için filtre değişikliği kartları yenilemez.
|
||||
|
||||
### 5.4 `LayoutJson` (`LayoutDto`)
|
||||
|
||||
|
|
|
|||
10
.github/instructions/lowcode.instructions.md
vendored
10
.github/instructions/lowcode.instructions.md
vendored
|
|
@ -1207,6 +1207,16 @@ anahtarları listeler; ortak anahtarlar (§4.3.3) her editörde geçerlidir.
|
|||
hata vermez, **sessizce yok sayılır** — bu yüzden `columns` bir dizi, `maxDisplayedTags` bir
|
||||
sayı, `multiple` bir boolean olmalıdır.
|
||||
|
||||
Lookup kolonu hücre metnini açılışta yüklenen lookup listesinden bulur. Lookup'ı ekranın kendi
|
||||
kayıt kaynağından beslenen kolonda (ör. `SELECT DISTINCT CompanyName … FROM Hr_T_Visitor` ile
|
||||
Autocomplete) kaydedilen yeni değer bu listede yoktur. Sunucu bu kolonları ekran açılırken
|
||||
işaretler (`ColumnFormatDto.IsLookupSelfSourced`: Query lookup'ının FROM/JOIN nesnesi ekranın
|
||||
`SelectCommand` nesnesiyle aynı; yalnızca `true` yazılır, seed dosyasına girmez). Grid, Tree ve
|
||||
Card görünümünde satır eklenince/güncellenince yalnızca işaretli kolonlar ve yalnızca kaydedilen
|
||||
değer listede yoksa lookup yeniden yüklenir (`refreshLookupsForRow`) — hücre, editörün açılır
|
||||
listesi ve filtre satırı sayfa yenilenmeden güncellenir. Başka kaynaktan beslenen lookup, statik
|
||||
veri, WebService ve cascade lookup için kayıtta hiç lookup isteği atılmaz.
|
||||
|
||||
**Lookup zorunlu** yazan editörlerde `LookupDataSourceType` + `LookupQuery` + `ValueExpr` +
|
||||
`DisplayExpr` dolu olmalıdır; aksi hâlde alan boş bir liste gösterir:
|
||||
|
||||
|
|
|
|||
|
|
@ -184,6 +184,14 @@ public class ColumnFormatDto : AuditedEntityDto<Guid>
|
|||
set { LookupJson = JsonSerializer.Serialize(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lookup sorgusu ekranın kendi kayıt kaynağını okuyor; kaydedilen yeni değer lookup listesine
|
||||
/// ancak kayıttan sonra girdiği için istemci lookup'ı yalnızca bu alanlarda yeniler. Ekran
|
||||
/// açılırken hesaplanır; false yazılmadığı için seed dosyasına girmez.
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public bool IsLookupSelfSourced { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string ValidationRuleJson { get; set; }
|
||||
public List<ValidationRuleDto> ValidationRuleDto
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Authorization;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Sozsoft.Platform.DynamicData;
|
||||
using Sozsoft.Platform.Entities;
|
||||
using Sozsoft.Platform.Enums;
|
||||
using Sozsoft.Platform.Queries;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Users;
|
||||
|
|
@ -282,6 +283,33 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
|||
return Widgets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query lookup'ı ekranın veri kaynağındaki bir nesneyi (FROM/JOIN) okuyan alanları işaretler.
|
||||
/// WebService lookup'ının kaynağı sorgudan okunamadığı için dışarıda kalır.
|
||||
/// </summary>
|
||||
private static void MarkSelfSourcedLookups(ListForm listForm, List<ColumnFormatDto> columnFormats)
|
||||
{
|
||||
var sourceObjects = listForm.SelectCommandType == SelectCommandTypeEnum.Query
|
||||
? WizardExportCollector.ExtractQueryObjectNames(listForm.SelectCommand).ToHashSet(StringComparer.OrdinalIgnoreCase)
|
||||
: new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (listForm.SelectCommandType != SelectCommandTypeEnum.Query
|
||||
&& WizardExportCollector.NormalizeDatabaseObjectName(listForm.SelectCommand) is { } objectName)
|
||||
{
|
||||
sourceObjects.Add(objectName);
|
||||
}
|
||||
|
||||
if (sourceObjects.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var columnFormat in columnFormats.Where(c => !string.IsNullOrWhiteSpace(c.LookupJson)))
|
||||
{
|
||||
var lookup = columnFormat.LookupDto;
|
||||
columnFormat.IsLookupSelfSourced = lookup.DataSourceType == UiLookupDataSourceTypeEnum.Query
|
||||
&& WizardExportCollector.ExtractQueryObjectNames(lookup.LookupQuery).Any(sourceObjects.Contains);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<GridDto> GetGridAsync(string ListFormCode)
|
||||
{
|
||||
//Izin logic process
|
||||
|
|
@ -306,6 +334,7 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
|||
ColumnFormats = ObjectMapper.Map<List<ListFormField>, List<ColumnFormatDto>>(fields),
|
||||
Widgets = await GetWidgetsAsync(listForm)
|
||||
};
|
||||
MarkSelfSourcedLookups(listForm, result.ColumnFormats);
|
||||
|
||||
//Workflow kriterlerini alıp GridOptionsDto içine atıyoruz ki frontend'de kullanabilelim
|
||||
var workflowDto = result.GridOptions.WorkflowDto;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -227,6 +227,8 @@ export interface ColumnFormatDto extends AuditedEntityDto<string> {
|
|||
columnGroupSummaryDto: ColumnTotalSummaryDto
|
||||
lookupJson?: string
|
||||
lookupDto: LookupDto
|
||||
/** Lookup ekranın kendi kayıt kaynağından besleniyor; kayıttan sonra lookup yenilenir. */
|
||||
isLookupSelfSourced?: boolean
|
||||
validationRuleJson?: string
|
||||
validationRuleDto: ValidationRuleDto[]
|
||||
columnStylingJson?: string
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ import {
|
|||
import { getValueByField, resolveEditorType, withEditorTypeDefaults } from './shared/editingForm'
|
||||
import { createEditingFormHandlers } from './shared/editingFormScripts'
|
||||
import { flattenColumns } from './shared/columns'
|
||||
import { getInitialExtraFilters, mergeFilterTriplets } from './shared/hooks'
|
||||
import { getInitialExtraFilters, mergeFilterTriplets, useListFormWidgets } from './shared/hooks'
|
||||
import { useFilters } from './useFilters'
|
||||
import { useListFormColumns } from './useListFormColumns'
|
||||
import { useListFormCustomDataSource } from './useListFormCustomDataSource'
|
||||
|
|
@ -373,6 +373,7 @@ const CardView = (props: CardViewProps) => {
|
|||
const [isPopupFullScreen, setIsPopupFullScreen] = useState(false)
|
||||
const [formData, setFormData] = useState<Record<string, any>>()
|
||||
const widgetGroupRef = useRef<HTMLDivElement>(null)
|
||||
const { widgets, refreshWidgets } = useListFormWidgets(listFormCode, gridDto)
|
||||
const formDataRef = useRef<Record<string, any> | undefined>(undefined)
|
||||
useEffect(() => {
|
||||
formDataRef.current = formData
|
||||
|
|
@ -447,7 +448,12 @@ const CardView = (props: CardViewProps) => {
|
|||
;(cardDataSource?.store() as any)?.invalidateSelectCache?.()
|
||||
const reload = cardDataSource?.reload()
|
||||
Promise.resolve(reload).finally(() => refreshParentData?.())
|
||||
}, [refreshParentData])
|
||||
void refreshWidgets()
|
||||
}, [refreshParentData, refreshWidgets])
|
||||
const onCardChanged = useCallback(() => {
|
||||
refreshParentData?.()
|
||||
void refreshWidgets()
|
||||
}, [refreshParentData, refreshWidgets])
|
||||
const setCardColumnCount = useCallback(
|
||||
(cardColumnCount: number) => {
|
||||
setListState({
|
||||
|
|
@ -477,7 +483,7 @@ const CardView = (props: CardViewProps) => {
|
|||
[gridDto, listFormCode],
|
||||
)
|
||||
|
||||
const { getBandedColumns, loadLookupDisplayValues } = useListFormColumns({
|
||||
const { getBandedColumns, loadLookupDisplayValues, refreshLookupsForRow } = useListFormColumns({
|
||||
gridDto,
|
||||
listFormCode,
|
||||
isSubForm,
|
||||
|
|
@ -729,6 +735,17 @@ const CardView = (props: CardViewProps) => {
|
|||
]).then(([values, cascadeValues]) => applyLookupDisplayValues({ ...values, ...cascadeValues }))
|
||||
}, [applyLookupDisplayValues, getLookupColumns, loadLookupDisplayValues])
|
||||
|
||||
// Kart metinleri lookup haritasından okunur; yenilenen lookup haritaya işlenip kartlar yeniden çizilir.
|
||||
const refreshCardLookups = useCallback(
|
||||
async (instance: any, rowData?: Record<string, any>) => {
|
||||
if (!(await refreshLookupsForRow(instance, rowData))) return
|
||||
|
||||
applyLookupDisplayValues(await loadLookupDisplayValues(getLookupColumns()))
|
||||
instance.repaint?.()
|
||||
},
|
||||
[applyLookupDisplayValues, getLookupColumns, loadLookupDisplayValues, refreshLookupsForRow],
|
||||
)
|
||||
|
||||
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef: cardViewRef })
|
||||
const dataSource = useMemo<CustomStore<any, any> | undefined>(() => {
|
||||
if (!gridDto) return undefined
|
||||
|
|
@ -1102,8 +1119,8 @@ const CardView = (props: CardViewProps) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div ref={widgetGroupRef} className={(gridDto?.widgets?.length ?? 0) > 0 ? 'mt-2' : ''}>
|
||||
<WidgetGroup widgetGroups={gridDto?.widgets ?? []} />
|
||||
<div ref={widgetGroupRef} className={widgets.length > 0 ? 'mt-2' : ''}>
|
||||
<WidgetGroup widgetGroups={widgets} />
|
||||
</div>
|
||||
|
||||
<Container className={DX_CLASSNAMES}>
|
||||
|
|
@ -1162,9 +1179,15 @@ const CardView = (props: CardViewProps) => {
|
|||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}}
|
||||
onCardInserted={() => refreshParentData?.()}
|
||||
onCardUpdated={() => refreshParentData?.()}
|
||||
onCardRemoved={() => refreshParentData?.()}
|
||||
onCardInserted={(e) => {
|
||||
onCardChanged()
|
||||
void refreshCardLookups(e.component, e.data)
|
||||
}}
|
||||
onCardUpdated={(e) => {
|
||||
onCardChanged()
|
||||
void refreshCardLookups(e.component, e.data)
|
||||
}}
|
||||
onCardRemoved={onCardChanged}
|
||||
>
|
||||
<CardHeader template={renderCardHeader} />
|
||||
<Editing
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import { useFilters } from './useFilters'
|
|||
import {
|
||||
useListFormCustomSources,
|
||||
useListFormGridDto,
|
||||
useListFormWidgets,
|
||||
useWidgetGroupHeight,
|
||||
} from './shared/hooks'
|
||||
import GridFilterDialogs from './GridFilterDialogs'
|
||||
|
|
@ -101,7 +102,8 @@ const GanttView = (props: GanttViewProps) => {
|
|||
const pendingStateRef = useRef<Record<string, any> | undefined>(undefined)
|
||||
const loadedStateKeyRef = useRef('')
|
||||
|
||||
const widgetGroupHeight = useWidgetGroupHeight(widgetGroupRef, gridDto?.widgets)
|
||||
const { widgets, refreshWidgets } = useListFormWidgets(listFormCode, gridDto)
|
||||
const widgetGroupHeight = useWidgetGroupHeight(widgetGroupRef, widgets)
|
||||
useListFormCustomSources(gridDto)
|
||||
|
||||
const { customSaveState, customLoadState, storageKey } = useListFormStateStoring({
|
||||
|
|
@ -249,9 +251,10 @@ const GanttView = (props: GanttViewProps) => {
|
|||
stylingMode: 'icon',
|
||||
onClick: () => {
|
||||
gridRef.current?.instance()?.refresh()
|
||||
void refreshWidgets()
|
||||
},
|
||||
}),
|
||||
[translate],
|
||||
[translate, refreshWidgets],
|
||||
)
|
||||
|
||||
// Gantt'in kendi sutun secicisi yok; ici TreeList oldugu icin onun secicisi acilir.
|
||||
|
|
@ -424,8 +427,8 @@ const GanttView = (props: GanttViewProps) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div ref={widgetGroupRef} className={(gridDto?.widgets?.length ?? 0) > 0 ? 'mt-2' : ''}>
|
||||
<WidgetGroup widgetGroups={gridDto?.widgets ?? []} />
|
||||
<div ref={widgetGroupRef} className={widgets.length > 0 ? 'mt-2' : ''}>
|
||||
<WidgetGroup widgetGroups={widgets} />
|
||||
</div>
|
||||
|
||||
<Container className={DX_CLASSNAMES}>
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ import {
|
|||
useExtraFilters,
|
||||
useListFormCustomSources,
|
||||
useListFormGridDto,
|
||||
useListFormWidgets,
|
||||
useWidgetGroupHeight,
|
||||
} from './shared/hooks'
|
||||
import { startWorkflowForInsertedRow } from './shared/workflowRows'
|
||||
|
|
@ -127,7 +128,8 @@ const Grid = (props: GridProps) => {
|
|||
entityId: string
|
||||
} | null>(null)
|
||||
|
||||
const widgetGroupHeight = useWidgetGroupHeight(widgetGroupRef, gridDto?.widgets)
|
||||
const { widgets, refreshWidgets } = useListFormWidgets(listFormCode, gridDto)
|
||||
const widgetGroupHeight = useWidgetGroupHeight(widgetGroupRef, widgets)
|
||||
useListFormCustomSources(gridDto)
|
||||
|
||||
const getGridInstance = useCallback(() => gridRef.current?.instance(), [])
|
||||
|
|
@ -175,7 +177,7 @@ const Grid = (props: GridProps) => {
|
|||
[gridDto, listFormCode],
|
||||
)
|
||||
|
||||
const { getBandedColumns } = useListFormColumns({
|
||||
const { getBandedColumns, refreshLookupsForRow } = useListFormColumns({
|
||||
gridDto,
|
||||
listFormCode,
|
||||
isSubForm,
|
||||
|
|
@ -205,7 +207,8 @@ const Grid = (props: GridProps) => {
|
|||
const refreshData = useCallback(() => {
|
||||
const grid = getGridInstance()
|
||||
Promise.resolve(grid?.refresh()).finally(() => updateWorkflowApprovalButtons(grid))
|
||||
}, [getGridInstance, updateWorkflowApprovalButtons])
|
||||
void refreshWidgets()
|
||||
}, [getGridInstance, updateWorkflowApprovalButtons, refreshWidgets])
|
||||
|
||||
const getSelectedRowKeys = useCallback(
|
||||
async () => (await getGridInstance()?.getSelectedRowKeys()) ?? [],
|
||||
|
|
@ -559,8 +562,8 @@ const Grid = (props: GridProps) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div ref={widgetGroupRef} className={(gridDto?.widgets?.length ?? 0) > 0 ? 'mt-2' : ''}>
|
||||
<WidgetGroup widgetGroups={gridDto?.widgets ?? []} />
|
||||
<div ref={widgetGroupRef} className={widgets.length > 0 ? 'mt-2' : ''}>
|
||||
<WidgetGroup widgetGroups={widgets} />
|
||||
</div>
|
||||
|
||||
<Container className={DX_CLASSNAMES}>
|
||||
|
|
@ -623,9 +626,18 @@ const Grid = (props: GridProps) => {
|
|||
onRowInserted={(e) => {
|
||||
startWorkflowForInsertedRow(e, gridDto, listFormCode, refreshGrid)
|
||||
props.refreshData?.()
|
||||
void refreshWidgets()
|
||||
void refreshLookupsForRow(e.component, e.data)
|
||||
}}
|
||||
onRowUpdated={(e) => {
|
||||
props.refreshData?.()
|
||||
void refreshWidgets()
|
||||
void refreshLookupsForRow(e.component, e.data)
|
||||
}}
|
||||
onRowRemoved={() => {
|
||||
props.refreshData?.()
|
||||
void refreshWidgets()
|
||||
}}
|
||||
onRowUpdated={() => props.refreshData?.()}
|
||||
onRowRemoved={() => props.refreshData?.()}
|
||||
onEditorPreparing={onEditorPreparing}
|
||||
>
|
||||
<Export
|
||||
|
|
|
|||
|
|
@ -41,7 +41,12 @@ import {
|
|||
} from './shared/editPopup'
|
||||
import { getValueByField, resolveEditorType, withEditorTypeDefaults } from './shared/editingForm'
|
||||
import { flattenGridColumns } from './shared/columns'
|
||||
import { useListFormCustomSources, useListFormGridDto, useWidgetGroupHeight } from './shared/hooks'
|
||||
import {
|
||||
useListFormCustomSources,
|
||||
useListFormGridDto,
|
||||
useListFormWidgets,
|
||||
useWidgetGroupHeight,
|
||||
} from './shared/hooks'
|
||||
|
||||
interface SchedulerViewProps {
|
||||
listFormCode: string
|
||||
|
|
@ -68,7 +73,8 @@ const SchedulerView = (props: SchedulerViewProps) => {
|
|||
const [userAvatarUrls, setUserAvatarUrls] = useState<Record<string, string>>({})
|
||||
const layout = layoutTypes.scheduler || 'scheduler'
|
||||
|
||||
const widgetGroupHeight = useWidgetGroupHeight(widgetGroupRef, gridDto?.widgets)
|
||||
const { widgets, refreshWidgets } = useListFormWidgets(listFormCode, gridDto)
|
||||
const widgetGroupHeight = useWidgetGroupHeight(widgetGroupRef, widgets)
|
||||
useListFormCustomSources(gridDto)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -161,7 +167,8 @@ const SchedulerView = (props: SchedulerViewProps) => {
|
|||
dataSource.reload()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
void refreshWidgets()
|
||||
}, [refreshWidgets])
|
||||
|
||||
const onCurrentViewChange = useCallback((value: string) => {
|
||||
setCurrentView(value)
|
||||
|
|
@ -769,8 +776,8 @@ const SchedulerView = (props: SchedulerViewProps) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div ref={widgetGroupRef} className={(gridDto?.widgets?.length ?? 0) > 0 ? 'mt-2' : ''}>
|
||||
<WidgetGroup widgetGroups={gridDto?.widgets ?? []} />
|
||||
<div ref={widgetGroupRef} className={widgets.length > 0 ? 'mt-2' : ''}>
|
||||
<WidgetGroup widgetGroups={widgets} />
|
||||
</div>
|
||||
|
||||
<Container className={DX_CLASSNAMES}>
|
||||
|
|
@ -820,6 +827,9 @@ const SchedulerView = (props: SchedulerViewProps) => {
|
|||
onAppointmentDeleting={() => {
|
||||
props.refreshData?.()
|
||||
}}
|
||||
onAppointmentAdded={() => void refreshWidgets()}
|
||||
onAppointmentUpdated={() => void refreshWidgets()}
|
||||
onAppointmentDeleted={() => void refreshWidgets()}
|
||||
height={
|
||||
gridDto.gridOptions.height > 0
|
||||
? gridDto.gridOptions.height
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ import {
|
|||
useExtraFilters,
|
||||
useListFormCustomSources,
|
||||
useListFormGridDto,
|
||||
useListFormWidgets,
|
||||
useWidgetGroupHeight,
|
||||
} from './shared/hooks'
|
||||
import { startWorkflowForInsertedRow } from './shared/workflowRows'
|
||||
|
|
@ -117,7 +118,8 @@ const Tree = (props: TreeProps) => {
|
|||
entityId: string
|
||||
} | null>(null)
|
||||
|
||||
const widgetGroupHeight = useWidgetGroupHeight(widgetGroupRef, gridDto?.widgets)
|
||||
const { widgets, refreshWidgets } = useListFormWidgets(listFormCode, gridDto)
|
||||
const widgetGroupHeight = useWidgetGroupHeight(widgetGroupRef, widgets)
|
||||
useListFormCustomSources(gridDto)
|
||||
|
||||
const getTreeInstance = useCallback(() => gridRef.current?.instance(), [])
|
||||
|
|
@ -165,7 +167,7 @@ const Tree = (props: TreeProps) => {
|
|||
[gridDto, listFormCode],
|
||||
)
|
||||
|
||||
const { getBandedColumns } = useListFormColumns({
|
||||
const { getBandedColumns, refreshLookupsForRow } = useListFormColumns({
|
||||
gridDto,
|
||||
listFormCode,
|
||||
isSubForm,
|
||||
|
|
@ -195,7 +197,8 @@ const Tree = (props: TreeProps) => {
|
|||
const refreshData = useCallback(() => {
|
||||
const tree = getTreeInstance()
|
||||
Promise.resolve(tree?.refresh()).finally(() => updateWorkflowApprovalButtons(tree))
|
||||
}, [getTreeInstance, updateWorkflowApprovalButtons])
|
||||
void refreshWidgets()
|
||||
}, [getTreeInstance, updateWorkflowApprovalButtons, refreshWidgets])
|
||||
|
||||
const getSelectedRowKeys = useCallback(
|
||||
async () => (await getTreeInstance()?.getSelectedRowKeys()) ?? [],
|
||||
|
|
@ -535,8 +538,8 @@ const Tree = (props: TreeProps) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div ref={widgetGroupRef} className={(gridDto?.widgets?.length ?? 0) > 0 ? 'mt-2' : ''}>
|
||||
<WidgetGroup widgetGroups={gridDto?.widgets ?? []} />
|
||||
<div ref={widgetGroupRef} className={widgets.length > 0 ? 'mt-2' : ''}>
|
||||
<WidgetGroup widgetGroups={widgets} />
|
||||
</div>
|
||||
|
||||
<Container className={DX_CLASSNAMES}>
|
||||
|
|
@ -605,9 +608,18 @@ const Tree = (props: TreeProps) => {
|
|||
onRowInserted={(e) => {
|
||||
startWorkflowForInsertedRow(e, gridDto, listFormCode, refreshTree)
|
||||
props.refreshData?.()
|
||||
void refreshWidgets()
|
||||
void refreshLookupsForRow(e.component, e.data)
|
||||
}}
|
||||
onRowUpdated={(e) => {
|
||||
props.refreshData?.()
|
||||
void refreshWidgets()
|
||||
void refreshLookupsForRow(e.component, e.data)
|
||||
}}
|
||||
onRowRemoved={() => {
|
||||
props.refreshData?.()
|
||||
void refreshWidgets()
|
||||
}}
|
||||
onRowUpdated={() => props.refreshData?.()}
|
||||
onRowRemoved={() => props.refreshData?.()}
|
||||
onEditorPreparing={onEditorPreparing}
|
||||
onContentReady={(e) => {
|
||||
updateWorkflowApprovalButtons(e.component)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Liste görünümlerinin paylaştığı küçük React hook'ları.
|
||||
*/
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { RefObject } from 'react'
|
||||
import type { GridDto } from '@/proxy/form/models'
|
||||
import { getList } from '@/services/form.service'
|
||||
|
|
@ -48,6 +48,37 @@ export const useListFormGridDto = (listFormCode: string, externalGridDto?: GridD
|
|||
return gridDto
|
||||
}
|
||||
|
||||
/**
|
||||
* Widget değerleri GridDto ile bir kez hesaplanır; görünüm yenilendiğinde ya da satır
|
||||
* eklendiğinde/değiştiğinde değerler bayatlamasın diye `refreshWidgets` yalnızca
|
||||
* widget'ları yeniden çeker. GridDto'nun kendisi değiştirilmez: yeni referans kolonları
|
||||
* ve veri kaynağını baştan kurar, görünüm durumunu (sayfa, seçim, açık satır) sıfırlardı.
|
||||
*/
|
||||
export const useListFormWidgets = (listFormCode: string, gridDto?: GridDto) => {
|
||||
const [state, setState] = useState<{ source?: GridDto; widgets: GridDto['widgets'] }>({
|
||||
widgets: [],
|
||||
})
|
||||
const requestIdRef = useRef(0)
|
||||
|
||||
const widgets = state.source === gridDto ? state.widgets : (gridDto?.widgets ?? [])
|
||||
const hasWidgets = (gridDto?.widgets?.length ?? 0) > 0
|
||||
|
||||
const refreshWidgets = useCallback(async () => {
|
||||
if (!listFormCode || !gridDto || !hasWidgets) return
|
||||
|
||||
const requestId = ++requestIdRef.current
|
||||
try {
|
||||
const response = await getList({ listFormCode })
|
||||
if (requestId !== requestIdRef.current) return
|
||||
setState({ source: gridDto, widgets: response.data?.widgets ?? [] })
|
||||
} catch (error) {
|
||||
console.error('Widget refresh error:', error)
|
||||
}
|
||||
}, [listFormCode, gridDto, hasWidgets])
|
||||
|
||||
return { widgets, refreshWidgets }
|
||||
}
|
||||
|
||||
/**
|
||||
* WidgetGroup alanının yüksekliğini izler; `fullHeight` grid'lerinin
|
||||
* `calc(100vh - …)` hesabında kullanılır.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,11 @@ import {
|
|||
UiLookupDataSourceTypeEnum,
|
||||
} from '@/proxy/form/models'
|
||||
import { addCss, getEditingFormFields } from './Utils'
|
||||
import { cachedLookupLoad, clearLookupCache } from '../shared/lookupCache'
|
||||
import {
|
||||
cachedLookupLoad,
|
||||
clearLookupCache,
|
||||
clearLookupCacheByPrefix,
|
||||
} from '../shared/lookupCache'
|
||||
import {
|
||||
NO_IMAGE,
|
||||
normalizeImageSize,
|
||||
|
|
@ -269,6 +273,80 @@ const useListFormColumns = ({
|
|||
[listFormCode, lookupDataSource],
|
||||
)
|
||||
|
||||
/**
|
||||
* Lookup'ı ekranın kendi kayıt kaynağından beslenen kolonda (`isLookupSelfSourced`, sunucu
|
||||
* hesaplar) kaydedilen yeni değer, açılışta yüklenen lookup listesinde olmadığı için hücrede ve
|
||||
* editörün açılır listesinde görünmez. Başka kaynaktan beslenen lookup kayıtla değişmez; kontrol
|
||||
* yalnızca bu kolonlarda yapılır ve istek yalnızca kaydedilen değer listede yoksa atılır.
|
||||
* Bir kolon yenilendiyse `true` döner.
|
||||
*/
|
||||
const refreshLookupsForRow = useCallback(
|
||||
async (instance: any, rowData?: Record<string, any>) => {
|
||||
if (!instance?.columnOption || !rowData || !gridDto) return false
|
||||
|
||||
const getCachePrefix = (colData: ColumnFormatDto) => {
|
||||
const { lookupDto } = colData
|
||||
if (lookupDto.dataSourceType == UiLookupDataSourceTypeEnum.Query) {
|
||||
return `query:${listFormCode}:${colData.fieldName}:`
|
||||
}
|
||||
if (lookupDto.dataSourceType == UiLookupDataSourceTypeEnum.WebService) {
|
||||
return `api:${lookupDto.lookupQuery}:`
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const updated = await Promise.all(
|
||||
gridDto.columnFormats
|
||||
.filter(
|
||||
(colData) =>
|
||||
colData.fieldName &&
|
||||
colData.lookupDto?.dataSourceType &&
|
||||
!colData.lookupDto.cascadeParentFields &&
|
||||
colData.isLookupSelfSourced &&
|
||||
rowData[colData.fieldName] !== undefined &&
|
||||
rowData[colData.fieldName] !== null &&
|
||||
rowData[colData.fieldName] !== '',
|
||||
)
|
||||
.map(async (colData) => {
|
||||
const prefix = getCachePrefix(colData)
|
||||
if (!prefix) return
|
||||
|
||||
const source = lookupDataSource({}, colData, listFormCode)
|
||||
const store =
|
||||
source instanceof CustomStore
|
||||
? source
|
||||
: (source as { store?: CustomStore<any, any> } | undefined)?.store
|
||||
const items = ((await store?.load?.()) ?? []) as Record<string, any>[]
|
||||
const valueExpr = colData.lookupDto.valueExpr?.toLowerCase()
|
||||
const knownValues = new Set(
|
||||
items.map((item) => {
|
||||
const key = Object.keys(item).find((itemKey) => itemKey.toLowerCase() === valueExpr)
|
||||
return key ? String(item[key]) : undefined
|
||||
}),
|
||||
)
|
||||
|
||||
const rawValue = rowData[colData.fieldName!]
|
||||
const values = Array.isArray(rawValue) ? rawValue : String(rawValue).split('|')
|
||||
if (values.every((value) => knownValues.has(String(value)))) return
|
||||
|
||||
clearLookupCacheByPrefix(prefix)
|
||||
// `columnOption('lookup.dataSource', …)` kolonu yeniden kursa da DevExtreme yalnızca
|
||||
// valueMap'i boş lookup'ları yükler; hücre metnini tutan valueMap ancak lookup'ın
|
||||
// kendi update()'i ile yenilenir. CardView'da DevExtreme lookup'ı yoktur; metin
|
||||
// haritasını çağıran taraf yeniden kurar.
|
||||
const lookup = instance.columnOption(colData.fieldName, 'lookup')
|
||||
if (lookup?.update) await lookup.update()
|
||||
return true
|
||||
}),
|
||||
)
|
||||
|
||||
const changed = updated.some(Boolean)
|
||||
if (changed) instance.repaint?.()
|
||||
return changed
|
||||
},
|
||||
[gridDto, listFormCode, lookupDataSource],
|
||||
)
|
||||
|
||||
const createLookupStaticDataSource = (
|
||||
load: () => any,
|
||||
filter: any = null,
|
||||
|
|
@ -901,6 +979,7 @@ const useListFormColumns = ({
|
|||
return {
|
||||
getBandedColumns,
|
||||
loadLookupDisplayValues,
|
||||
refreshLookupsForRow,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -374,11 +374,13 @@ const useListFormCustomDataSource = ({
|
|||
}
|
||||
const updateUrl = getServiceAddress(gridOptions.updateServiceAddress)
|
||||
|
||||
// DevExtreme, update'in döndürdüğü nesneyi onRowUpdated'ın `data`'sı yapar ve
|
||||
// refreshMode reshape/repaint'te store'a iter; axios yanıtı değil gönderilen değerler dönmeli.
|
||||
return dynamicFetch(updateUrl, 'POST', searchParams, {
|
||||
keys: [toPrimaryKey(key)],
|
||||
data: values,
|
||||
listFormCode,
|
||||
})
|
||||
}).then(() => values)
|
||||
},
|
||||
insert: function (values) {
|
||||
if (!gridOptions.insertServiceAddress) {
|
||||
|
|
|
|||
|
|
@ -65,3 +65,10 @@ export const clearLookupCache = (key?: string) => {
|
|||
lookupCache.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
/** Anahtarı verilen önekle başlayan girdileri (ör. bir kolonun bütün filtre varyantları) temizler. */
|
||||
export const clearLookupCacheByPrefix = (prefix: string) => {
|
||||
Array.from(lookupCache.keys())
|
||||
.filter((key) => key.startsWith(prefix))
|
||||
.forEach((key) => lookupCache.delete(key))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue