Implement user settings management in ApiServerContext, including functions to get and update user settings. Refactor hooks for collapse state, column visibility, filter sidebar visibility, and view mode to utilize centralized user settings, enhancing state management and improving user experience.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
This commit is contained in:
parent
e3a9b8b6f9
commit
51abe4ca67
@ -27,6 +27,30 @@ const SPOTLIGHT_CACHE_TTL_MS = 10_000
|
||||
const spotlightCache = new Map()
|
||||
const runningSpotlightFetches = new Map()
|
||||
|
||||
const createEmptyUserSettings = () => ({
|
||||
viewMode: {},
|
||||
filterSidebarVisibility: {},
|
||||
columnVisibility: {},
|
||||
collapseState: {}
|
||||
})
|
||||
|
||||
const normalizeUserSettingsCategory = (category) =>
|
||||
category && typeof category === 'object' && !Array.isArray(category)
|
||||
? category
|
||||
: {}
|
||||
|
||||
const normalizeUserSettings = (settings = {}) => ({
|
||||
viewMode: normalizeUserSettingsCategory(settings?.viewMode),
|
||||
filterSidebarVisibility: normalizeUserSettingsCategory(
|
||||
settings?.filterSidebarVisibility
|
||||
),
|
||||
columnVisibility: normalizeUserSettingsCategory(settings?.columnVisibility),
|
||||
collapseState: normalizeUserSettingsCategory(settings?.collapseState)
|
||||
})
|
||||
|
||||
const emitWithAcknowledgement = (socket, eventName, data) =>
|
||||
new Promise((resolve) => socket.emit(eventName, data, resolve))
|
||||
|
||||
const stableStringify = (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return `[${value.map(stableStringify).join(',')}]`
|
||||
@ -77,6 +101,8 @@ const ApiServerProvider = ({ children }) => {
|
||||
const [showErrorModal, setShowErrorModal] = useState(false)
|
||||
const [errorModalContent, setErrorModalContent] = useState('')
|
||||
const [retryCallback, setRetryCallback] = useState(null)
|
||||
const [userSettings, setUserSettings] = useState(createEmptyUserSettings)
|
||||
const [userSettingsLoaded, setUserSettingsLoaded] = useState(false)
|
||||
const subscribedCallbacksRef = useRef(new Map())
|
||||
const subscribedLockCallbacksRef = useRef(new Map())
|
||||
const notificationListenersRef = useRef(new Set())
|
||||
@ -129,6 +155,68 @@ const ApiServerProvider = ({ children }) => {
|
||||
notificationListenersRef.current.delete(callback)
|
||||
}, [])
|
||||
|
||||
const getUserSettings = useCallback(async (socket = socketRef.current) => {
|
||||
if (!socket?.connected) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await emitWithAcknowledgement(
|
||||
socket,
|
||||
'getUserSettings',
|
||||
{}
|
||||
)
|
||||
if (!result?.success) {
|
||||
throw new Error(result?.error || 'Unable to get user settings')
|
||||
}
|
||||
|
||||
const settings = normalizeUserSettings(result.settings)
|
||||
setUserSettings(settings)
|
||||
return settings
|
||||
} catch (err) {
|
||||
logger.error('Failed to get user settings:', err)
|
||||
setUserSettings(createEmptyUserSettings())
|
||||
return null
|
||||
} finally {
|
||||
setUserSettingsLoaded(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const updateUserSettings = useCallback(
|
||||
async (category, key, value) => {
|
||||
setUserSettings((currentSettings) => ({
|
||||
...currentSettings,
|
||||
[category]: {
|
||||
...currentSettings[category],
|
||||
[key]: value
|
||||
}
|
||||
}))
|
||||
|
||||
const socket = socketRef.current
|
||||
if (!socket?.connected) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await emitWithAcknowledgement(
|
||||
socket,
|
||||
'updateUserSettings',
|
||||
{ category, key, value }
|
||||
)
|
||||
if (!result?.success) {
|
||||
throw new Error(result?.error || 'Unable to update user settings')
|
||||
}
|
||||
return result.settings
|
||||
} catch (err) {
|
||||
logger.error('Failed to update user settings:', err)
|
||||
messageApi.error('Failed to save user settings')
|
||||
await getUserSettings(socket)
|
||||
return null
|
||||
}
|
||||
},
|
||||
[getUserSettings, messageApi]
|
||||
)
|
||||
|
||||
const connectToServer = useCallback(() => {
|
||||
if (token && authenticated == true) {
|
||||
logger.debug('Token is available, connecting to api server...')
|
||||
@ -143,7 +231,20 @@ const ApiServerProvider = ({ children }) => {
|
||||
|
||||
newSocket.on('connect', () => {
|
||||
logger.debug('Api Server connected')
|
||||
newSocket.emit('authenticate', { token: token }, () => {
|
||||
newSocket.emit('authenticate', { token: token }, async (result) => {
|
||||
if (result?.valid !== true) {
|
||||
setConnecting(false)
|
||||
setError('Api Server authentication failed')
|
||||
newSocket.disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
setUserSettings(createEmptyUserSettings())
|
||||
setUserSettingsLoaded(false)
|
||||
await getUserSettings(newSocket)
|
||||
if (!newSocket.connected) {
|
||||
return
|
||||
}
|
||||
setConnecting(false)
|
||||
setConnected(true)
|
||||
setError(null)
|
||||
@ -180,6 +281,8 @@ const ApiServerProvider = ({ children }) => {
|
||||
setError('Api Server disconnected')
|
||||
clearSubscriptions()
|
||||
setConnected(false)
|
||||
setUserSettings(createEmptyUserSettings())
|
||||
setUserSettingsLoaded(false)
|
||||
})
|
||||
|
||||
newSocket.on('connect_error', (err) => {
|
||||
@ -188,6 +291,8 @@ const ApiServerProvider = ({ children }) => {
|
||||
setError('Api Server connection error')
|
||||
clearSubscriptions()
|
||||
setConnected(false)
|
||||
setUserSettings(createEmptyUserSettings())
|
||||
setUserSettingsLoaded(false)
|
||||
})
|
||||
|
||||
newSocket.on('error', (err) => {
|
||||
@ -197,7 +302,14 @@ const ApiServerProvider = ({ children }) => {
|
||||
|
||||
socketRef.current = newSocket
|
||||
}
|
||||
}, [token, authenticated, messageApi, handleLockUpdate, clearSubscriptions])
|
||||
}, [
|
||||
token,
|
||||
authenticated,
|
||||
messageApi,
|
||||
handleLockUpdate,
|
||||
clearSubscriptions,
|
||||
getUserSettings
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (token && authenticated == true) {
|
||||
@ -206,6 +318,8 @@ const ApiServerProvider = ({ children }) => {
|
||||
logger.debug('Token not available, disconnecting api server...')
|
||||
socketRef.current.disconnect()
|
||||
socketRef.current = null
|
||||
setUserSettings(createEmptyUserSettings())
|
||||
setUserSettingsLoaded(false)
|
||||
}
|
||||
|
||||
// Clean up function
|
||||
@ -1831,6 +1945,10 @@ const ApiServerProvider = ({ children }) => {
|
||||
error,
|
||||
connecting,
|
||||
connected,
|
||||
userSettings,
|
||||
userSettingsLoaded,
|
||||
getUserSettings,
|
||||
updateUserSettings,
|
||||
lockObject,
|
||||
unlockObject,
|
||||
fetchObjectLock,
|
||||
|
||||
@ -1,29 +1,20 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useCallback, useContext } from 'react'
|
||||
import { ApiServerContext } from '../context/ApiServerContext'
|
||||
|
||||
const useCollapseState = (componentName, defaultState = {}) => {
|
||||
const getInitialState = () => {
|
||||
const stored = sessionStorage.getItem(`${componentName}_collapseState`)
|
||||
if (stored) {
|
||||
return JSON.parse(stored)
|
||||
}
|
||||
return defaultState
|
||||
}
|
||||
const { userSettings, updateUserSettings } = useContext(ApiServerContext)
|
||||
const collapseState =
|
||||
userSettings.collapseState[componentName] ?? defaultState
|
||||
|
||||
const [collapseState, setCollapseState] = useState(getInitialState)
|
||||
|
||||
useEffect(() => {
|
||||
sessionStorage.setItem(
|
||||
`${componentName}_collapseState`,
|
||||
JSON.stringify(collapseState)
|
||||
)
|
||||
}, [collapseState, componentName])
|
||||
|
||||
const updateCollapseState = (key, value) => {
|
||||
setCollapseState((prev) => ({
|
||||
...prev,
|
||||
[key]: value
|
||||
}))
|
||||
}
|
||||
const updateCollapseState = useCallback(
|
||||
(key, value) => {
|
||||
updateUserSettings('collapseState', componentName, {
|
||||
...collapseState,
|
||||
[key]: value
|
||||
})
|
||||
},
|
||||
[collapseState, componentName, updateUserSettings]
|
||||
)
|
||||
|
||||
return [collapseState, updateCollapseState]
|
||||
}
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useCallback, useContext, useMemo } from 'react'
|
||||
import { getModelByName } from '../../../database/ObjectModels'
|
||||
import { ApiServerContext } from '../context/ApiServerContext'
|
||||
|
||||
const useColumnVisibility = (type, customColumns) => {
|
||||
const getInitialVisibility = () => {
|
||||
const storageKey = `${type}_columnVisibility`
|
||||
const stored = sessionStorage.getItem(storageKey)
|
||||
if (stored) {
|
||||
return JSON.parse(stored)
|
||||
}
|
||||
const { userSettings, updateUserSettings } = useContext(ApiServerContext)
|
||||
const defaultVisibility = useMemo(() => {
|
||||
// Default visibility - all columns visible
|
||||
if (customColumns && Array.isArray(customColumns)) {
|
||||
return customColumns.reduce((acc, col) => {
|
||||
@ -28,23 +25,20 @@ const useColumnVisibility = (type, customColumns) => {
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
}, [customColumns, type])
|
||||
|
||||
const [columnVisibility, setColumnVisibility] = useState(getInitialVisibility)
|
||||
const columnVisibility =
|
||||
userSettings.columnVisibility[type] ?? defaultVisibility
|
||||
|
||||
useEffect(() => {
|
||||
sessionStorage.setItem(
|
||||
`${type}_columnVisibility`,
|
||||
JSON.stringify(columnVisibility)
|
||||
)
|
||||
}, [columnVisibility, type])
|
||||
|
||||
const updateColumnVisibility = (key, value) => {
|
||||
setColumnVisibility((prev) => ({
|
||||
...prev,
|
||||
[key]: value
|
||||
}))
|
||||
}
|
||||
const updateColumnVisibility = useCallback(
|
||||
(key, value) => {
|
||||
updateUserSettings('columnVisibility', type, {
|
||||
...columnVisibility,
|
||||
[key]: value
|
||||
})
|
||||
},
|
||||
[columnVisibility, type, updateUserSettings]
|
||||
)
|
||||
|
||||
return [columnVisibility, updateColumnVisibility]
|
||||
}
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useCallback, useContext } from 'react'
|
||||
import { ApiServerContext } from '../context/ApiServerContext'
|
||||
|
||||
const useFilterSidebarVisibility = (componentName, defaultValue = false) => {
|
||||
const getInitialVisibility = () => {
|
||||
const stored = sessionStorage.getItem(`${componentName}_filterSidebarVisibility`)
|
||||
if (stored !== null) {
|
||||
return stored === 'true'
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
const { userSettings, updateUserSettings } = useContext(ApiServerContext)
|
||||
const showFilterSidebar =
|
||||
userSettings.filterSidebarVisibility[componentName] ?? defaultValue
|
||||
|
||||
const [showFilterSidebar, setShowFilterSidebar] = useState(getInitialVisibility)
|
||||
|
||||
useEffect(() => {
|
||||
sessionStorage.setItem(`${componentName}_filterSidebarVisibility`, showFilterSidebar)
|
||||
}, [showFilterSidebar, componentName])
|
||||
const setShowFilterSidebar = useCallback(
|
||||
(nextValue) => {
|
||||
const value =
|
||||
typeof nextValue === 'function'
|
||||
? nextValue(showFilterSidebar)
|
||||
: nextValue
|
||||
updateUserSettings('filterSidebarVisibility', componentName, value)
|
||||
},
|
||||
[componentName, showFilterSidebar, updateUserSettings]
|
||||
)
|
||||
|
||||
return [showFilterSidebar, setShowFilterSidebar]
|
||||
}
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useCallback, useContext } from 'react'
|
||||
import { ApiServerContext } from '../context/ApiServerContext'
|
||||
|
||||
const useViewMode = (componentName, defaultMode = 'list') => {
|
||||
const getInitialViewMode = () => {
|
||||
const stored = sessionStorage.getItem(`${componentName}_viewMode`)
|
||||
return stored ? stored : defaultMode
|
||||
}
|
||||
const { userSettings, updateUserSettings } = useContext(ApiServerContext)
|
||||
const viewMode = userSettings.viewMode[componentName] ?? defaultMode
|
||||
|
||||
const [viewMode, setViewMode] = useState(getInitialViewMode)
|
||||
|
||||
useEffect(() => {
|
||||
sessionStorage.setItem(`${componentName}_viewMode`, viewMode)
|
||||
}, [viewMode, componentName])
|
||||
const setViewMode = useCallback(
|
||||
(nextMode) => {
|
||||
const mode =
|
||||
typeof nextMode === 'function' ? nextMode(viewMode) : nextMode
|
||||
updateUserSettings('viewMode', componentName, mode)
|
||||
},
|
||||
[componentName, updateUserSettings, viewMode]
|
||||
)
|
||||
|
||||
return [viewMode, setViewMode]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user