11import { createSimpleContext } from '@app-builder/utils/create-context' ;
22import { type ReactNode , useCallback , useMemo , useState } from 'react' ;
33import { type GraphObjectRef } from './graph-keys' ;
4-
5- export const GRAPH_ATTRIBUTES = [ 'ip' , 'iban' , 'device' , 'email' ] as const ;
6- export type GraphAttribute = ( typeof GRAPH_ATTRIBUTES ) [ number ] ;
7-
8- export const GRAPH_ATTRIBUTE_LABELS : Record < GraphAttribute , string > = {
9- ip : 'IP' ,
10- iban : 'IBAN' ,
11- device : 'Device' ,
12- email : 'Email' ,
13- } ;
14-
15- /** Pivot `rawType` → the attribute filter governing it. Unlisted pivot types are always shown. */
16- export const PIVOT_TYPE_ATTRIBUTES : Record < string , GraphAttribute > = {
17- same_ip : 'ip' ,
18- same_iban : 'iban' ,
19- same_device : 'device' ,
20- same_email : 'email' ,
21- } ;
4+ import { type GraphLayoutMode } from './graph-layout' ;
225
236/** Branch sizes a subtree must exceed to collapse into a cluster chip. `0` disables clustering. */
247export const CLUSTER_THRESHOLD_OPTIONS = [ 0 , 2 , 5 , 7 , 10 , 15 , 30 , 50 ] as const ;
258export type ClusterThreshold = ( typeof CLUSTER_THRESHOLD_OPTIONS ) [ number ] ;
269export const DEFAULT_CLUSTER_THRESHOLD : ClusterThreshold = 10 ;
2710
11+ export const LAYOUT_MODE_OPTIONS = [ 'rad-dagre' , 'balanced' , 'radial' ] as const satisfies readonly GraphLayoutMode [ ] ;
12+
2813/**
2914 * The node backing the settings panel's detail card. `persons` are the selection's
3015 * connected persons, or the folded members of a cluster.
3116 */
3217export type SelectedGraphObject = GraphObjectRef & { persons : GraphObjectRef [ ] } & (
3318 | { nodeType : 'person' | 'pivot' }
3419 | { nodeType : 'cluster' ; nodeCount : number ; internalEdgeCount : number }
20+ | { nodeType : 'hypernode' ; hypernodeCount : number }
3521 ) ;
3622
3723/**
@@ -54,10 +40,13 @@ export type CustomerGraphContextValue = {
5440 showCompanies : boolean ;
5541 setShowCompanies : ( value : boolean ) => void ;
5642
57- // Attribute filters (pivots)
58- attributes : GraphAttribute [ ] ;
59- setAttributes : ( value : GraphAttribute [ ] ) => void ;
60- toggleAttribute : ( attribute : GraphAttribute ) => void ;
43+ /** Configured relation labels available for filtering pivots. */
44+ relationLabels : string [ ] ;
45+ setRelationLabels : ( labels : string [ ] ) => void ;
46+ /** Selected relation labels (pivots matching these labels are shown). */
47+ selectedRelationLabels : string [ ] ;
48+ setSelectedRelationLabels : ( labels : string [ ] ) => void ;
49+ toggleRelationLabel : ( label : string ) => void ;
6150
6251 // Display options
6352 showRiskScore : boolean ;
@@ -69,6 +58,9 @@ export type CustomerGraphContextValue = {
6958 showEdgeLabels : boolean ;
7059 setShowEdgeLabels : ( value : boolean ) => void ;
7160
61+ layoutMode : GraphLayoutMode ;
62+ setLayoutMode : ( value : GraphLayoutMode ) => void ;
63+
7264 // Clustering (branch size at which a subtree collapses; `0` disables)
7365 clusterThreshold : ClusterThreshold ;
7466 setClusterThreshold : ( value : ClusterThreshold ) => void ;
@@ -123,19 +115,35 @@ export function CustomerGraphProvider({
123115 initialSelectedObject = null ,
124116 clusterThreshold : controlledClusterThreshold ,
125117 onClusterThresholdChange,
118+ layoutMode : controlledLayoutMode ,
119+ onLayoutModeChange,
126120} : {
127121 children : ReactNode ;
128122 initialSelectedObject ?: SelectedGraphObject | null ;
129123 /** When provided with `onClusterThresholdChange`, survives provider remounts (e.g. graph regenerate). */
130124 clusterThreshold ?: ClusterThreshold ;
131125 onClusterThresholdChange ?: ( value : ClusterThreshold ) => void ;
126+ layoutMode ?: GraphLayoutMode ;
127+ onLayoutModeChange ?: ( value : GraphLayoutMode ) => void ;
132128} ) {
133129 const [ showPersons , setShowPersons ] = useState ( true ) ;
134130 const [ showCompanies , setShowCompanies ] = useState ( true ) ;
135- const [ attributes , setAttributes ] = useState < GraphAttribute [ ] > ( [ ...GRAPH_ATTRIBUTES ] ) ;
131+ const [ relationLabels , setRelationLabels ] = useState < string [ ] > ( [ ] ) ;
132+ const [ selectedRelationLabels , setSelectedRelationLabels ] = useState < string [ ] > ( [ ] ) ;
136133 const [ showRiskScore , setShowRiskScore ] = useState ( false ) ;
137134 const [ showTags , setShowTags ] = useState ( false ) ;
138135 const [ showEdgeLabels , setShowEdgeLabels ] = useState ( false ) ;
136+ const [ uncontrolledLayoutMode , setUncontrolledLayoutMode ] = useState < GraphLayoutMode > ( 'rad-dagre' ) ;
137+ const layoutMode = controlledLayoutMode ?? uncontrolledLayoutMode ;
138+ const setLayoutMode = useCallback (
139+ ( value : GraphLayoutMode ) => {
140+ onLayoutModeChange ?.( value ) ;
141+ if ( controlledLayoutMode === undefined ) {
142+ setUncontrolledLayoutMode ( value ) ;
143+ }
144+ } ,
145+ [ controlledLayoutMode , onLayoutModeChange ] ,
146+ ) ;
139147 const [ uncontrolledClusterThreshold , setUncontrolledClusterThreshold ] =
140148 useState < ClusterThreshold > ( DEFAULT_CLUSTER_THRESHOLD ) ;
141149 const clusterThreshold = controlledClusterThreshold ?? uncontrolledClusterThreshold ;
@@ -170,8 +178,22 @@ export function CustomerGraphProvider({
170178 setExpandedRootIds ( ( prev ) => toggleInSet ( prev , rootId ) ) ;
171179 } , [ ] ) ;
172180
173- const toggleAttribute = useCallback ( ( attribute : GraphAttribute ) => {
174- setAttributes ( ( prev ) => ( prev . includes ( attribute ) ? prev . filter ( ( a ) => a !== attribute ) : [ ...prev , attribute ] ) ) ;
181+ const toggleRelationLabel = useCallback ( ( label : string ) => {
182+ setSelectedRelationLabels ( ( prev ) =>
183+ prev . includes ( label ) ? prev . filter ( ( item ) => item !== label ) : [ ...prev , label ] ,
184+ ) ;
185+ } , [ ] ) ;
186+
187+ const syncRelationLabels = useCallback ( ( labels : string [ ] ) => {
188+ const uniqueLabels = [ ...new Set ( labels ) ] ;
189+ setRelationLabels ( uniqueLabels ) ;
190+ setSelectedRelationLabels ( ( prev ) => {
191+ // First load → select all; otherwise keep selection and auto-select newly added labels.
192+ if ( prev . length === 0 ) return uniqueLabels ;
193+ const kept = prev . filter ( ( label ) => uniqueLabels . includes ( label ) ) ;
194+ const added = uniqueLabels . filter ( ( label ) => ! prev . includes ( label ) ) ;
195+ return [ ...kept , ...added ] ;
196+ } ) ;
175197 } , [ ] ) ;
176198
177199 const clearCheckedNodes = useCallback ( ( ) => {
@@ -200,16 +222,20 @@ export function CustomerGraphProvider({
200222 setShowPersons,
201223 showCompanies,
202224 setShowCompanies,
203- attributes,
204- setAttributes,
205- toggleAttribute,
225+ relationLabels,
226+ setRelationLabels : syncRelationLabels ,
227+ selectedRelationLabels,
228+ setSelectedRelationLabels,
229+ toggleRelationLabel,
206230 showRiskScore,
207231 setShowRiskScore,
208232 showTags,
209233 setShowTags,
210234 nodeTagsVisible : showTags || selectionMode ,
211235 showEdgeLabels,
212236 setShowEdgeLabels,
237+ layoutMode,
238+ setLayoutMode,
213239 clusterThreshold,
214240 setClusterThreshold,
215241 selectedObject,
@@ -234,11 +260,15 @@ export function CustomerGraphProvider({
234260 [
235261 showPersons ,
236262 showCompanies ,
237- attributes ,
238- toggleAttribute ,
263+ relationLabels ,
264+ syncRelationLabels ,
265+ selectedRelationLabels ,
266+ toggleRelationLabel ,
239267 showRiskScore ,
240268 showTags ,
241269 showEdgeLabels ,
270+ layoutMode ,
271+ setLayoutMode ,
242272 clusterThreshold ,
243273 setClusterThreshold ,
244274 selectedObject ,
0 commit comments