import React, { useCallback } from 'react' import { Box, Typography, IconButton, Chip, Tooltip, Button } from '@mui/material' import { Theme } from '@mui/material/styles' import { withStyles } from '@mui/styles' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import DeleteIcon from '@mui/icons-material/Delete' import DeleteSweepIcon from '@mui/icons-material/DeleteSweep' import Info from '@mui/icons-material/Info' import * as q from '../../../../backend/src/Model' import { AppState } from '../../reducers' import { sidebarActions, globalActions } from '../../actions' import Copy from '../helper/Copy' import Save from '../helper/Save' import DateFormatter from '../helper/DateFormatter' import ValueRenderer from './ValueRenderer/ValueRenderer' import MessageHistory from './ValueRenderer/MessageHistory' import ActionButtons from './ValueRenderer/ActionButtons' import DeleteSelectedTopicButton from './ValueRenderer/DeleteSelectedTopicButton' import { useDecoder } from '../hooks/useDecoder' import SimpleBreadcrumb from './SimpleBreadcrumb' import AIAssistant from './AIAssistant' interface Props { node?: q.TreeNode classes: any compareMessage?: q.Message connectionId?: string sidebarActions: typeof sidebarActions globalActions: typeof globalActions } function DetailsTab(props: Props) { const { node, compareMessage, classes } = props const decodeMessage = useDecoder(node) const getDecodedValue = useCallback( () => node?.message && decodeMessage(node.message)?.message?.toUnicodeString(), [node, decodeMessage] ) const getData = () => { if (node?.message && node.message.payload) { return node.message.payload.base64Message } } const handleMessageHistorySelect = useCallback( (message: q.Message) => { if (message !== compareMessage) { props.sidebarActions.setCompareMessage(message) } else { props.sidebarActions.setCompareMessage(undefined) } }, [compareMessage, props.sidebarActions] ) const deleteTopic = useCallback( (topic?: q.TreeNode, recursive: boolean = false) => { if (!topic) { return } props.sidebarActions.clearTopic(topic, recursive) }, [props.sidebarActions] ) if (!node) { return ( Select a topic to view details {/* About Button - always show even when no topic selected */} ) } const [value] = node && node.message && node.message.payload ? node.message.payload?.format(node.type) : [null, undefined] const hasValue = Boolean(value) return ( {/* Topic Section - Breadcrumb with actions */} {node.childTopicCount() === 0 && ( deleteTopic(node, false)} className={classes.iconButton}> )} {node.childTopicCount() > 0 && ( deleteTopic(node, true)} className={classes.iconButton}> )} {/* Value Section - Simplified layout */} {hasValue && ( {/* Metadata bar - Date on left, Retained/QoS on right */} {node.message?.retain && ( )} {/* Action toolbar */} Current Value {node.message?.retain && } {/* Value Display */} Loading...}> {/* Message History */} {/* Stats Section - Moved to end of value section */} Messages {node.messages} Subtopics {node.childTopicCount()} Total {node.leafMessageCount()} )} {/* AI Assistant - Always available when a node is selected */} {node && } {/* About Section - always visible at bottom */} ) } const styles = (theme: Theme) => ({ root: { display: 'flex', flexDirection: 'column' as const, gap: theme.spacing(3), [theme.breakpoints.down('sm')]: { gap: theme.spacing(2), }, }, emptyState: { display: 'flex', flexDirection: 'column' as const, alignItems: 'center', justifyContent: 'center', minHeight: '200px', padding: theme.spacing(3), gap: theme.spacing(3), }, aboutSection: { marginTop: theme.spacing(3), paddingTop: theme.spacing(2), borderTop: `1px solid ${theme.palette.divider}`, }, aboutSection: { marginTop: theme.spacing(3), paddingTop: theme.spacing(2), borderTop: `1px solid ${theme.palette.divider}`, }, // Topic section topicSection: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: theme.spacing(1), paddingBottom: theme.spacing(2), borderBottom: `1px solid ${theme.palette.divider}`, }, topicActions: { display: 'flex', gap: theme.spacing(0.5), alignItems: 'center', flexShrink: 0, }, iconButton: { padding: theme.spacing(0.5), }, // Stats section statsSection: { marginTop: theme.spacing(2), }, statsGrid: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: theme.spacing(1.5), [theme.breakpoints.down('sm')]: { gap: theme.spacing(1), }, }, statItem: { display: 'flex', flexDirection: 'column' as const, alignItems: 'center', padding: theme.spacing(1.5, 1), backgroundColor: theme.palette.action.hover, borderRadius: theme.shape.borderRadius, gap: theme.spacing(0.5), }, statLabel: { fontSize: '0.75rem', fontWeight: 500, textTransform: 'uppercase' as const, letterSpacing: '0.5px', }, statValue: { fontSize: '1.25rem', fontWeight: 600, lineHeight: 1, }, // Value section valueSection: { display: 'flex', flexDirection: 'column' as const, gap: theme.spacing(2), }, metadataBar: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: theme.spacing(1), flexWrap: 'wrap' as const, padding: theme.spacing(1), backgroundColor: theme.palette.action.hover, borderRadius: theme.shape.borderRadius, }, metadataLeft: { display: 'flex', gap: theme.spacing(1), alignItems: 'center', flexWrap: 'wrap' as const, }, metadataRight: { display: 'flex', alignItems: 'center', }, chip: { height: '24px', }, actionToolbar: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: theme.spacing(1), flexWrap: 'wrap' as const, }, valueTitle: { fontWeight: 600, color: theme.palette.text.primary, fontSize: '0.875rem', textTransform: 'uppercase' as const, letterSpacing: '0.5px', flexShrink: 0, }, actionButtons: { display: 'flex', alignItems: 'center', flex: 1, }, valueActions: { display: 'flex', gap: theme.spacing(0.5), alignItems: 'center', }, valueDisplay: { marginTop: theme.spacing(1), }, historySection: { marginTop: theme.spacing(1), }, }) const mapStateToProps = (state: AppState) => ({ compareMessage: state.sidebar.get('compareMessage'), }) const mapDispatchToProps = (dispatch: any) => ({ sidebarActions: bindActionCreators(sidebarActions, dispatch), globalActions: bindActionCreators(globalActions, dispatch), }) export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(DetailsTab))