Initial commit: Gold Trading Simulator with AI-powered analysis
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
import { useState, ReactNode } from 'react';
|
||||
import { X, Maximize2, Minimize2, Pin, PinOff } from 'lucide-react';
|
||||
import ComponentSettings from './ComponentSettings';
|
||||
import type { TabConfig, LayoutMode, TabCustomization } from '@/types';
|
||||
|
||||
interface TabPanel {
|
||||
config: TabConfig;
|
||||
content: ReactNode;
|
||||
}
|
||||
|
||||
interface TabbedContainerProps {
|
||||
panels: TabPanel[];
|
||||
mode: LayoutMode;
|
||||
onTabClose?: (tabId: string) => void;
|
||||
onTabPin?: (tabId: string) => void;
|
||||
onCustomizationUpdate?: (tabId: string, customization: TabCustomization) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function TabbedContainer({
|
||||
panels,
|
||||
mode,
|
||||
onTabClose,
|
||||
onTabPin,
|
||||
onCustomizationUpdate,
|
||||
className = '',
|
||||
}: TabbedContainerProps) {
|
||||
const [activeTabId, setActiveTabId] = useState<string>(
|
||||
panels.find((p) => p.config.visible)?.config.id || panels[0]?.config.id
|
||||
);
|
||||
const [expandedTab, setExpandedTab] = useState<string | null>(null);
|
||||
|
||||
const visiblePanels = panels
|
||||
.filter((p) => p.config.visible)
|
||||
.sort((a, b) => a.config.order - b.config.order);
|
||||
|
||||
const activePanel = visiblePanels.find((p) => p.config.id === activeTabId);
|
||||
|
||||
const handleExpand = (tabId: string) => {
|
||||
setExpandedTab(expandedTab === tabId ? null : tabId);
|
||||
};
|
||||
|
||||
// Tabs mode - single panel with tab switcher
|
||||
if (mode === 'tabs') {
|
||||
return (
|
||||
<div className={`flex flex-col h-full ${className}`}>
|
||||
{/* Tab switcher */}
|
||||
<div className="flex items-center gap-1 border-b border-gray-700 bg-dark-card px-2 overflow-x-auto">
|
||||
{visiblePanels.map((panel) => (
|
||||
<button
|
||||
key={panel.config.id}
|
||||
onClick={() => setActiveTabId(panel.config.id)}
|
||||
className={`flex items-center gap-2 px-4 py-3 border-b-2 transition-colors whitespace-nowrap ${
|
||||
activeTabId === panel.config.id
|
||||
? 'border-gold-500 text-gold-500 bg-dark-hover'
|
||||
: 'border-transparent text-gray-400 hover:text-white hover:bg-dark-hover'
|
||||
}`}
|
||||
>
|
||||
{panel.config.pinned && <Pin className="w-3 h-3" />}
|
||||
<span>{panel.config.label}</span>
|
||||
{onTabClose && !panel.config.pinned && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onTabClose(panel.config.id);
|
||||
}}
|
||||
className="p-0.5 hover:bg-red-500/20 rounded"
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Active panel content */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
{activePanel && (
|
||||
<div className="h-full">{activePanel.content}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Split mode - two main sections
|
||||
if (mode === 'split') {
|
||||
const leftPanels = visiblePanels.filter((p) => p.config.position !== 'right');
|
||||
const rightPanels = visiblePanels.filter((p) => p.config.position === 'right');
|
||||
|
||||
return (
|
||||
<div className={`grid grid-cols-2 gap-6 h-full ${className}`}>
|
||||
{/* Left section */}
|
||||
<div className="space-y-6 overflow-y-auto">
|
||||
{leftPanels.map((panel) => (
|
||||
<PanelCard
|
||||
key={panel.config.id}
|
||||
panel={panel}
|
||||
onExpand={handleExpand}
|
||||
onPin={onTabPin}
|
||||
onClose={onTabClose}
|
||||
onCustomizationUpdate={onCustomizationUpdate}
|
||||
isExpanded={expandedTab === panel.config.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Right section */}
|
||||
<div className="space-y-6 overflow-y-auto">
|
||||
{rightPanels.map((panel) => (
|
||||
<PanelCard
|
||||
key={panel.config.id}
|
||||
panel={panel}
|
||||
onExpand={handleExpand}
|
||||
onPin={onTabPin}
|
||||
onClose={onTabClose}
|
||||
onCustomizationUpdate={onCustomizationUpdate}
|
||||
isExpanded={expandedTab === panel.config.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Grid mode - responsive grid layout
|
||||
const getSizeClass = (size: TabConfig['size']) => {
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return 'col-span-1';
|
||||
case 'medium':
|
||||
return 'col-span-1 lg:col-span-2';
|
||||
case 'large':
|
||||
return 'col-span-1 lg:col-span-3';
|
||||
case 'full':
|
||||
return 'col-span-full';
|
||||
default:
|
||||
return 'col-span-1';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`grid grid-cols-1 lg:grid-cols-6 gap-6 ${className}`}>
|
||||
{visiblePanels.map((panel) => (
|
||||
<div
|
||||
key={panel.config.id}
|
||||
className={`${getSizeClass(panel.config.size)} ${
|
||||
expandedTab === panel.config.id ? 'fixed inset-4 z-40' : ''
|
||||
}`}
|
||||
>
|
||||
<PanelCard
|
||||
panel={panel}
|
||||
onExpand={handleExpand}
|
||||
onPin={onTabPin}
|
||||
onClose={onTabClose}
|
||||
onCustomizationUpdate={onCustomizationUpdate}
|
||||
isExpanded={expandedTab === panel.config.id}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Individual panel card component
|
||||
interface PanelCardProps {
|
||||
panel: TabPanel;
|
||||
onExpand: (tabId: string) => void;
|
||||
onPin?: (tabId: string) => void;
|
||||
onClose?: (tabId: string) => void;
|
||||
onCustomizationUpdate?: (tabId: string, customization: TabCustomization) => void;
|
||||
isExpanded: boolean;
|
||||
}
|
||||
|
||||
function PanelCard({ panel, onExpand, onPin, onClose, onCustomizationUpdate, isExpanded }: PanelCardProps) {
|
||||
const getAvailableSettings = (tabId: string) => {
|
||||
// Define available settings per tab
|
||||
switch (tabId) {
|
||||
case 'news':
|
||||
return {
|
||||
autoRefresh: true,
|
||||
refreshRate: true,
|
||||
filters: {
|
||||
sentiment: ['ALL', 'POSITIVE', 'NEGATIVE', 'NEUTRAL'],
|
||||
impact: ['ALL', 'HIGH', 'MEDIUM', 'LOW'],
|
||||
},
|
||||
theme: true,
|
||||
};
|
||||
case 'alerts':
|
||||
return {
|
||||
autoRefresh: true,
|
||||
refreshRate: true,
|
||||
filters: {
|
||||
severity: ['ALL', 'CRITICAL', 'HIGH', 'MEDIUM', 'LOW'],
|
||||
type: ['ALL', 'PRICE_SPIKE', 'NEWS_BREAKING', 'SUPPORT_BREACH'],
|
||||
},
|
||||
};
|
||||
case 'chart':
|
||||
return {
|
||||
displayMode: ['candlestick', 'line', 'area'],
|
||||
theme: true,
|
||||
};
|
||||
case 'analytics':
|
||||
return {
|
||||
displayMode: ['detailed', 'compact', 'charts-only'],
|
||||
theme: true,
|
||||
};
|
||||
case 'daily-checklist':
|
||||
return {
|
||||
autoRefresh: false,
|
||||
displayMode: ['all-phases', 'current-phase-only'],
|
||||
theme: true,
|
||||
};
|
||||
case 'trading-journal':
|
||||
return {
|
||||
displayMode: ['detailed', 'compact', 'list'],
|
||||
filters: {
|
||||
emotion: ['all', 'confident', 'neutral', 'anxious'],
|
||||
result: ['all', 'winners', 'losers'],
|
||||
},
|
||||
};
|
||||
case 'market-summary':
|
||||
return {
|
||||
autoRefresh: true,
|
||||
refreshRate: true,
|
||||
displayMode: ['overview', 'levels', 'events', 'ai'],
|
||||
};
|
||||
default:
|
||||
return { theme: true };
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={`card flex flex-col group ${
|
||||
isExpanded ? 'h-full' : 'h-auto'
|
||||
}`}
|
||||
>
|
||||
{/* Panel header */}
|
||||
<div className="flex items-center justify-between mb-4 pb-3 border-b border-gray-700">
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||
{panel.config.pinned && <Pin className="w-4 h-4 text-gold-500" />}
|
||||
{panel.config.label}
|
||||
</h3>
|
||||
<div className="flex items-center gap-1">
|
||||
{onCustomizationUpdate && (
|
||||
<ComponentSettings
|
||||
customization={panel.config.customization}
|
||||
onUpdate={(customization) =>
|
||||
onCustomizationUpdate(panel.config.id, customization)
|
||||
}
|
||||
availableSettings={getAvailableSettings(panel.config.id)}
|
||||
/>
|
||||
)}
|
||||
{onPin && (
|
||||
<button
|
||||
onClick={() => onPin(panel.config.id)}
|
||||
className="p-1.5 hover:bg-dark-hover rounded transition-colors"
|
||||
title={panel.config.pinned ? 'Unpin' : 'Pin'}
|
||||
>
|
||||
{panel.config.pinned ? (
|
||||
<PinOff className="w-4 h-4" />
|
||||
) : (
|
||||
<Pin className="w-4 h-4" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => onExpand(panel.config.id)}
|
||||
className="p-1.5 hover:bg-dark-hover rounded transition-colors"
|
||||
title={isExpanded ? 'Minimize' : 'Expand'}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<Minimize2 className="w-4 h-4" />
|
||||
) : (
|
||||
<Maximize2 className="w-4 h-4" />
|
||||
)}
|
||||
</button>
|
||||
{onClose && !panel.config.pinned && (
|
||||
<button
|
||||
onClick={() => onClose(panel.config.id)}
|
||||
className="p-1.5 hover:bg-red-500/20 rounded transition-colors"
|
||||
title="Close"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Panel content */}
|
||||
<div className={`flex-1 ${isExpanded ? 'overflow-y-auto' : ''}`}>
|
||||
{panel.content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user