mirror of
https://github.com/coleam00/Archon.git
synced 2025-12-23 18:29:18 -05:00
233 lines
8.6 KiB
TypeScript
233 lines
8.6 KiB
TypeScript
import React from 'react';
|
|
import { FileText, Hash, List, Box, Type, ToggleLeft } from 'lucide-react';
|
|
import { SectionProps } from '../types/prp.types';
|
|
import { formatKey, formatValue } from '../utils/formatters';
|
|
import { hasComplexNesting } from '../utils/normalizer';
|
|
import { CollapsibleSectionWrapper } from '../components/CollapsibleSectionWrapper';
|
|
import { SimpleMarkdown } from '../components/SimpleMarkdown';
|
|
|
|
/**
|
|
* Generic fallback section component that intelligently renders any data structure
|
|
* This component provides comprehensive rendering for any data type with proper formatting
|
|
*/
|
|
export const GenericSection: React.FC<SectionProps> = ({
|
|
title,
|
|
data,
|
|
icon = <FileText className="w-5 h-5" />,
|
|
accentColor = 'gray',
|
|
defaultOpen = true,
|
|
isDarkMode = false,
|
|
isCollapsible = true,
|
|
isOpen,
|
|
onToggle
|
|
}) => {
|
|
// Auto-detect appropriate icon based on data type
|
|
const getAutoIcon = () => {
|
|
if (typeof data === 'string') return <Type className="w-5 h-5" />;
|
|
if (typeof data === 'number') return <Hash className="w-5 h-5" />;
|
|
if (typeof data === 'boolean') return <ToggleLeft className="w-5 h-5" />;
|
|
if (Array.isArray(data)) return <List className="w-5 h-5" />;
|
|
if (typeof data === 'object' && data !== null) return <Box className="w-5 h-5" />;
|
|
return icon;
|
|
};
|
|
const renderValue = (value: any, depth: number = 0): React.ReactNode => {
|
|
const indent = depth * 16;
|
|
const maxDepth = 5; // Prevent infinite recursion
|
|
|
|
// Handle null/undefined
|
|
if (value === null || value === undefined) {
|
|
return <span className="text-gray-400 italic">Empty</span>;
|
|
}
|
|
|
|
// Handle primitives
|
|
if (typeof value === 'string') {
|
|
// Check if the string looks like markdown content
|
|
const hasMarkdownIndicators = /^#{1,6}\s+.+$|^[-*+]\s+.+$|^\d+\.\s+.+$|```|^\>.+$|\*\*.+\*\*|\*.+\*|`[^`]+`/m.test(value);
|
|
|
|
if (hasMarkdownIndicators && value.length > 20) {
|
|
// Render as markdown for content with markdown syntax
|
|
// Remove any leading headers since the section already has a title
|
|
const contentWithoutLeadingHeaders = value.replace(/^#{1,6}\s+.+$/m, '').trim();
|
|
const finalContent = contentWithoutLeadingHeaders || value;
|
|
|
|
return <SimpleMarkdown content={finalContent} className="text-gray-700 dark:text-gray-300" />;
|
|
}
|
|
|
|
// For shorter strings or non-markdown, use simple formatting
|
|
return <span className="text-gray-700 dark:text-gray-300">{formatValue(value)}</span>;
|
|
}
|
|
|
|
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
return <span className="text-gray-700 dark:text-gray-300 font-mono">{formatValue(value)}</span>;
|
|
}
|
|
|
|
// Prevent deep recursion
|
|
if (depth >= maxDepth) {
|
|
return (
|
|
<span className="text-gray-500 italic text-sm">
|
|
[Complex nested structure - too deep to display]
|
|
</span>
|
|
);
|
|
}
|
|
|
|
// Handle arrays
|
|
if (Array.isArray(value)) {
|
|
if (value.length === 0) {
|
|
return <span className="text-gray-400 italic">No items</span>;
|
|
}
|
|
|
|
// Check if it's an array of primitives
|
|
const isSimpleArray = value.every(item =>
|
|
typeof item === 'string' ||
|
|
typeof item === 'number' ||
|
|
typeof item === 'boolean' ||
|
|
item === null ||
|
|
item === undefined
|
|
);
|
|
|
|
if (isSimpleArray) {
|
|
// For very long arrays, show first 10 and count
|
|
const displayItems = value.length > 10 ? value.slice(0, 10) : value;
|
|
const hasMore = value.length > 10;
|
|
|
|
return (
|
|
<div>
|
|
<ul className="space-y-1 mt-2">
|
|
{displayItems.map((item, index) => (
|
|
<li key={index} className="flex items-start gap-2" style={{ marginLeft: indent }}>
|
|
<span className="text-gray-400 mt-0.5">•</span>
|
|
<span className="text-gray-700 dark:text-gray-300">{formatValue(item)}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
{hasMore && (
|
|
<p className="text-sm text-gray-500 italic mt-2" style={{ marginLeft: indent + 16 }}>
|
|
... and {value.length - 10} more items
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Array of objects
|
|
const displayItems = value.length > 5 ? value.slice(0, 5) : value;
|
|
const hasMore = value.length > 5;
|
|
|
|
return (
|
|
<div className="space-y-3 mt-2">
|
|
{displayItems.map((item, index) => (
|
|
<div key={index} className="relative" style={{ marginLeft: indent }}>
|
|
<div className="absolute left-0 top-0 bottom-0 w-0.5 bg-gradient-to-b from-gray-300 to-transparent dark:from-gray-600"></div>
|
|
<div className="pl-4">
|
|
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
|
|
[{index}]
|
|
</div>
|
|
{renderValue(item, depth + 1)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
{hasMore && (
|
|
<p className="text-sm text-gray-500 italic" style={{ marginLeft: indent + 16 }}>
|
|
... and {value.length - 5} more items
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Handle objects
|
|
if (typeof value === 'object' && value !== null) {
|
|
// Simplified object rendering to debug black screen
|
|
return (
|
|
<div className="mt-2 text-gray-700 dark:text-gray-300">
|
|
<pre className="text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded whitespace-pre-wrap">
|
|
{JSON.stringify(value, null, 2)}
|
|
</pre>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Fallback for any other type (functions, symbols, etc.)
|
|
return (
|
|
<span className="text-gray-500 italic text-sm">
|
|
[{typeof value}]
|
|
</span>
|
|
);
|
|
};
|
|
|
|
const getBackgroundColor = () => {
|
|
const colorMap = {
|
|
blue: 'bg-blue-50/50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800',
|
|
purple: 'bg-purple-50/50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800',
|
|
green: 'bg-green-50/50 dark:bg-green-900/20 border-green-200 dark:border-green-800',
|
|
orange: 'bg-orange-50/50 dark:bg-orange-900/20 border-orange-200 dark:border-orange-800',
|
|
pink: 'bg-pink-50/50 dark:bg-pink-900/20 border-pink-200 dark:border-pink-800',
|
|
cyan: 'bg-cyan-50/50 dark:bg-cyan-900/20 border-cyan-200 dark:border-cyan-800',
|
|
gray: 'bg-gray-50/50 dark:bg-gray-900/20 border-gray-200 dark:border-gray-800',
|
|
};
|
|
return colorMap[accentColor as keyof typeof colorMap] || colorMap.gray;
|
|
};
|
|
|
|
const finalIcon = icon === <FileText className="w-5 h-5" /> ? getAutoIcon() : icon;
|
|
|
|
// Enhanced styling based on data complexity
|
|
const isComplexData = hasComplexNesting(data);
|
|
const headerClass = isComplexData
|
|
? `p-6 rounded-lg border-2 shadow-sm ${getBackgroundColor()}`
|
|
: `p-4 rounded-lg border ${getBackgroundColor()}`;
|
|
|
|
const header = (
|
|
<div className={headerClass}>
|
|
<h3 className="font-semibold text-gray-800 dark:text-white flex items-center gap-2">
|
|
<div className="p-1.5 rounded bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400">
|
|
{finalIcon}
|
|
</div>
|
|
<span className="flex-1">{title}</span>
|
|
</h3>
|
|
</div>
|
|
);
|
|
|
|
const contentClass = isComplexData
|
|
? `px-6 pb-6 -mt-1 rounded-b-lg border-2 border-t-0 shadow-sm ${getBackgroundColor()}`
|
|
: `px-4 pb-4 -mt-1 rounded-b-lg border border-t-0 ${getBackgroundColor()}`;
|
|
|
|
const content = (
|
|
<div className={contentClass}>
|
|
<div className="overflow-x-auto">
|
|
{/* Add a subtle background for complex data */}
|
|
{isComplexData ? (
|
|
<div className="bg-gray-50 dark:bg-gray-900/50 rounded p-3 -mx-2">
|
|
{renderValue(data)}
|
|
</div>
|
|
) : (
|
|
renderValue(data)
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
try {
|
|
return (
|
|
<CollapsibleSectionWrapper
|
|
header={header}
|
|
isCollapsible={isCollapsible}
|
|
defaultOpen={defaultOpen}
|
|
isOpen={isOpen}
|
|
onToggle={onToggle}
|
|
>
|
|
{content}
|
|
</CollapsibleSectionWrapper>
|
|
);
|
|
} catch (error) {
|
|
console.error('Error rendering GenericSection:', error, { title, data });
|
|
return (
|
|
<div className="p-4 border border-red-300 rounded bg-red-50 dark:bg-red-900">
|
|
<h3 className="text-red-800 dark:text-red-200 font-semibold">{title}</h3>
|
|
<p className="text-red-600 dark:text-red-300 text-sm mt-2">Error rendering section content</p>
|
|
<pre className="text-xs mt-2 bg-gray-100 dark:bg-gray-800 p-2 rounded overflow-auto">
|
|
{JSON.stringify(data, null, 2)}
|
|
</pre>
|
|
</div>
|
|
);
|
|
}
|
|
}; |