Compare commits
No commits in common. "51abe4ca679eedfca49c80df8cf6d355c8940a26" and "0a347c1396bd99178479289df378e6276032ea61" have entirely different histories.
51abe4ca67
...
0a347c1396
@ -27,30 +27,6 @@ const SPOTLIGHT_CACHE_TTL_MS = 10_000
|
|||||||
const spotlightCache = new Map()
|
const spotlightCache = new Map()
|
||||||
const runningSpotlightFetches = 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) => {
|
const stableStringify = (value) => {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return `[${value.map(stableStringify).join(',')}]`
|
return `[${value.map(stableStringify).join(',')}]`
|
||||||
@ -101,8 +77,6 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
const [showErrorModal, setShowErrorModal] = useState(false)
|
const [showErrorModal, setShowErrorModal] = useState(false)
|
||||||
const [errorModalContent, setErrorModalContent] = useState('')
|
const [errorModalContent, setErrorModalContent] = useState('')
|
||||||
const [retryCallback, setRetryCallback] = useState(null)
|
const [retryCallback, setRetryCallback] = useState(null)
|
||||||
const [userSettings, setUserSettings] = useState(createEmptyUserSettings)
|
|
||||||
const [userSettingsLoaded, setUserSettingsLoaded] = useState(false)
|
|
||||||
const subscribedCallbacksRef = useRef(new Map())
|
const subscribedCallbacksRef = useRef(new Map())
|
||||||
const subscribedLockCallbacksRef = useRef(new Map())
|
const subscribedLockCallbacksRef = useRef(new Map())
|
||||||
const notificationListenersRef = useRef(new Set())
|
const notificationListenersRef = useRef(new Set())
|
||||||
@ -155,68 +129,6 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
notificationListenersRef.current.delete(callback)
|
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(() => {
|
const connectToServer = useCallback(() => {
|
||||||
if (token && authenticated == true) {
|
if (token && authenticated == true) {
|
||||||
logger.debug('Token is available, connecting to api server...')
|
logger.debug('Token is available, connecting to api server...')
|
||||||
@ -231,20 +143,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
|
|
||||||
newSocket.on('connect', () => {
|
newSocket.on('connect', () => {
|
||||||
logger.debug('Api Server connected')
|
logger.debug('Api Server connected')
|
||||||
newSocket.emit('authenticate', { token: token }, async (result) => {
|
newSocket.emit('authenticate', { token: token }, () => {
|
||||||
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)
|
setConnecting(false)
|
||||||
setConnected(true)
|
setConnected(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
@ -281,8 +180,6 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
setError('Api Server disconnected')
|
setError('Api Server disconnected')
|
||||||
clearSubscriptions()
|
clearSubscriptions()
|
||||||
setConnected(false)
|
setConnected(false)
|
||||||
setUserSettings(createEmptyUserSettings())
|
|
||||||
setUserSettingsLoaded(false)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
newSocket.on('connect_error', (err) => {
|
newSocket.on('connect_error', (err) => {
|
||||||
@ -291,8 +188,6 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
setError('Api Server connection error')
|
setError('Api Server connection error')
|
||||||
clearSubscriptions()
|
clearSubscriptions()
|
||||||
setConnected(false)
|
setConnected(false)
|
||||||
setUserSettings(createEmptyUserSettings())
|
|
||||||
setUserSettingsLoaded(false)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
newSocket.on('error', (err) => {
|
newSocket.on('error', (err) => {
|
||||||
@ -302,14 +197,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
|
|
||||||
socketRef.current = newSocket
|
socketRef.current = newSocket
|
||||||
}
|
}
|
||||||
}, [
|
}, [token, authenticated, messageApi, handleLockUpdate, clearSubscriptions])
|
||||||
token,
|
|
||||||
authenticated,
|
|
||||||
messageApi,
|
|
||||||
handleLockUpdate,
|
|
||||||
clearSubscriptions,
|
|
||||||
getUserSettings
|
|
||||||
])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (token && authenticated == true) {
|
if (token && authenticated == true) {
|
||||||
@ -318,8 +206,6 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
logger.debug('Token not available, disconnecting api server...')
|
logger.debug('Token not available, disconnecting api server...')
|
||||||
socketRef.current.disconnect()
|
socketRef.current.disconnect()
|
||||||
socketRef.current = null
|
socketRef.current = null
|
||||||
setUserSettings(createEmptyUserSettings())
|
|
||||||
setUserSettingsLoaded(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up function
|
// Clean up function
|
||||||
@ -1945,10 +1831,6 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
error,
|
error,
|
||||||
connecting,
|
connecting,
|
||||||
connected,
|
connected,
|
||||||
userSettings,
|
|
||||||
userSettingsLoaded,
|
|
||||||
getUserSettings,
|
|
||||||
updateUserSettings,
|
|
||||||
lockObject,
|
lockObject,
|
||||||
unlockObject,
|
unlockObject,
|
||||||
fetchObjectLock,
|
fetchObjectLock,
|
||||||
|
|||||||
@ -1,20 +1,29 @@
|
|||||||
import { useCallback, useContext } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
|
||||||
|
|
||||||
const useCollapseState = (componentName, defaultState = {}) => {
|
const useCollapseState = (componentName, defaultState = {}) => {
|
||||||
const { userSettings, updateUserSettings } = useContext(ApiServerContext)
|
const getInitialState = () => {
|
||||||
const collapseState =
|
const stored = sessionStorage.getItem(`${componentName}_collapseState`)
|
||||||
userSettings.collapseState[componentName] ?? defaultState
|
if (stored) {
|
||||||
|
return JSON.parse(stored)
|
||||||
|
}
|
||||||
|
return defaultState
|
||||||
|
}
|
||||||
|
|
||||||
const updateCollapseState = useCallback(
|
const [collapseState, setCollapseState] = useState(getInitialState)
|
||||||
(key, value) => {
|
|
||||||
updateUserSettings('collapseState', componentName, {
|
useEffect(() => {
|
||||||
...collapseState,
|
sessionStorage.setItem(
|
||||||
[key]: value
|
`${componentName}_collapseState`,
|
||||||
})
|
JSON.stringify(collapseState)
|
||||||
},
|
)
|
||||||
[collapseState, componentName, updateUserSettings]
|
}, [collapseState, componentName])
|
||||||
)
|
|
||||||
|
const updateCollapseState = (key, value) => {
|
||||||
|
setCollapseState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[key]: value
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
return [collapseState, updateCollapseState]
|
return [collapseState, updateCollapseState]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
import { useCallback, useContext, useMemo } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
|
||||||
|
|
||||||
const useColumnVisibility = (type, customColumns) => {
|
const useColumnVisibility = (type, customColumns) => {
|
||||||
const { userSettings, updateUserSettings } = useContext(ApiServerContext)
|
const getInitialVisibility = () => {
|
||||||
const defaultVisibility = useMemo(() => {
|
const storageKey = `${type}_columnVisibility`
|
||||||
|
const stored = sessionStorage.getItem(storageKey)
|
||||||
|
if (stored) {
|
||||||
|
return JSON.parse(stored)
|
||||||
|
}
|
||||||
// Default visibility - all columns visible
|
// Default visibility - all columns visible
|
||||||
if (customColumns && Array.isArray(customColumns)) {
|
if (customColumns && Array.isArray(customColumns)) {
|
||||||
return customColumns.reduce((acc, col) => {
|
return customColumns.reduce((acc, col) => {
|
||||||
@ -25,20 +28,23 @@ const useColumnVisibility = (type, customColumns) => {
|
|||||||
}
|
}
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
}, [customColumns, type])
|
}
|
||||||
|
|
||||||
const columnVisibility =
|
const [columnVisibility, setColumnVisibility] = useState(getInitialVisibility)
|
||||||
userSettings.columnVisibility[type] ?? defaultVisibility
|
|
||||||
|
|
||||||
const updateColumnVisibility = useCallback(
|
useEffect(() => {
|
||||||
(key, value) => {
|
sessionStorage.setItem(
|
||||||
updateUserSettings('columnVisibility', type, {
|
`${type}_columnVisibility`,
|
||||||
...columnVisibility,
|
JSON.stringify(columnVisibility)
|
||||||
[key]: value
|
)
|
||||||
})
|
}, [columnVisibility, type])
|
||||||
},
|
|
||||||
[columnVisibility, type, updateUserSettings]
|
const updateColumnVisibility = (key, value) => {
|
||||||
)
|
setColumnVisibility((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[key]: value
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
return [columnVisibility, updateColumnVisibility]
|
return [columnVisibility, updateColumnVisibility]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,19 @@
|
|||||||
import { useCallback, useContext } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
|
||||||
|
|
||||||
const useFilterSidebarVisibility = (componentName, defaultValue = false) => {
|
const useFilterSidebarVisibility = (componentName, defaultValue = false) => {
|
||||||
const { userSettings, updateUserSettings } = useContext(ApiServerContext)
|
const getInitialVisibility = () => {
|
||||||
const showFilterSidebar =
|
const stored = sessionStorage.getItem(`${componentName}_filterSidebarVisibility`)
|
||||||
userSettings.filterSidebarVisibility[componentName] ?? defaultValue
|
if (stored !== null) {
|
||||||
|
return stored === 'true'
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
const setShowFilterSidebar = useCallback(
|
const [showFilterSidebar, setShowFilterSidebar] = useState(getInitialVisibility)
|
||||||
(nextValue) => {
|
|
||||||
const value =
|
useEffect(() => {
|
||||||
typeof nextValue === 'function'
|
sessionStorage.setItem(`${componentName}_filterSidebarVisibility`, showFilterSidebar)
|
||||||
? nextValue(showFilterSidebar)
|
}, [showFilterSidebar, componentName])
|
||||||
: nextValue
|
|
||||||
updateUserSettings('filterSidebarVisibility', componentName, value)
|
|
||||||
},
|
|
||||||
[componentName, showFilterSidebar, updateUserSettings]
|
|
||||||
)
|
|
||||||
|
|
||||||
return [showFilterSidebar, setShowFilterSidebar]
|
return [showFilterSidebar, setShowFilterSidebar]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,16 @@
|
|||||||
import { useCallback, useContext } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
|
||||||
|
|
||||||
const useViewMode = (componentName, defaultMode = 'list') => {
|
const useViewMode = (componentName, defaultMode = 'list') => {
|
||||||
const { userSettings, updateUserSettings } = useContext(ApiServerContext)
|
const getInitialViewMode = () => {
|
||||||
const viewMode = userSettings.viewMode[componentName] ?? defaultMode
|
const stored = sessionStorage.getItem(`${componentName}_viewMode`)
|
||||||
|
return stored ? stored : defaultMode
|
||||||
|
}
|
||||||
|
|
||||||
const setViewMode = useCallback(
|
const [viewMode, setViewMode] = useState(getInitialViewMode)
|
||||||
(nextMode) => {
|
|
||||||
const mode =
|
useEffect(() => {
|
||||||
typeof nextMode === 'function' ? nextMode(viewMode) : nextMode
|
sessionStorage.setItem(`${componentName}_viewMode`, viewMode)
|
||||||
updateUserSettings('viewMode', componentName, mode)
|
}, [viewMode, componentName])
|
||||||
},
|
|
||||||
[componentName, updateUserSettings, viewMode]
|
|
||||||
)
|
|
||||||
|
|
||||||
return [viewMode, setViewMode]
|
return [viewMode, setViewMode]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -186,13 +186,6 @@ export const Payment = {
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
columnWidth: 250
|
columnWidth: 250
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'postedAt',
|
|
||||||
label: 'Posted At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 175
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'invoice',
|
name: 'invoice',
|
||||||
label: 'Invoice',
|
label: 'Invoice',
|
||||||
@ -202,13 +195,6 @@ export const Payment = {
|
|||||||
showHyperlink: true,
|
showHyperlink: true,
|
||||||
columnWidth: 200
|
columnWidth: 200
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'authorisedAt',
|
|
||||||
label: 'Authorised At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 175
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'payTo',
|
name: 'payTo',
|
||||||
label: 'Pay To',
|
label: 'Pay To',
|
||||||
@ -225,6 +211,27 @@ export const Payment = {
|
|||||||
return objectData?.invoice?.from
|
return objectData?.invoice?.from
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'paymentDate',
|
||||||
|
label: 'Payment Date',
|
||||||
|
type: 'dateTime',
|
||||||
|
required: true,
|
||||||
|
columnWidth: 175
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postedAt',
|
||||||
|
label: 'Posted At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 175
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'authorisedAt',
|
||||||
|
label: 'Authorised At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 175
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'declinedAt',
|
name: 'declinedAt',
|
||||||
label: 'Declined At',
|
label: 'Declined At',
|
||||||
@ -232,6 +239,13 @@ export const Payment = {
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
columnWidth: 175
|
columnWidth: 175
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'cancelledAt',
|
||||||
|
label: 'Cancelled At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 175
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'amount',
|
name: 'amount',
|
||||||
label: 'Amount',
|
label: 'Amount',
|
||||||
@ -241,19 +255,19 @@ export const Payment = {
|
|||||||
required: true,
|
required: true,
|
||||||
columnWidth: 150
|
columnWidth: 150
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'cancelledAt',
|
|
||||||
label: 'Cancelled At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 175
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'paymentMethod',
|
name: 'paymentMethod',
|
||||||
label: 'Payment Method',
|
label: 'Payment Method',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: false,
|
required: false,
|
||||||
columnWidth: 150
|
columnWidth: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'notes',
|
||||||
|
label: 'Notes',
|
||||||
|
type: 'text',
|
||||||
|
required: false,
|
||||||
|
columnWidth: 200
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: [
|
stats: [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user