1
- import { useState , useCallback , ReactNode } from 'react' ;
1
+ import { useState , useCallback , ReactNode , useEffect } from 'react' ;
2
2
import { LoadingOutlined } from '@ant-design/icons' ;
3
+ import { useAtomValue } from 'jotai' ;
3
4
import { Spin } from 'antd' ;
4
- import get from 'lodash/get' ;
5
- import isNil from 'lodash/isNil' ;
6
-
7
5
import type { ExpandableConfig } from 'antd/es/table/interface' ;
8
- import type { EntityCoreIdentifiable } from '@/api/entitycore/types/shared/global' ;
9
6
10
- export interface ExpandableTableCache < T extends EntityCoreIdentifiable > {
11
- [ key : string ] : Array < T > | null ;
12
- }
7
+ import { resetFilterSignalAtom } from '@/features/entities/circuit/elements/context' ;
8
+ import { log } from '@/utils/logger' ;
9
+
10
+ import type { EntityCoreIdentifiable } from '@/api/entitycore/types/shared/global' ;
13
11
14
12
export interface ExpandableTableState < T extends EntityCoreIdentifiable > {
15
- expandedData : ExpandableTableCache < T > ;
13
+ expandedData : Record < string , Array < T > | null > ;
16
14
loadingRows : Record < string , boolean > ;
15
+ expandedRowKeys : Array < string > ;
17
16
}
18
17
19
18
export interface UseExpandableTableOptions < T extends EntityCoreIdentifiable , P = unknown > {
20
- /**
21
- * fetch data for expanded row
22
- */
19
+ // fetch data for expanded row
23
20
fetcher ?: ( record : T , params ?: P ) => Promise < T | Array < T > > ;
24
- /**
25
- * optional parameters to pass to fetcher
26
- */
21
+ // optional parameters to pass to fetcher
27
22
fetcherParams ?: P ;
28
- /**
29
- * get unique key for caching
30
- */
31
23
getRowKey : ( record : T ) => string ;
32
- /**
33
- * get the id to fetch (e.g., id of circuit)
34
- */
24
+ // get the id to fetch (e.g., id of circuit)
35
25
getFetchId : ( record : T ) => string | null ;
36
- /**
37
- * render expanded content
38
- */
26
+ // render expanded content
39
27
renderExpanded : ( records : Array < T > , originalRecord : T , isLoading : boolean ) => ReactNode ;
40
- /**
41
- * determine if row is expandable
42
- */
28
+ // determine if row is expandable
43
29
isRowExpandable ?: ( record : T ) => boolean ;
44
- /**
45
- * clear cache when component unmounts
46
- */
47
- persistCache ?: boolean ;
48
- /**
49
- * index of the column to render the expand icon
50
- */
30
+ // index of the column to render the expand icon
51
31
expandIconColumnIndex ?: number ;
52
- /**
53
- * render the expand icon
54
- */
32
+ // render the expand icon
55
33
expandIcon ?: ExpandableConfig < T > [ 'expandIcon' ] ;
34
+ // whether this is a top-level table that should sync with filter resets
35
+ isTopLevel ?: boolean ;
56
36
}
57
37
58
38
/**
59
- * Reusable hook for managing expandable tables with caching and hierarchy support
39
+ * Reusable hook for managing expandable tables with hierarchy support
60
40
*/
61
41
export function useExpandableTable < T extends EntityCoreIdentifiable , P = unknown > (
62
42
options : UseExpandableTableOptions < T , P >
63
43
) : {
64
44
expandableConfig : ExpandableConfig < T > ;
65
- clearCache : ( ) => void ;
66
45
isRowExpanded : ( record : T ) => boolean ;
67
46
isRowLoading : ( record : T ) => boolean ;
68
47
getExpandedData : ( record : T ) => Array < T > | null ;
@@ -74,22 +53,29 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
74
53
getFetchId,
75
54
renderExpanded,
76
55
isRowExpandable = ( ) => true ,
77
- persistCache = true ,
78
56
expandIconColumnIndex,
79
57
expandIcon,
58
+ isTopLevel = false ,
80
59
} = options ;
81
60
82
61
const [ state , setState ] = useState < ExpandableTableState < T > > ( {
83
62
expandedData : { } ,
84
63
loadingRows : { } ,
64
+ expandedRowKeys : [ ] ,
85
65
} ) ;
86
66
87
- const clearCache = useCallback ( ( ) => {
88
- setState ( {
89
- expandedData : { } ,
90
- loadingRows : { } ,
91
- } ) ;
92
- } , [ ] ) ;
67
+ const resetFilterSignal = useAtomValue ( resetFilterSignalAtom ) ;
68
+
69
+ // Only top-level tables should reset when filters are cleared
70
+ useEffect ( ( ) => {
71
+ if ( isTopLevel && resetFilterSignal > 0 ) {
72
+ setState ( {
73
+ expandedData : { } ,
74
+ loadingRows : { } ,
75
+ expandedRowKeys : [ ] , // Clear expanded keys to sync with Ant Design table
76
+ } ) ;
77
+ }
78
+ } , [ resetFilterSignal , isTopLevel ] ) ;
93
79
94
80
const isRowExpanded = useCallback (
95
81
( record : T ) : boolean => {
@@ -110,7 +96,7 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
110
96
const getExpandedData = useCallback (
111
97
( record : T ) : Array < T > | null => {
112
98
const key = getRowKey ( record ) ;
113
- return get ( state . expandedData , key , null ) ;
99
+ return state . expandedData [ key ] || null ;
114
100
} ,
115
101
[ state . expandedData , getRowKey ]
116
102
) ;
@@ -121,31 +107,33 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
121
107
const fetchId = getFetchId ( record ) ;
122
108
123
109
if ( ! expanded ) {
124
- // Row is being collapsed - remove from expanded data but keep in cache if persistCache is true
125
- if ( ! persistCache ) {
126
- setState ( ( prev ) => {
127
- const newExpandedData = { ...prev . expandedData } ;
128
- delete newExpandedData [ key ] ;
129
- const newLoadingRows = { ...prev . loadingRows } ;
130
- delete newLoadingRows [ key ] ;
131
- return {
132
- expandedData : newExpandedData ,
133
- loadingRows : newLoadingRows ,
134
- } ;
135
- } ) ;
136
- }
137
- return ;
138
- }
139
-
140
- const existingData = get ( state . expandedData , key , null ) ;
141
- if ( ! isNil ( existingData ) ) {
110
+ setState ( ( prev ) => {
111
+ const newExpandedData = { ...prev . expandedData } ;
112
+ delete newExpandedData [ key ] ;
113
+ const newLoadingRows = { ...prev . loadingRows } ;
114
+ delete newLoadingRows [ key ] ;
115
+
116
+ const newState : ExpandableTableState < T > = {
117
+ expandedData : newExpandedData ,
118
+ loadingRows : newLoadingRows ,
119
+ expandedRowKeys : isTopLevel
120
+ ? prev . expandedRowKeys . filter ( ( rowKey ) => rowKey !== key )
121
+ : prev . expandedRowKeys ,
122
+ } ;
123
+
124
+ return newState ;
125
+ } ) ;
142
126
return ;
143
127
}
144
128
145
129
if ( fetcher ) {
146
130
setState ( ( prev ) => ( {
147
131
...prev ,
148
132
loadingRows : { ...prev . loadingRows , [ key ] : true } ,
133
+ expandedRowKeys :
134
+ isTopLevel && ! prev . expandedRowKeys . includes ( key )
135
+ ? [ ...prev . expandedRowKeys , key ]
136
+ : prev . expandedRowKeys ,
149
137
} ) ) ;
150
138
151
139
try {
@@ -161,6 +149,7 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
161
149
...prev . loadingRows ,
162
150
[ key ] : false ,
163
151
} ,
152
+ expandedRowKeys : prev . expandedRowKeys ,
164
153
} ) ) ;
165
154
} catch ( error ) {
166
155
setState ( ( prev ) => ( {
@@ -172,21 +161,22 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
172
161
...prev . loadingRows ,
173
162
[ key ] : false ,
174
163
} ,
164
+ expandedRowKeys : prev . expandedRowKeys ,
175
165
} ) ) ;
176
166
}
177
167
} else if ( ! fetchId ) {
178
168
// No fetcher and no fetchId - this shouldn't happen for expandable rows
179
169
// eslint-disable-next-line no-console
180
- console . warn ( 'Row is expandable but no fetcher provided and no fetchId available' ) ;
170
+ log ( 'warn' , 'Row is expandable but no fetcher provided and no fetchId available' ) ;
181
171
}
182
172
} ,
183
- [ fetcher , fetcherParams , getRowKey , getFetchId , persistCache , state . expandedData ]
173
+ [ fetcher , fetcherParams , getRowKey , getFetchId , isTopLevel ]
184
174
) ;
185
175
186
176
const expandedRowRender = useCallback (
187
177
( record : T ) : ReactNode => {
188
178
const key = getRowKey ( record ) ;
189
- const records = get ( state . expandedData , key , null ) ;
179
+ const records = state . expandedData [ key ] || null ;
190
180
const isLoading = Boolean ( state . loadingRows [ key ] ) ;
191
181
192
182
if ( isLoading ) {
@@ -197,7 +187,7 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
197
187
) ;
198
188
}
199
189
200
- if ( isNil ( records ) ) {
190
+ if ( records === null ) {
201
191
return null ;
202
192
}
203
193
@@ -206,17 +196,19 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
206
196
[ state . expandedData , state . loadingRows , getRowKey , renderExpanded ]
207
197
) ;
208
198
199
+ // create expandable config - only use controlled mode for top-level tables
209
200
const expandableConfig : ExpandableConfig < T > = {
210
201
rowExpandable : isRowExpandable ,
211
202
onExpand,
212
203
expandedRowRender,
213
204
expandIconColumnIndex,
214
205
expandIcon,
206
+ // only top-level tables use controlled mode (expandedRowKeys)
207
+ ...( isTopLevel && { expandedRowKeys : state . expandedRowKeys } ) ,
215
208
} ;
216
209
217
210
return {
218
211
expandableConfig,
219
- clearCache,
220
212
isRowExpanded,
221
213
isRowLoading,
222
214
getExpandedData,
0 commit comments