Compare commits

..

No commits in common. "9b3de96be360f17cac8044739b07802a50a7771d" and "e4255443a0f75a7be757cfe554f6da36c1cc9a61" have entirely different histories.

4 changed files with 17 additions and 59 deletions

View File

@ -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,

View File

@ -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}

View File

@ -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}

View File

@ -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}
> >