Enhance SpotlightTooltip Component and Update Styles
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good

- Refactored SpotlightTooltip component to improve action handling and data rendering.
- Introduced new utility functions for value checking and nested property access.
- Updated rendering logic to utilize ObjectCard for better display of spotlight data.
- Added CSS styles for spotlight tooltip to ensure a transparent background.
This commit is contained in:
Tom Butcher 2026-11-08 21:11:14 +00:00
parent 3544ac0d89
commit f0b0ad41a6
2 changed files with 123 additions and 73 deletions

View File

@ -484,6 +484,11 @@ code {
padding: 0 !important;
}
.spotlight-tooltip.ant-card,
.spotlight-tooltip .ant-card {
background: transparent !important;
}
.ant-popover:has(.spotlight-tooltip) {
z-index: 1051;
}

View File

@ -1,34 +1,102 @@
// SpotlightTooltip.js
import PropTypes from 'prop-types'
import { Descriptions, Card, Flex, Typography, Skeleton, Spin } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { useEffect, useState, useContext, useCallback } from 'react'
import { Button, Card, Flex, Typography } from 'antd'
import {
createElement,
useEffect,
useState,
useContext,
useCallback,
useMemo
} from 'react'
import { AuthContext } from '../context/AuthContext'
import ObjectProperty from './ObjectProperty'
import { useActions } from '../context/ActionsContext'
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
import { ApiServerContext } from '../context/ApiServerContext'
import merge from 'lodash/merge'
import {
getModelByName,
getModelProperty
} from '../../../database/ObjectModels'
import { getModelByName } from '../../../database/ObjectModels'
import { hasActionPermission } from '../../../database/permissions'
import ObjectCard from './ObjectCard'
import Tooltip from './Tooltip'
const { Text } = Typography
const hasValue = (value) => value !== undefined && value !== null && value !== ''
const getNestedValue = (obj, path) => {
return path.split('.').reduce((current, key) => current?.[key], obj)
}
const SpotlightTooltip = ({ query, type }) => {
const [spotlightData, setSpotlightData] = useState(null)
const [loading, setLoading] = useState(true)
const [initialized, setInitialized] = useState(false)
const { token } = useContext(AuthContext)
const { token, userProfile } = useContext(AuthContext)
const { callAction } = useActions()
const { fetchSpotlightData, subscribeToObjectUpdates, connected } =
useContext(ApiServerContext)
// Get the model for this type
const model = getModelByName(type)
const modelProperties = model.properties || []
const rowActions = useMemo(
() => model.actions?.filter((action) => action.row == true) || [],
[model]
)
const renderActions = useCallback(
(objectData, actionsDisabled = false) => {
return (
<Flex gap='small' align='center' justify='center'>
{rowActions.map((action, index) => {
const denied = !hasActionPermission(userProfile, model, action.name)
var disabled = denied
if (action.disabled) {
if (typeof action.disabled === 'function') {
disabled =
denied ||
action.disabled({
...objectData,
_user: userProfile
}) ||
actionsDisabled
} else {
disabled = denied || action.disabled || actionsDisabled
}
}
return (
<Tooltip key={index} title={action.label}>
<Button
icon={
action.icon ? (
createElement(action.icon)
) : (
<QuestionCircleIcon />
)
}
disabled={disabled || objectData?.isSkeleton}
type={'text'}
size={'small'}
danger={action?.danger || false}
onClick={() => {
if (denied) return
callAction(
action,
{ ...objectData, _user: userProfile },
type
)
}}
/>
</Tooltip>
)
})}
</Flex>
)
},
[callAction, model, rowActions, type, userProfile]
)
// Update event handler
const updateObjectEventHandler = useCallback((value) => {
setSpotlightData((prev) => merge({}, prev, value))
}, [])
@ -47,7 +115,6 @@ const SpotlightTooltip = ({ query, type }) => {
}
}, [token, fetchSpotlight, initialized])
// Subscribe to object updates when component mounts
useEffect(() => {
if (spotlightData?._id && type && connected && token) {
const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
@ -69,6 +136,28 @@ const SpotlightTooltip = ({ query, type }) => {
updateObjectEventHandler
])
const visibleColumns = useMemo(() => {
const columnNames = model.columns || []
const columns = { name: true }
if (!spotlightData) {
columnNames.slice(0, 4).forEach((colName) => {
columns[colName] = true
})
columnNames.forEach((colName) => {
if (columns[colName] == null) columns[colName] = false
})
return columns
}
for (const colName of columnNames) {
if (colName === 'name') continue
columns[colName] = hasValue(getNestedValue(spotlightData, colName))
}
return columns
}, [model.columns, spotlightData])
if (!spotlightData && !loading) {
return (
<Card className='spotlight-tooltip'>
@ -87,68 +176,24 @@ const SpotlightTooltip = ({ query, type }) => {
)
}
// Helper to get nested property value
const getNestedValue = (obj, path) => {
return path.split('.').reduce((current, key) => current?.[key], obj)
}
return (
<div className='spotlight-tooltip'>
<Spin indicator={<LoadingOutlined />} spinning={loading}>
<Descriptions bordered column={1} size='small'>
{loading ? (
<>
<Descriptions.Item
label={
<Skeleton.Input active size='small' style={{ width: 80 }} />
}
>
<Skeleton.Input active size='small' style={{ width: 120 }} />
</Descriptions.Item>
<Descriptions.Item
label={
<Skeleton.Input active size='small' style={{ width: 80 }} />
}
>
<Skeleton.Input active size='small' style={{ width: 120 }} />
</Descriptions.Item>
<Descriptions.Item
label={
<Skeleton.Input active size='small' style={{ width: 80 }} />
}
>
<Skeleton.Input active size='small' style={{ width: 120 }} />
</Descriptions.Item>
</>
) : (
modelProperties
.filter((prop) => {
const value = getNestedValue(spotlightData, prop.name)
return value !== undefined && value !== null && value !== ''
})
.map((prop) => {
const value = getNestedValue(spotlightData, prop.name)
return (
<Descriptions.Item key={prop.name} label={prop.label}>
<ObjectProperty
{...getModelProperty(type, prop.name)}
value={value}
objectData={spotlightData}
objectType={prop.objectType || type}
isEditing={false}
longId={false}
showSpotlight={false}
showLabel={false}
showName={false}
showId={false}
showQuantity={false}
/>
</Descriptions.Item>
)
})
)}
</Descriptions>
</Spin>
<div className='spotlight-tooltip' style={{ width: 360 }}>
<ObjectCard
isSkeleton={!spotlightData && loading}
model={model}
modelProperties={modelProperties}
visibleColumns={visibleColumns}
record={
spotlightData || {
_id: query,
isSkeleton: true
}
}
isEditing={false}
rowActions={rowActions}
renderActions={renderActions}
cardStyle='borderless'
/>
</div>
)
}