Implemented bulk selection and sequential cancellation functionality for the StreamsTable component, enabling users to efficiently manage multiple payment streams simultaneously.
-
Checkbox Column: Added as the first column in the table
- Only renders for streams with status
activeorscheduled - Completed and canceled streams are not selectable
- Only renders for streams with status
-
Header Selection: "Select All" checkbox in table header
- Toggles only eligible streams (active/scheduled) on current page
- Automatically checks when all eligible streams are manually selected
- Automatically unchecks when any stream is deselected
-
State Management:
- Uses
Set<string>for O(1) lookup performance - Automatically cleans up invalid selections when streams change
- Maintains selection state across table interactions
- Uses
- Appearance: Shows when
selectedStreamIds.size >= 1 - Position: Fixed at bottom of viewport with high z-index (1000)
- UI Elements:
- Selected count display (e.g., "3 streams selected")
- Prominent "Cancel X Streams" button
- Progress indicator during operation (e.g., "Canceling 3/10...")
- Styling:
- Wave 4 theme compliant (dark background, high contrast)
- Slide-up animation on mount
- Responsive design (centered on desktop, full-width on mobile)
- Execution: Uses
for...ofloop for sequential API calls - Error Handling:
- Failed cancellations are logged but don't stop the sequence
- Continues processing remaining streams after failures
- Post-Action Cleanup:
- Clears
selectedStreamIdsstate - Triggers table data refresh via
onRefreshcallback - Logs success/failure summary to console
- Clears
- No lag when selecting large numbers of rows
- Set-based selection state for efficient lookups
- Sequential API calls prevent backend overload
- Automatic cleanup prevents memory leaks
- Added selection state management
- Implemented checkbox rendering logic
- Created
BulkActionBarcomponent - Added sequential bulk cancellation handler
- Comprehensive inline documentation
- Complete test suite for selection logic
- Tests for "Select All" edge cases
- Integration tests for sequential cancellation
- Progress indicator tests
- Error handling tests
- Added
.bulk-action-barstyles - Slide-up animation keyframes
- Responsive mobile styles
- High-contrast Wave 4 theme colors
- Added
handleRefreshcallback - Passed
onRefreshprop to StreamsTable
✓ Renders checkboxes only for active/scheduled streams
✓ Selects individual streams on checkbox click
✓ "Select All" selects only eligible streams
✓ "Select All" auto-checks when all manually selected
✓ Deselects all when "Select All" clicked again
✓ Hides "Select All" when no selectable streams exist
✓ Shows bulk action bar when streams selected
✓ Calls cancelStream sequentially for each stream
✓ Shows progress during bulk cancellation
✓ Continues on failure, doesn't stop sequence
✓ Clears selection after completion
✓ Disables button during operation
<StreamsTable
streams={streams}
filters={filters}
onFiltersChange={setFilters}
onCancel={handleCancel}
onEditStartTime={setEditingStream}
onRefresh={handleRefresh} // New prop for bulk refresh
/>The implementation uses sequential execution (for...of loop) rather than parallel (Promise.all()) to:
- Prevent overwhelming the backend with simultaneous requests
- Provide accurate progress tracking
- Ensure predictable execution order
The useEffect hook automatically cleans up selections when:
- Streams are filtered
- Data is refreshed
- Selected streams are removed from the list
This prevents stale selections and ensures UI consistency.
- All checkboxes have proper
aria-labelattributes - Bulk action bar uses semantic HTML
- Progress states are clearly communicated
- Keyboard navigation fully supported
- Toast notifications for success/failure summary
- Undo functionality for bulk cancellations
- Batch API endpoint for improved performance
- Selection persistence across page navigation
- Export selected streams functionality
- Selection state: O(1) lookup time
- No UI lag with 100+ streams
- Sequential cancellation prevents rate limiting
- Minimal re-renders via React.memo (if needed)