From 0a09905f9f849ce9ff221095e5c25b231f040d27 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Thu, 20 Aug 2026 19:42:25 +0100 Subject: [PATCH] Enhance PermissionsMatrix with session storage for expanded rows and scroll position - Implemented session storage functionality to persist expanded row keys and scroll position in the PermissionsMatrix, improving user experience during navigation. - Refactored state management to utilize hooks for handling expanded rows and scroll restoration. - Added utility functions for reading and writing to session storage, ensuring data integrity and availability across user sessions. - Updated event handling for scroll events to maintain the user's scroll position, enhancing usability in the permissions matrix. --- .../Dashboard/common/PermissionsMatrix.jsx | 109 +++++++++++++++--- 1 file changed, 96 insertions(+), 13 deletions(-) diff --git a/src/components/Dashboard/common/PermissionsMatrix.jsx b/src/components/Dashboard/common/PermissionsMatrix.jsx index 9353a8b1..c1cc4f80 100644 --- a/src/components/Dashboard/common/PermissionsMatrix.jsx +++ b/src/components/Dashboard/common/PermissionsMatrix.jsx @@ -4,7 +4,9 @@ import { useState, createElement, useContext, - useRef + useRef, + useEffect, + useLayoutEffect } from 'react' import PropTypes from 'prop-types' import { Table, Flex, Tooltip, Button } from 'antd' @@ -21,6 +23,30 @@ import PermissionCheckbox from './PermissionCheckbox' import { CaretRightOutlined } from '@ant-design/icons' const DEFAULT_SCROLL_HEIGHT = 'calc(var(--unit-100vh) - 258px)' +const PERMISSIONS_MATRIX_SESSION_KEY = 'permissionsMatrix:ui' + +const readPermissionsMatrixSession = () => { + try { + const parsed = JSON.parse( + sessionStorage.getItem(PERMISSIONS_MATRIX_SESSION_KEY) + ) + if (!parsed || typeof parsed !== 'object') return {} + return parsed + } catch { + return {} + } +} + +const writePermissionsMatrixSession = (patch) => { + try { + sessionStorage.setItem( + PERMISSIONS_MATRIX_SESSION_KEY, + JSON.stringify({ ...readPermissionsMatrixSession(), ...patch }) + ) + } catch { + // sessionStorage may be unavailable + } +} const getColumnIndexProps = (columnIndex) => ({ 'data-col': String(columnIndex), @@ -328,9 +354,26 @@ const PermissionsMatrix = ({ () => buildPermissionMatrixDataSource(models), [models] ) - const [expandedRowKeys, setExpandedRowKeys] = useState(null) + const [expandedRowKeys, setExpandedRowKeys] = useState(() => { + const stored = readPermissionsMatrixSession().expandedRowKeys + return Array.isArray(stored) ? stored : [] + }) + const matrixRef = useRef(null) + const scrollPositionRef = useRef(null) + if (scrollPositionRef.current === null) { + const storedScroll = readPermissionsMatrixSession().scroll + scrollPositionRef.current = { + left: Number(storedScroll?.left) || 0, + top: Number(storedScroll?.top) || 0 + } + } const hoveredColumnIndexRef = useRef(null) + const persistExpandedRowKeys = useCallback((keys) => { + setExpandedRowKeys(keys) + writePermissionsMatrixSession({ expandedRowKeys: keys }) + }, []) + const handleMatrixMouseOver = useCallback((event) => { const cell = event.target.closest('.ant-table-cell') if (!cell || !event.currentTarget.contains(cell)) return @@ -345,11 +388,44 @@ const PermissionsMatrix = ({ event.currentTarget.style.removeProperty('--permissions-hovered-col') }, []) - const resolvedExpandedRowKeys = useMemo( - () => expandedRowKeys ?? dataSource.map((row) => row.key), - [dataSource, expandedRowKeys] + const resolvedExpandedRowKeys = useMemo(() => { + const sectionKeys = new Set(dataSource.map((row) => row.key)) + return expandedRowKeys.filter((key) => sectionKeys.has(key)) + }, [dataSource, expandedRowKeys]) + + const getTableBody = useCallback( + () => matrixRef.current?.querySelector('.ant-table-body') || null, + [] ) + const restoreTableScroll = useCallback(() => { + const body = getTableBody() + if (!body) return + body.scrollLeft = scrollPositionRef.current.left + body.scrollTop = scrollPositionRef.current.top + }, [getTableBody]) + + useLayoutEffect(() => { + restoreTableScroll() + }, [restoreTableScroll, resolvedExpandedRowKeys]) + + useEffect(() => { + const body = getTableBody() + if (!body) return + + const handleScroll = (event) => { + const next = { + left: event.currentTarget.scrollLeft, + top: event.currentTarget.scrollTop + } + scrollPositionRef.current = next + writePermissionsMatrixSession({ scroll: next }) + } + + body.addEventListener('scroll', handleScroll, { passive: true }) + return () => body.removeEventListener('scroll', handleScroll) + }, [getTableBody, resolvedExpandedRowKeys]) + const isSectionExpanded = useCallback( (record) => resolvedExpandedRowKeys.includes(record.key), [resolvedExpandedRowKeys] @@ -358,14 +434,20 @@ const PermissionsMatrix = ({ const handleToggleSection = useCallback( (record) => { if (!record.isGroup) return - setExpandedRowKeys((current) => { - const keys = current ?? dataSource.map((row) => row.key) - return keys.includes(record.key) - ? keys.filter((key) => key !== record.key) - : [...keys, record.key] - }) + persistExpandedRowKeys( + expandedRowKeys.includes(record.key) + ? expandedRowKeys.filter((key) => key !== record.key) + : [...expandedRowKeys, record.key] + ) }, - [dataSource] + [expandedRowKeys, persistExpandedRowKeys] + ) + + const handleExpandedRowsChange = useCallback( + (keys) => { + persistExpandedRowKeys(keys) + }, + [persistExpandedRowKeys] ) var adjustedScrollHeight = scrollHeight @@ -627,6 +709,7 @@ const PermissionsMatrix = ({ return (
null }}