import React, { useEffect, useMemo, useState } from 'react'; import DocumentSummarySection from './DocumentSummarySection'; import { buildDocumentMetadataItems, extractDocumentMetadataPayload } from './documentMetadata'; const DocumentInfoPanel = ({ document, summaryProps = {}, metadataItems: metadataItemsProp, metadataPayload: metadataPayloadProp, metadataTabLabel = 'Metadata', detailsTabLabel = 'Details', contentConfig: contentConfigProp = null, activeTab: controlledActiveTab, onTabChange, defaultTabId = 'details', resetKey = null, classNamePrefix = 'document-info', hideTabNavWhenSingle = true, }) => { const base = classNamePrefix; const metadataItems = useMemo(() => { if (Array.isArray(metadataItemsProp) && metadataItemsProp.length) { return metadataItemsProp; } return buildDocumentMetadataItems(document); }, [metadataItemsProp, document]); const metadataPayload = useMemo(() => { if (metadataPayloadProp !== undefined) { return metadataPayloadProp; } return extractDocumentMetadataPayload(document); }, [metadataPayloadProp, document]); const contentConfig = contentConfigProp || null; const contentEnabled = Boolean(contentConfig && (contentConfig.enabled ?? true)); const showContentTab = Boolean(contentConfig && ((contentConfig.forceDisplay ?? contentEnabled))); const [contentState, setContentState] = useState(() => { if (!contentConfig) { return null; } if (!contentEnabled || typeof contentConfig.loadContent !== 'function') { return { status: contentEnabled ? 'idle' : 'unavailable', data: null, error: null }; } return { status: 'idle', data: null, error: null }; }); useEffect(() => { if (!contentConfig || !showContentTab) { setContentState(null); return undefined; } if (!contentEnabled || typeof contentConfig.loadContent !== 'function') { setContentState({ status: contentEnabled ? 'idle' : 'unavailable', data: null, error: null }); return undefined; } let cancelled = false; const controller = new AbortController(); setContentState({ status: 'loading', data: null, error: null }); Promise.resolve(contentConfig.loadContent({ signal: controller.signal })) .then((result) => { if (cancelled) { return; } if (result && result.length) { setContentState({ status: 'loaded', data: result, error: null }); } else { setContentState({ status: 'empty', data: '', error: null }); } }) .catch((error) => { if (cancelled || error?.name === 'AbortError') { return; } setContentState({ status: 'error', data: null, error, }); }); return () => { cancelled = true; controller.abort(); contentConfig.onCancel?.(); }; }, [contentConfig, contentEnabled, showContentTab, document?.id, resetKey]); const visibleTabs = useMemo(() => { const tabsList = []; tabsList.push({ id: 'details', label: detailsTabLabel, render: () => (
{metadataItems.length ? (
{metadataItems.map(({ label, value }) => (
{label}
{value || '—'}
))}
) : (

No details available.

)}
), }); if (showContentTab && contentConfig) { tabsList.push({ id: contentConfig.id || 'content', label: contentConfig.label || 'Content', render: () => { const messageClass = `${base}__message`; const errorClass = `${base}__message ${base}__message--error`; const objectClass = `${base}__object ${base}__object--ocr-text`; if (!contentEnabled || !contentConfig.loadContent) { return (
{contentConfig.unavailableMessage || 'Content not available.'}
); } if (!contentState) { return (
{contentConfig.emptyMessage || 'No content available.'}
); } switch (contentState.status) { case 'loading': return (
{contentConfig.loadingMessage || 'Loading content…'}
); case 'error': { const errorMessage = contentConfig.errorMessage || (contentState.error instanceof Error ? contentState.error.message : null) || 'Failed to load content.'; return
{errorMessage}
; } case 'empty': return (
{contentConfig.emptyMessage || 'No content available.'}
); case 'loaded': return (
{contentState.data}
); case 'unavailable': return (
{contentConfig.unavailableMessage || 'Content not available.'}
); default: return (
{contentConfig.emptyMessage || 'No content available.'}
); } }, }); } if (metadataPayload) { tabsList.push({ id: 'metadata', label: metadataTabLabel, render: () => (
              {JSON.stringify(metadataPayload, null, 2)}
            
), }); } return tabsList; }, [ base, detailsTabLabel, metadataItems, contentConfig, contentEnabled, contentState, metadataPayload, metadataTabLabel, showContentTab, ]); const fallbackTabId = useMemo(() => { if (!visibleTabs.length) { return null; } if (defaultTabId && visibleTabs.some((tab) => tab.id === defaultTabId)) { return defaultTabId; } return visibleTabs[0].id; }, [visibleTabs, defaultTabId]); const renderTabContent = (tab, context = {}) => { if (!tab) { return null; } if (typeof tab.render === 'function') { return tab.render(context); } if (tab.component) { const TabComponent = tab.component; return ; } return React.isValidElement(tab.render) ? tab.render : null; }; const isControlled = controlledActiveTab !== undefined && controlledActiveTab !== null; const [uncontrolledTab, setUncontrolledTab] = useState( isControlled ? controlledActiveTab : fallbackTabId, ); useEffect(() => { if (!isControlled) { setUncontrolledTab(fallbackTabId); } }, [fallbackTabId, resetKey, isControlled]); useEffect(() => { if (isControlled && controlledActiveTab && !visibleTabs.some((tab) => tab.id === controlledActiveTab)) { const nextTab = fallbackTabId; if (nextTab && nextTab !== controlledActiveTab) { onTabChange?.(nextTab); } } }, [isControlled, controlledActiveTab, visibleTabs, fallbackTabId, onTabChange]); const activeTabId = isControlled ? controlledActiveTab : uncontrolledTab; const handleTabSelect = (tabId) => { if (!visibleTabs.some((tab) => tab.id === tabId)) { return; } if (!isControlled) { setUncontrolledTab(tabId); } if (tabId !== activeTabId) { onTabChange?.(tabId); } }; const singleTab = visibleTabs.length === 1 ? visibleTabs[0] : null; const shouldHideNav = hideTabNavWhenSingle && singleTab; return ( <> {shouldHideNav ? (
{renderTabContent(singleTab, { document })}
) : (
{visibleTabs.map((tab) => ( ))}
{visibleTabs.map((tab) => ( tab.id === activeTabId ? (
{renderTabContent(tab, { document })}
) : null ))}
)} ); }; export default DocumentInfoPanel;