@@ -14,6 +14,7 @@ import {
1414 Skeleton ,
1515 Typography ,
1616 Select ,
17+ Checkbox ,
1718} from "antd" ;
1819import * as d3 from "d3" ;
1920import { roleColorMap , transitionStyle } from "../../helpers/utility" ;
@@ -29,7 +30,7 @@ import { buildColumnsFromSettings } from "./columnBuilders";
2930
3031const { Text } = Typography ;
3132
32- const { selectFilteredEvent } = filteredEventsActions ;
33+ const { selectFilteredEvent, setSelectedEventUids , toggleEventUidSelection , setColumnFilters , resetColumnFilters } = filteredEventsActions ;
3334
3435const EVENT_TYPES = [ "all" , "snv" , "cna" , "fusion" , "complexsv" ] ;
3536
@@ -48,37 +49,90 @@ const getColumnTitle = (title) => {
4849
4950class FilteredEventsListPanel extends Component {
5051 handleResetFilters = ( ) => {
51- const { additionalColumns } = this . props ;
52+ const { additionalColumns, resetColumnFilters } = this . props ;
5253 const defaultColumnKeys = this . getDefaultColumnKeys ( ) ;
5354 const additionalKeys = ( additionalColumns || [ ] ) . map ( ( col ) => col . key ) ;
5455 const defaultKeys = [ ...new Set ( [ ...defaultColumnKeys , ...additionalKeys ] ) ] ;
56+
57+ resetColumnFilters ( ) ;
5558 this . setState ( {
56- geneFilters : [ ] ,
57- tierFilters : [ ] ,
58- typeFilters : [ ] ,
59- roleFilters : [ ] ,
60- effectFilters : [ ] ,
61- variantFilters : [ ] ,
6259 selectedColumnKeys : defaultKeys ,
6360 } ) ;
6461 } ;
62+
63+ handleCheckboxChange = ( record , checked ) => {
64+ const { toggleEventUidSelection } = this . props ;
65+ toggleEventUidSelection ( record . uid , checked ) ;
66+ } ;
67+
68+ handleHeaderCheckboxChange = ( records ) => {
69+ const { selectedEventUids, setSelectedEventUids } = this . props ;
70+
71+ // Get all tier 1 and 2 records from current view
72+ const tier1And2Records = records . filter (
73+ ( r ) => r . tier && ( + r . tier === 1 || + r . tier === 2 )
74+ ) ;
75+ const tier1And2Uids = tier1And2Records . map ( ( r ) => r . uid ) ;
76+
77+ // Check current state
78+ const selectedTier1And2 = tier1And2Uids . filter ( ( uid ) =>
79+ selectedEventUids . includes ( uid )
80+ ) ;
81+ const allSelected = selectedTier1And2 . length === tier1And2Uids . length && tier1And2Uids . length > 0 ;
82+
83+ if ( allSelected ) {
84+ // Deselect all tier 1 and 2
85+ const newUids = selectedEventUids . filter (
86+ ( uid ) => ! tier1And2Uids . includes ( uid )
87+ ) ;
88+ setSelectedEventUids ( newUids ) ;
89+ } else {
90+ // Select all tier 1 and 2
91+ const newSelectedUids = [ ...new Set ( [ ...selectedEventUids , ...tier1And2Uids ] ) ] ;
92+ setSelectedEventUids ( newSelectedUids ) ;
93+ }
94+ } ;
95+
96+ getHeaderCheckboxState = ( records ) => {
97+ const { selectedEventUids } = this . props ;
98+
99+ // Get all tier 1 and 2 records from current view
100+ const tier1And2Records = records . filter (
101+ ( r ) => r . tier && ( + r . tier === 1 || + r . tier === 2 )
102+ ) ;
103+ const tier1And2Uids = tier1And2Records . map ( ( r ) => r . uid ) ;
104+
105+ if ( tier1And2Uids . length === 0 ) {
106+ return { checked : false , indeterminate : false } ;
107+ }
108+
109+ const selectedTier1And2 = tier1And2Uids . filter ( ( uid ) =>
110+ selectedEventUids . includes ( uid )
111+ ) ;
112+
113+ if ( selectedTier1And2 . length === 0 ) {
114+ return { checked : false , indeterminate : false } ;
115+ } else if ( selectedTier1And2 . length === tier1And2Uids . length ) {
116+ return { checked : true , indeterminate : false } ;
117+ } else {
118+ return { checked : false , indeterminate : true } ;
119+ }
120+ } ;
121+
122+ isEventSelected = ( record ) => {
123+ const { selectedEventUids } = this . props ;
124+ return selectedEventUids . includes ( record . uid ) ;
125+ } ;
65126 state = {
66127 eventType : "all" ,
67- tierFilters : [ 1 , 2 ] , // start with tiers 1 & 2 checked
68- typeFilters : [ ] ,
69- roleFilters : [ ] ,
70- effectFilters : [ ] ,
71- variantFilters : [ ] ,
72- geneFilters : [ ] ,
73128 tierCountsMap : { } ,
129+ geneVariantsWithTierChanges : null ,
74130 selectedColumnKeys : [ ] ,
75131 } ;
76132
77133 // Track if a fetch is in progress to prevent concurrent calls
78134 _isFetchingTierCounts = false ;
79135
80- // add as a class field
81-
82136 getDefaultColumnKeys = ( ) => {
83137 const { data : settingsData , dataset } = this . props ;
84138
@@ -150,16 +204,13 @@ class FilteredEventsListPanel extends Component {
150204 } ;
151205
152206 handleTableChange = ( pagination , filters ) => {
153- // When the user changes filters (e.g. checks tier 3),
154- // update tierFilters in the state:
155- this . setState ( {
156- geneFilters : filters . gene || [ ] ,
157- tierFilters : filters . tier || [ ] ,
158- typeFilters : filters . type || [ ] ,
159- roleFilters : filters . role || [ ] ,
160- effectFilters : filters . effect || [ ] ,
161- variantFilters : filters . variant || [ ] ,
207+ const columnFilters = { } ;
208+ Object . keys ( filters ) . forEach ( ( key ) => {
209+ if ( filters [ key ] && filters [ key ] . length > 0 ) {
210+ columnFilters [ key ] = filters [ key ] ;
211+ }
162212 } ) ;
213+ this . props . setColumnFilters ( columnFilters ) ;
163214 } ;
164215
165216 fetchTierCountsForRecords = async ( ) => {
@@ -201,7 +252,7 @@ class FilteredEventsListPanel extends Component {
201252
202253 // If no gene-variants have tier changes, nothing to fetch
203254 if ( geneVariantsWithTiers . size === 0 ) {
204- this . setState ( { tierCountsMap : { } } ) ;
255+ this . setState ( { tierCountsMap : { } , geneVariantsWithTierChanges : geneVariantsWithTiers } ) ;
205256 return ;
206257 }
207258
@@ -221,7 +272,7 @@ class FilteredEventsListPanel extends Component {
221272
222273 // Guard: nothing to fetch after filtering
223274 if ( uniqueRecords . length === 0 ) {
224- this . setState ( { tierCountsMap : { } } ) ;
275+ this . setState ( { tierCountsMap : { } , geneVariantsWithTierChanges : geneVariantsWithTiers } ) ;
225276 return ;
226277 }
227278
@@ -248,15 +299,22 @@ class FilteredEventsListPanel extends Component {
248299 await Promise . all ( batchPromises ) ;
249300 }
250301
251- this . setState ( { tierCountsMap : map } ) ;
302+ this . setState ( { tierCountsMap : map , geneVariantsWithTierChanges : geneVariantsWithTiers } ) ;
252303 } finally {
253304 this . _isFetchingTierCounts = false ;
254305 }
255306 } ;
256307
257308 getTierTooltipContent = ( record ) => {
258309 const key = `${ record . gene } -${ record . type } ` ;
259- const tierCounts = this . state . tierCountsMap [ key ] ;
310+ const { tierCountsMap, geneVariantsWithTierChanges } = this . state ;
311+
312+ // Check if this gene-variant has no tier changes
313+ if ( geneVariantsWithTierChanges && ! geneVariantsWithTierChanges . has ( key ) ) {
314+ return "No tier change" ;
315+ }
316+
317+ const tierCounts = tierCountsMap [ key ] ;
260318 if ( ! tierCounts ) return "Loading tier distribution..." ;
261319 const total =
262320 ( tierCounts [ 1 ] || 0 ) + ( tierCounts [ 2 ] || 0 ) + ( tierCounts [ 3 ] || 0 ) ;
@@ -318,15 +376,8 @@ class FilteredEventsListPanel extends Component {
318376 let records =
319377 ( eventType === "all" ? filteredEvents : recordsHash . get ( eventType ) ) || [ ] ;
320378
321- // Build filter values object for controlled filter state
322- const filterValues = {
323- gene : this . state . geneFilters ,
324- tier : this . state . tierFilters ,
325- type : this . state . typeFilters ,
326- role : this . state . roleFilters ,
327- effect : this . state . effectFilters ,
328- variant : this . state . variantFilters ,
329- } ;
379+ const { columnFilters } = this . props ;
380+ const filterValues = { ...columnFilters } ;
330381
331382 // Build columns from settings.json and dataset configuration
332383 const columns = buildColumnsFromSettings (
@@ -341,6 +392,28 @@ class FilteredEventsListPanel extends Component {
341392 filterValues
342393 ) ;
343394
395+ // Checkbox column for selecting events
396+ const headerCheckboxState = this . getHeaderCheckboxState ( records ) ;
397+ const checkboxColumn = {
398+ title : (
399+ < Checkbox
400+ checked = { headerCheckboxState . checked }
401+ indeterminate = { headerCheckboxState . indeterminate }
402+ onChange = { ( ) => this . handleHeaderCheckboxChange ( records ) }
403+ />
404+ ) ,
405+ key : "select" ,
406+ width : 50 ,
407+ fixed : "left" ,
408+ align : "center" ,
409+ render : ( _ , record ) => (
410+ < Checkbox
411+ checked = { this . isEventSelected ( record ) }
412+ onChange = { ( e ) => this . handleCheckboxChange ( record , e . target . checked ) }
413+ />
414+ ) ,
415+ } ;
416+
344417 return (
345418 < Wrapper >
346419 { error ? (
@@ -459,9 +532,10 @@ class FilteredEventsListPanel extends Component {
459532 < Skeleton active loading = { loading } >
460533 < Table
461534 columns = { [
535+ checkboxColumn ,
462536 ...( additionalColumns || [ ] ) ,
463537 ...columns ,
464- ] . filter ( ( col ) => selectedColumnKeys . includes ( col . key ) ) }
538+ ] . filter ( ( col ) => col . key === "select" || selectedColumnKeys . includes ( col . key ) ) }
465539 dataSource = { records }
466540 pagination = { { pageSize : 50 } }
467541 showSorterTooltip = { false }
@@ -621,6 +695,14 @@ FilteredEventsListPanel.defaultProps = {};
621695const mapDispatchToProps = ( dispatch ) => ( {
622696 selectFilteredEvent : ( filteredEvent , viewMode ) =>
623697 dispatch ( selectFilteredEvent ( filteredEvent , viewMode ) ) ,
698+ setSelectedEventUids : ( uids ) =>
699+ dispatch ( setSelectedEventUids ( uids ) ) ,
700+ toggleEventUidSelection : ( uid , selected ) =>
701+ dispatch ( toggleEventUidSelection ( uid , selected ) ) ,
702+ setColumnFilters : ( columnFilters ) =>
703+ dispatch ( setColumnFilters ( columnFilters ) ) ,
704+ resetColumnFilters : ( ) =>
705+ dispatch ( resetColumnFilters ( ) ) ,
624706} ) ;
625707const mapStateToProps = ( state ) => {
626708 const mergedEvents = selectMergedEvents ( state ) ;
@@ -630,6 +712,8 @@ const mapStateToProps = (state) => {
630712 filteredEvents : mergedEvents . filteredEvents ,
631713 originalFilteredEvents : state . FilteredEvents . originalFilteredEvents ,
632714 selectedFilteredEvent : mergedEvents . selectedFilteredEvent ,
715+ selectedEventUids : state . FilteredEvents . selectedEventUids || [ ] ,
716+ columnFilters : state . FilteredEvents . columnFilters || { tier : [ 1 , 2 ] } ,
633717 viewMode : state . FilteredEvents . viewMode ,
634718 error : state . FilteredEvents . error ,
635719 id : state . CaseReport . id ,
0 commit comments