Skip to content

Commit 5b8e97c

Browse files
committed
add subcircuits in listing page with filters
add subcircuits and derived circuits in details page
1 parent e279270 commit 5b8e97c

File tree

40 files changed

+1293
-357
lines changed

40 files changed

+1293
-357
lines changed

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ services:
4747
- '3000:8000'
4848
env_file:
4949
- .env.local
50-
restart: unless-stopped
50+
restart: unless-stopped

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
"input-otp": "^1.4.2",
106106
"install": "^0.13.0",
107107
"jotai": "^2.7.2",
108+
"jotai-cache": "^0.5.0",
108109
"jotai-devtools": "^0.11.0",
109110
"jotai-optics": "^0.4.0",
110111
"js-yaml": "^4.1.0",

pnpm-lock.yaml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api/entitycore/types/entities/circuit.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,7 @@ export interface ICircuit
8181
Timestamps,
8282
EntityCoreOwnership,
8383
EntityCoreType,
84-
EntityCoreBaseAsset {
85-
sub_circuits?: Array<ICircuit>;
86-
}
84+
EntityCoreBaseAsset {}
8785

8886
type CircuitScaleFilter = {
8987
scale: string | null;

src/api/entitycore/types/shared/hierarchy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ export interface HierarchyNode {
44
id: string;
55
name: string;
66
parent_id: string | null;
7-
children: HierarchyNode[];
7+
children: Array<HierarchyNode>;
88
authorized_public: boolean;
99
authorized_project_id: string;
1010
}
1111

1212
export interface HierarchyTreeResponse {
1313
derivation_type: TDerivationType;
14-
data: HierarchyNode[];
14+
data: Array<HierarchyNode>;
1515
}

src/api/entitycore/types/shared/response.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
interface Pagination {
1+
export interface EntityCorePagination {
22
page: number;
33
page_size: number;
44
total_items: number;
@@ -14,6 +14,6 @@ type Facet = {
1414
export type Facets = Record<string, Array<Facet>>;
1515
export interface EntityCoreResponse<T> {
1616
data: Array<T>;
17-
pagination: Pagination;
17+
pagination: EntityCorePagination;
1818
facets?: Facets;
1919
}

src/components/explore-section/ExploreSectionListingView/ExploreSectionTable.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ function CustomTH({
4545
handleResizing: () => void;
4646
className?: string;
4747
}) {
48-
console.log('–– – ExploreSectionTable.tsx:48 – props:', props);
49-
5048
const { position, left, right, zIndex, transform } = style;
5149

5250
// preserve positioning styles for fixed columns, but use our custom styles for everything else
@@ -295,6 +293,8 @@ export default function ExploreSectionTable<T extends EntityCoreIdentifiable>({
295293
rowClassName,
296294
tableStyle,
297295
onRow,
296+
rowKey,
297+
defaultDisplayLoadMore = true,
298298
}: TableProps<T> &
299299
AdditionalTableProps<T> & {
300300
renderButton?: (props: RenderButtonProps<T>) => ReactNode;
@@ -307,6 +307,7 @@ export default function ExploreSectionTable<T extends EntityCoreIdentifiable>({
307307
useBrainRegion?: boolean;
308308
expandableConfig?: ExpandableConfig<T>;
309309
tableStyle?: CSSProperties | undefined;
310+
defaultDisplayLoadMore?: boolean;
310311
}) {
311312
const { rowSelection, selectedRows, clearSelectedRows } = useRowSelection({
312313
dataKey,
@@ -335,7 +336,7 @@ export default function ExploreSectionTable<T extends EntityCoreIdentifiable>({
335336
hasError={hasError}
336337
loading={loading}
337338
onCellClick={onCellClick}
338-
rowKey={(row) => row.id}
339+
rowKey={(row) => (rowKey && typeof rowKey === 'function' ? rowKey?.(row) : row.id)}
339340
rowSelection={rowSelection}
340341
showLoadMore={toggleDisplayMore}
341342
scrollable={scrollable}
@@ -352,7 +353,7 @@ export default function ExploreSectionTable<T extends EntityCoreIdentifiable>({
352353
visible={controlsVisible}
353354
dataType={dataContext.dataType}
354355
>
355-
{displayLoadMoreBtn && (
356+
{displayLoadMoreBtn && defaultDisplayLoadMore && (
356357
<LoadMoreButton
357358
hide={toggleDisplayMore}
358359
dataKey={dataKey}

src/components/explore-section/ExploreSectionListingView/FilterControls.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export default function FilterControls({
9999
{!resourceId && <ExploreSectionNameSearch dataType={dataType} dataKey={dataKey} />}
100100
{dataType === DataType.Circuit && (
101101
<div className="text-red-500">
102-
<ViewToggle setToggle={() => {}} toggle="flat" />
102+
<ViewToggle />
103103
</div>
104104
)}
105105
<div className="inline-flex w-full place-content-end gap-2">

src/components/explore-section/ExploreSectionListingView/expandable-row/expandable-base-table.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export function createExpandableTableConfig<T extends EntityCoreIdentifiable, P
113113
scroll: {
114114
x: undefined, // no horizontal scroll needed since parent will adjust
115115
y: undefined, // disable vertical scrolling to show all rows
116-
...expandedTableProps.scroll,
116+
...expandedTableProps?.scroll,
117117
},
118118
pagination: false as const, // ensure no pagination in nested table
119119
};

src/components/explore-section/ExploreSectionListingView/expandable-row/use-expandable-table.tsx

Lines changed: 62 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,47 @@
1-
import { useState, useCallback, ReactNode } from 'react';
1+
import { useState, useCallback, ReactNode, useEffect } from 'react';
22
import { LoadingOutlined } from '@ant-design/icons';
3+
import { useAtomValue } from 'jotai';
34
import { Spin } from 'antd';
4-
import get from 'lodash/get';
5-
import isNil from 'lodash/isNil';
6-
75
import type { ExpandableConfig } from 'antd/es/table/interface';
8-
import type { EntityCoreIdentifiable } from '@/api/entitycore/types/shared/global';
96

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';
1311

1412
export interface ExpandableTableState<T extends EntityCoreIdentifiable> {
15-
expandedData: ExpandableTableCache<T>;
13+
expandedData: Record<string, Array<T> | null>;
1614
loadingRows: Record<string, boolean>;
15+
expandedRowKeys: Array<string>;
1716
}
1817

1918
export interface UseExpandableTableOptions<T extends EntityCoreIdentifiable, P = unknown> {
20-
/**
21-
* fetch data for expanded row
22-
*/
19+
// fetch data for expanded row
2320
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
2722
fetcherParams?: P;
28-
/**
29-
* get unique key for caching
30-
*/
3123
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)
3525
getFetchId: (record: T) => string | null;
36-
/**
37-
* render expanded content
38-
*/
26+
// render expanded content
3927
renderExpanded: (records: Array<T>, originalRecord: T, isLoading: boolean) => ReactNode;
40-
/**
41-
* determine if row is expandable
42-
*/
28+
// determine if row is expandable
4329
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
5131
expandIconColumnIndex?: number;
52-
/**
53-
* render the expand icon
54-
*/
32+
// render the expand icon
5533
expandIcon?: ExpandableConfig<T>['expandIcon'];
34+
// whether this is a top-level table that should sync with filter resets
35+
isTopLevel?: boolean;
5636
}
5737

5838
/**
59-
* Reusable hook for managing expandable tables with caching and hierarchy support
39+
* Reusable hook for managing expandable tables with hierarchy support
6040
*/
6141
export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown>(
6242
options: UseExpandableTableOptions<T, P>
6343
): {
6444
expandableConfig: ExpandableConfig<T>;
65-
clearCache: () => void;
6645
isRowExpanded: (record: T) => boolean;
6746
isRowLoading: (record: T) => boolean;
6847
getExpandedData: (record: T) => Array<T> | null;
@@ -74,22 +53,29 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
7453
getFetchId,
7554
renderExpanded,
7655
isRowExpandable = () => true,
77-
persistCache = true,
7856
expandIconColumnIndex,
7957
expandIcon,
58+
isTopLevel = false,
8059
} = options;
8160

8261
const [state, setState] = useState<ExpandableTableState<T>>({
8362
expandedData: {},
8463
loadingRows: {},
64+
expandedRowKeys: [],
8565
});
8666

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]);
9379

9480
const isRowExpanded = useCallback(
9581
(record: T): boolean => {
@@ -110,7 +96,7 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
11096
const getExpandedData = useCallback(
11197
(record: T): Array<T> | null => {
11298
const key = getRowKey(record);
113-
return get(state.expandedData, key, null);
99+
return state.expandedData[key] || null;
114100
},
115101
[state.expandedData, getRowKey]
116102
);
@@ -121,31 +107,33 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
121107
const fetchId = getFetchId(record);
122108

123109
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+
});
142126
return;
143127
}
144128

145129
if (fetcher) {
146130
setState((prev) => ({
147131
...prev,
148132
loadingRows: { ...prev.loadingRows, [key]: true },
133+
expandedRowKeys:
134+
isTopLevel && !prev.expandedRowKeys.includes(key)
135+
? [...prev.expandedRowKeys, key]
136+
: prev.expandedRowKeys,
149137
}));
150138

151139
try {
@@ -161,6 +149,7 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
161149
...prev.loadingRows,
162150
[key]: false,
163151
},
152+
expandedRowKeys: prev.expandedRowKeys,
164153
}));
165154
} catch (error) {
166155
setState((prev) => ({
@@ -172,21 +161,22 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
172161
...prev.loadingRows,
173162
[key]: false,
174163
},
164+
expandedRowKeys: prev.expandedRowKeys,
175165
}));
176166
}
177167
} else if (!fetchId) {
178168
// No fetcher and no fetchId - this shouldn't happen for expandable rows
179169
// 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');
181171
}
182172
},
183-
[fetcher, fetcherParams, getRowKey, getFetchId, persistCache, state.expandedData]
173+
[fetcher, fetcherParams, getRowKey, getFetchId, isTopLevel]
184174
);
185175

186176
const expandedRowRender = useCallback(
187177
(record: T): ReactNode => {
188178
const key = getRowKey(record);
189-
const records = get(state.expandedData, key, null);
179+
const records = state.expandedData[key] || null;
190180
const isLoading = Boolean(state.loadingRows[key]);
191181

192182
if (isLoading) {
@@ -197,7 +187,7 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
197187
);
198188
}
199189

200-
if (isNil(records)) {
190+
if (records === null) {
201191
return null;
202192
}
203193

@@ -206,17 +196,19 @@ export function useExpandableTable<T extends EntityCoreIdentifiable, P = unknown
206196
[state.expandedData, state.loadingRows, getRowKey, renderExpanded]
207197
);
208198

199+
// create expandable config - only use controlled mode for top-level tables
209200
const expandableConfig: ExpandableConfig<T> = {
210201
rowExpandable: isRowExpandable,
211202
onExpand,
212203
expandedRowRender,
213204
expandIconColumnIndex,
214205
expandIcon,
206+
// only top-level tables use controlled mode (expandedRowKeys)
207+
...(isTopLevel && { expandedRowKeys: state.expandedRowKeys }),
215208
};
216209

217210
return {
218211
expandableConfig,
219-
clearCache,
220212
isRowExpanded,
221213
isRowLoading,
222214
getExpandedData,

0 commit comments

Comments
 (0)