/** * ToggleTableRow Component * Reusable component for displaying long HTML content in table rows with expand/collapse functionality */ import React, { useState, useRef, useEffect } from 'react'; import { ChevronDownIcon, HorizontaLDots } from '../../icons'; import HTMLContentRenderer from './HTMLContentRenderer'; import Badge from '../ui/badge/Badge'; interface ToggleTableRowProps { /** The row data */ row: any; /** The column key that contains the toggleable content */ contentKey: string; /** Custom label for the expanded content (e.g., "Content Outline", "Description") */ contentLabel?: string; /** Column span for the expanded row (should match number of columns in table) */ colSpan: number; /** Whether the row is expanded (controlled) */ isExpanded?: boolean; /** Whether the row is initially expanded (uncontrolled) */ defaultExpanded?: boolean; /** Callback when toggle state changes */ onToggle?: (expanded: boolean, rowId: string | number) => void; /** Custom className */ className?: string; } const ToggleTableRow: React.FC = ({ row, contentKey, contentLabel = 'Content', colSpan, isExpanded: controlledExpanded, defaultExpanded = false, onToggle, className = '', }) => { // Use controlled state if provided, otherwise use internal state const [internalExpanded, setInternalExpanded] = useState(defaultExpanded); const isExpanded = controlledExpanded !== undefined ? controlledExpanded : internalExpanded; const [contentHeight, setContentHeight] = useState('auto'); const contentRef = useRef(null); // Get content - handle fallback to description if primary contentKey is empty let content = row[contentKey]; let contentMetadata: Record | null = null; if (content && typeof content === 'object' && content !== null && 'content' in content) { contentMetadata = { ...content }; content = content.content; } if (!content || (typeof content === 'string' && content.trim().length === 0)) { // Try fallback to description if primary content is empty content = row.description || row.content_outline || null; } // Check if content exists - handle both strings and objects const hasContent = content && ( typeof content === 'string' ? content.trim().length > 0 : typeof content === 'object' && content !== null && Object.keys(content).length > 0 ); useEffect(() => { if (isExpanded && contentRef.current) { // Measure content height for smooth animation const height = contentRef.current.scrollHeight; setContentHeight(height); } else { setContentHeight(0); } }, [isExpanded, content]); const handleToggle = () => { if (!hasContent) return; const newExpanded = !isExpanded; // Update internal state if uncontrolled if (controlledExpanded === undefined) { setInternalExpanded(newExpanded); } // Notify parent if (onToggle) { onToggle(newExpanded, row.id ?? row.id); } }; if (!hasContent) { return null; } // Don't render anything if not expanded - no row HTML before toggle if (!isExpanded) { return null; } return (
{contentLabel}
{/* Show idea title if available (for Tasks page) */} {row.idea_title && (
Idea:
{row.idea_title}
)} {/* Metadata badges */} {/* Rendered content */}
); }; interface ToggleMetadataProps { row: any; contentMetadata: Record | null; } const ToggleMetadata: React.FC = ({ row, contentMetadata }) => { const primaryKeyword = row.content_primary_keyword || row.primary_keyword || contentMetadata?.primary_keyword || contentMetadata?.metadata?.primary_keyword || null; const secondaryKeywords = row.content_secondary_keywords || row.secondary_keywords || contentMetadata?.secondary_keywords || contentMetadata?.metadata?.secondary_keywords || []; const tags = row.content_tags || row.tags || contentMetadata?.tags || contentMetadata?.metadata?.tags || []; const categories = row.content_categories || row.categories || contentMetadata?.categories || contentMetadata?.metadata?.categories || []; // Extract meta_description, avoiding JSON strings let metaDescription: string | null = null; // Try direct fields first if (row.meta_description && typeof row.meta_description === 'string') { metaDescription = row.meta_description; } else if (row.content_meta_description && typeof row.content_meta_description === 'string') { metaDescription = row.content_meta_description; } else if (contentMetadata?.meta_description && typeof contentMetadata.meta_description === 'string') { metaDescription = contentMetadata.meta_description; } else if (contentMetadata?.metadata?.meta_description && typeof contentMetadata.metadata.meta_description === 'string') { metaDescription = contentMetadata.metadata.meta_description; } // If metaDescription looks like JSON, try to parse it if (metaDescription && metaDescription.trim().startsWith('{')) { try { const parsed = JSON.parse(metaDescription); // If parsed object has meta_description, use that if (parsed.meta_description && typeof parsed.meta_description === 'string') { metaDescription = parsed.meta_description; } else { // If it's a full JSON response, extract meta_description from it metaDescription = parsed.meta_description || null; } } catch { // Not valid JSON, keep as is } } const hasMetadata = primaryKeyword || (secondaryKeywords && secondaryKeywords.length > 0) || (tags && tags.length > 0) || (categories && categories.length > 0) || metaDescription; if (!hasMetadata) { return null; } const renderBadgeList = (items: any, color: 'info' | 'light' = 'light') => { if (!items) return null; const list = Array.isArray(items) ? items : [items]; if (list.length === 0) return null; return (
{list.map((item, index) => ( {item} ))}
); }; return (
{primaryKeyword && (
Primary Keyword: {primaryKeyword}
)} {(() => { const badges = renderBadgeList(secondaryKeywords); if (!badges) return null; return (
Secondary Keywords: {badges}
); })()} {(() => { const badges = renderBadgeList(tags); if (!badges) return null; return (
Tags: {badges}
); })()} {(() => { const badges = renderBadgeList(categories); if (!badges) return null; return (
Categories: {badges}
); })()} {metaDescription && (
Meta Description {metaDescription}
)}
); }; /** * Toggle Button Component - To be used in table cells */ interface ToggleButtonProps { /** Whether the row is expanded */ isExpanded: boolean; /** Click handler */ onClick: () => void; /** Whether content exists */ hasContent: boolean; /** Custom className */ className?: string; } export const ToggleButton: React.FC = ({ isExpanded, onClick, hasContent, className = '', }) => { if (!hasContent) { return ( ); } return ( ); }; export default ToggleTableRow;