Compare commits
No commits in common. "9b3de96be360f17cac8044739b07802a50a7771d" and "e4255443a0f75a7be757cfe554f6da36c1cc9a61" have entirely different histories.
9b3de96be3
...
e4255443a0
@ -47,7 +47,6 @@ const createSkeletonRows = (rowCount, keyPrefix, keyName) => {
|
|||||||
|
|
||||||
const ObjectChildTable = ({
|
const ObjectChildTable = ({
|
||||||
maxWidth = '100%',
|
maxWidth = '100%',
|
||||||
name,
|
|
||||||
properties = [],
|
properties = [],
|
||||||
columns = [],
|
columns = [],
|
||||||
visibleColumns = {},
|
visibleColumns = {},
|
||||||
@ -265,17 +264,6 @@ const ObjectChildTable = ({
|
|||||||
const rollupDataSource = useMemo(() => {
|
const rollupDataSource = useMemo(() => {
|
||||||
if (!rollups || rollups.length === 0) return []
|
if (!rollups || rollups.length === 0) return []
|
||||||
|
|
||||||
// Use value from form/props, or fall back to objectData when entering edit mode
|
|
||||||
// (form may not have populated the field yet)
|
|
||||||
const itemsForRollup =
|
|
||||||
value ?? (name && objectData ? objectData[name] : null) ?? []
|
|
||||||
|
|
||||||
// Build parent object with children array for rollup functions (e.g. objectData.parts)
|
|
||||||
const updatedObjectData = { ...objectData }
|
|
||||||
if (name) {
|
|
||||||
updatedObjectData[name] = itemsForRollup
|
|
||||||
}
|
|
||||||
|
|
||||||
// Single summary row where each rollup value is placed under
|
// Single summary row where each rollup value is placed under
|
||||||
// the column that matches its `property` field.
|
// the column that matches its `property` field.
|
||||||
const summaryRow = {}
|
const summaryRow = {}
|
||||||
@ -287,6 +275,8 @@ const ObjectChildTable = ({
|
|||||||
|
|
||||||
if (rollup && typeof rollup.value === 'function') {
|
if (rollup && typeof rollup.value === 'function') {
|
||||||
try {
|
try {
|
||||||
|
const updatedObjectData = { ...objectData }
|
||||||
|
updatedObjectData[property.name] = value
|
||||||
summaryRow[property.name] = rollup.value(updatedObjectData)
|
summaryRow[property.name] = rollup.value(updatedObjectData)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Fail quietly but log for debugging
|
// Fail quietly but log for debugging
|
||||||
@ -299,7 +289,7 @@ const ObjectChildTable = ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
return [summaryRow]
|
return [summaryRow]
|
||||||
}, [properties, rollups, objectData, value, name])
|
}, [properties, rollups, objectData, value])
|
||||||
|
|
||||||
const rollupColumns = useMemo(() => {
|
const rollupColumns = useMemo(() => {
|
||||||
const propertyColumns = properties.map((property, index) => {
|
const propertyColumns = properties.map((property, index) => {
|
||||||
@ -465,7 +455,6 @@ const ObjectChildTable = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
ObjectChildTable.propTypes = {
|
ObjectChildTable.propTypes = {
|
||||||
name: PropTypes.string,
|
|
||||||
properties: PropTypes.arrayOf(
|
properties: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
|
|||||||
@ -31,33 +31,12 @@ const ObjectDisplay = ({
|
|||||||
setObjectData((prev) => merge({}, prev, value))
|
setObjectData((prev) => merge({}, prev, value))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Ensure ID is valid before hydrate/subscribe (non-empty, not null/undefined)
|
// Detect minimal objects that only contain an _id
|
||||||
const isValidId = useCallback((id) => {
|
|
||||||
return id != null && String(id).trim() !== ''
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Extract string ID from object; handles both primitive _id and populated ref (_id as object)
|
|
||||||
const getStringId = useCallback((obj) => {
|
|
||||||
const id = obj?._id
|
|
||||||
if (id == null) return null
|
|
||||||
if (typeof id === 'string') return id
|
|
||||||
if (typeof id === 'object' && id !== null && typeof id._id === 'string')
|
|
||||||
return id._id
|
|
||||||
return null
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Detect minimal objects that only contain an _id (must be string, not populated object)
|
|
||||||
const isMinimalObject = useCallback((obj) => {
|
const isMinimalObject = useCallback((obj) => {
|
||||||
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false
|
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false
|
||||||
const keys = Object.keys(obj)
|
const keys = Object.keys(obj)
|
||||||
const id = obj._id
|
return keys.length === 1 && keys[0] === '_id' && obj._id
|
||||||
return (
|
}, [])
|
||||||
keys.length === 1 &&
|
|
||||||
keys[0] === '_id' &&
|
|
||||||
typeof id === 'string' &&
|
|
||||||
isValidId(id)
|
|
||||||
)
|
|
||||||
}, [isValidId])
|
|
||||||
|
|
||||||
// If only an _id is provided, fetch the full object via spotlight
|
// If only an _id is provided, fetch the full object via spotlight
|
||||||
const fetchFullObjectIfNeeded = useCallback(
|
const fetchFullObjectIfNeeded = useCallback(
|
||||||
@ -85,10 +64,9 @@ const ObjectDisplay = ({
|
|||||||
|
|
||||||
// Subscribe to object updates when component mounts
|
// Subscribe to object updates when component mounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = getStringId(object)
|
if (object?._id && objectType && connected && token != null) {
|
||||||
if (isValidId(id) && objectType && connected && token != null) {
|
|
||||||
const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
|
const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
|
||||||
id,
|
object._id,
|
||||||
objectType,
|
objectType,
|
||||||
updateObjectEventHandler
|
updateObjectEventHandler
|
||||||
)
|
)
|
||||||
@ -98,31 +76,27 @@ const ObjectDisplay = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
object,
|
object?._id,
|
||||||
objectType,
|
objectType,
|
||||||
subscribeToObjectUpdates,
|
subscribeToObjectUpdates,
|
||||||
connected,
|
connected,
|
||||||
token,
|
token,
|
||||||
updateObjectEventHandler,
|
updateObjectEventHandler
|
||||||
isValidId,
|
|
||||||
getStringId
|
|
||||||
])
|
])
|
||||||
|
|
||||||
// Update local state when object prop changes
|
// Update local state when object prop changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (token == null) return
|
if (token == null) return
|
||||||
const id = getStringId(object)
|
|
||||||
if (!isValidId(id)) return
|
|
||||||
const isMinimal = isMinimalObject(object)
|
const isMinimal = isMinimalObject(object)
|
||||||
// Only skip re-fetch when we have a minimal object and already hydrated this id
|
// Only skip re-fetch when we have a minimal object and already hydrated this id
|
||||||
if (isMinimal && idRef.current === id) return
|
if (isMinimal && idRef.current === object?._id) return
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
if (isMinimal) setIsHydrating(true)
|
if (isMinimal) setIsHydrating(true)
|
||||||
const hydrateObject = async () => {
|
const hydrateObject = async () => {
|
||||||
const fullObject = await fetchFullObjectIfNeeded(object)
|
const fullObject = await fetchFullObjectIfNeeded(object)
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setObjectData((prev) => merge({}, prev, fullObject))
|
setObjectData((prev) => merge({}, prev, fullObject))
|
||||||
if (isMinimal) idRef.current = id
|
if (isMinimal) idRef.current = object?._id
|
||||||
setIsHydrating(false)
|
setIsHydrating(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,7 +105,7 @@ const ObjectDisplay = ({
|
|||||||
cancelled = true
|
cancelled = true
|
||||||
setIsHydrating(false)
|
setIsHydrating(false)
|
||||||
}
|
}
|
||||||
}, [object, fetchFullObjectIfNeeded, isMinimalObject, isValidId, getStringId, token])
|
}, [object, fetchFullObjectIfNeeded, isMinimalObject, token])
|
||||||
if (!objectData) {
|
if (!objectData) {
|
||||||
return <Text type='secondary'>n/a</Text>
|
return <Text type='secondary'>n/a</Text>
|
||||||
}
|
}
|
||||||
@ -163,7 +137,7 @@ const ObjectDisplay = ({
|
|||||||
var hyperlink = null
|
var hyperlink = null
|
||||||
const defaultModelActions =
|
const defaultModelActions =
|
||||||
model.actions?.filter((action) => action.default == true) || []
|
model.actions?.filter((action) => action.default == true) || []
|
||||||
const objectId = getStringId(objectData)
|
const objectId = objectData._id
|
||||||
|
|
||||||
if (defaultModelActions.length >= 1 && objectId) {
|
if (defaultModelActions.length >= 1 && objectId) {
|
||||||
hyperlink = defaultModelActions[0].url(objectId)
|
hyperlink = defaultModelActions[0].url(objectId)
|
||||||
@ -255,9 +229,9 @@ const ObjectDisplay = ({
|
|||||||
<div style={{ minWidth: 0 }}>
|
<div style={{ minWidth: 0 }}>
|
||||||
{renderNameDisplay()}
|
{renderNameDisplay()}
|
||||||
|
|
||||||
{objectId && !objectData?.name ? (
|
{objectData?._id && !objectData?.name ? (
|
||||||
<IdDisplay
|
<IdDisplay
|
||||||
id={objectId}
|
id={objectData?._id}
|
||||||
reference={objectData?._reference || undefined}
|
reference={objectData?._reference || undefined}
|
||||||
type={objectType}
|
type={objectType}
|
||||||
longId={false}
|
longId={false}
|
||||||
|
|||||||
@ -407,7 +407,6 @@ const ObjectProperty = ({
|
|||||||
case 'objectChildren': {
|
case 'objectChildren': {
|
||||||
return (
|
return (
|
||||||
<ObjectChildTable
|
<ObjectChildTable
|
||||||
name={name}
|
|
||||||
value={value}
|
value={value}
|
||||||
properties={properties}
|
properties={properties}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
@ -798,7 +797,6 @@ const ObjectProperty = ({
|
|||||||
case 'objectChildren': {
|
case 'objectChildren': {
|
||||||
return (
|
return (
|
||||||
<ObjectChildTable
|
<ObjectChildTable
|
||||||
name={name}
|
|
||||||
properties={properties}
|
properties={properties}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
isEditing={true}
|
isEditing={true}
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { AuthContext } from './AuthContext'
|
|||||||
import { ApiServerContext } from './ApiServerContext'
|
import { ApiServerContext } from './ApiServerContext'
|
||||||
import NotificationCenter from '../common/NotificationCenter'
|
import NotificationCenter from '../common/NotificationCenter'
|
||||||
import Notification from '../common/Notification'
|
import Notification from '../common/Notification'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
|
||||||
|
|
||||||
const NotificationContext = createContext()
|
const NotificationContext = createContext()
|
||||||
|
|
||||||
@ -35,8 +34,6 @@ const NotificationProvider = ({ children }) => {
|
|||||||
const [notifications, setNotifications] = useState([])
|
const [notifications, setNotifications] = useState([])
|
||||||
const [notificationsLoading, setNotificationsLoading] = useState(false)
|
const [notificationsLoading, setNotificationsLoading] = useState(false)
|
||||||
|
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
|
||||||
|
|
||||||
const fetchNotifications = useCallback(async () => {
|
const fetchNotifications = useCallback(async () => {
|
||||||
if (!authenticated) return []
|
if (!authenticated) return []
|
||||||
setNotificationsLoading(true)
|
setNotificationsLoading(true)
|
||||||
@ -177,7 +174,7 @@ const NotificationProvider = ({ children }) => {
|
|||||||
<Drawer
|
<Drawer
|
||||||
title='Notifications'
|
title='Notifications'
|
||||||
placement='right'
|
placement='right'
|
||||||
width={isMobile ? '100%' : 460}
|
width={400}
|
||||||
onClose={() => setNotificationCenterVisible(false)}
|
onClose={() => setNotificationCenterVisible(false)}
|
||||||
open={notificationCenterVisible}
|
open={notificationCenterVisible}
|
||||||
>
|
>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user