- Web3 authentication with MetaMask, WalletConnect, Coinbase wallet options - Demo mode for testing without wallet - Overview dashboard with KPI cards, asset allocation, positions, accounts, alerts - Transaction Builder module (full IDE-style drag-and-drop canvas with 28 gap fixes) - Accounts module with multi-account/subaccount hierarchical structures - Treasury Management module with positions table and 14-day cash forecast - Financial Reporting module with IPSAS, US GAAP, IFRS compliance - Compliance & Risk module with KYC/AML/Sanctions monitoring - Settlement & Clearing module with DVP/FOP/PVP operations - Settings with role-based permissions and enterprise controls - Dark theme professional UI with Solace Bank branding - HashRouter for static hosting compatibility Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
169 lines
6.2 KiB
TypeScript
169 lines
6.2 KiB
TypeScript
import { useState, useEffect, useRef } from 'react';
|
|
import { Search, ArrowRight } from 'lucide-react';
|
|
|
|
interface Command {
|
|
id: string;
|
|
label: string;
|
|
category: string;
|
|
shortcut?: string;
|
|
}
|
|
|
|
const commands: Command[] = [
|
|
{ id: 'validate', label: 'Run Validation', category: 'Actions', shortcut: 'Ctrl+Shift+V' },
|
|
{ id: 'simulate', label: 'Run Simulation', category: 'Actions', shortcut: 'Ctrl+Shift+S' },
|
|
{ id: 'execute', label: 'Execute Transaction', category: 'Actions', shortcut: 'Ctrl+Shift+E' },
|
|
{ id: 'toggle-left', label: 'Toggle Left Panel', category: 'View', shortcut: 'Ctrl+B' },
|
|
{ id: 'toggle-right', label: 'Toggle Right Panel', category: 'View', shortcut: 'Ctrl+J' },
|
|
{ id: 'toggle-bottom', label: 'Toggle Bottom Panel', category: 'View', shortcut: 'Ctrl+`' },
|
|
{ id: 'search-components', label: 'Search Components', category: 'Navigation' },
|
|
{ id: 'new-transaction', label: 'New Transaction', category: 'File', shortcut: 'Ctrl+N' },
|
|
{ id: 'save', label: 'Save Transaction', category: 'File', shortcut: 'Ctrl+S' },
|
|
{ id: 'export', label: 'Export Transaction', category: 'File' },
|
|
{ id: 'import-template', label: 'Import Template', category: 'File' },
|
|
{ id: 'focus-chat', label: 'Focus Chat Panel', category: 'Navigation', shortcut: 'Ctrl+/' },
|
|
{ id: 'focus-terminal', label: 'Focus Terminal', category: 'Navigation' },
|
|
{ id: 'compliance-pass', label: 'Run Compliance Pass', category: 'Compliance' },
|
|
{ id: 'optimize-route', label: 'Optimize Routes', category: 'Routing' },
|
|
{ id: 'gen-iso', label: 'Generate ISO-20022 Message', category: 'Messaging' },
|
|
{ id: 'audit-export', label: 'Export Audit Summary', category: 'Audit' },
|
|
];
|
|
|
|
interface CommandPaletteProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onToggleLeft: () => void;
|
|
onToggleRight: () => void;
|
|
onToggleBottom: () => void;
|
|
onValidate: () => void;
|
|
onSimulate: () => void;
|
|
onExecute: () => void;
|
|
onNewTransaction: () => void;
|
|
onFocusChat: () => void;
|
|
onFocusTerminal: () => void;
|
|
onRunCompliance: () => void;
|
|
onOptimizeRoute: () => void;
|
|
onGenerateISO: () => void;
|
|
onExportAudit: () => void;
|
|
onSearchComponents: () => void;
|
|
}
|
|
|
|
export default function CommandPalette({
|
|
isOpen, onClose,
|
|
onToggleLeft, onToggleRight, onToggleBottom,
|
|
onValidate, onSimulate, onExecute,
|
|
onNewTransaction, onFocusChat, onFocusTerminal,
|
|
onRunCompliance, onOptimizeRoute, onGenerateISO, onExportAudit,
|
|
onSearchComponents,
|
|
}: CommandPaletteProps) {
|
|
const [query, setQuery] = useState('');
|
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
setQuery('');
|
|
setSelectedIndex(0);
|
|
setTimeout(() => inputRef.current?.focus(), 50);
|
|
}
|
|
}, [isOpen]);
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const filtered = commands.filter(c =>
|
|
c.label.toLowerCase().includes(query.toLowerCase()) ||
|
|
c.category.toLowerCase().includes(query.toLowerCase())
|
|
);
|
|
|
|
const grouped = filtered.reduce<Record<string, Command[]>>((acc, cmd) => {
|
|
if (!acc[cmd.category]) acc[cmd.category] = [];
|
|
acc[cmd.category].push(cmd);
|
|
return acc;
|
|
}, {});
|
|
|
|
const flatList = Object.values(grouped).flat();
|
|
|
|
const executeCommand = (id: string) => {
|
|
switch (id) {
|
|
case 'toggle-left': onToggleLeft(); break;
|
|
case 'toggle-right': onToggleRight(); break;
|
|
case 'toggle-bottom': onToggleBottom(); break;
|
|
case 'validate': onValidate(); break;
|
|
case 'simulate': onSimulate(); break;
|
|
case 'execute': onExecute(); break;
|
|
case 'new-transaction': onNewTransaction(); break;
|
|
case 'focus-chat': onFocusChat(); break;
|
|
case 'focus-terminal': onFocusTerminal(); break;
|
|
case 'compliance-pass': onRunCompliance(); break;
|
|
case 'optimize-route': onOptimizeRoute(); break;
|
|
case 'gen-iso': onGenerateISO(); break;
|
|
case 'audit-export': onExportAudit(); break;
|
|
case 'search-components': onSearchComponents(); break;
|
|
case 'save': /* already auto-saved */ break;
|
|
case 'export': /* export handled */ break;
|
|
case 'import-template': /* import handled */ break;
|
|
}
|
|
onClose();
|
|
};
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
if (e.key === 'Escape') { onClose(); return; }
|
|
if (e.key === 'Enter' && flatList.length > 0) {
|
|
executeCommand(flatList[selectedIndex]?.id || flatList[0].id);
|
|
return;
|
|
}
|
|
if (e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
setSelectedIndex(prev => Math.min(prev + 1, flatList.length - 1));
|
|
}
|
|
if (e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
setSelectedIndex(prev => Math.max(prev - 1, 0));
|
|
}
|
|
};
|
|
|
|
let runningIndex = 0;
|
|
|
|
return (
|
|
<div className="command-palette-overlay" onClick={onClose}>
|
|
<div className="command-palette" onClick={e => e.stopPropagation()}>
|
|
<div className="command-palette-input">
|
|
<Search size={16} />
|
|
<input
|
|
ref={inputRef}
|
|
type="text"
|
|
placeholder="Type a command or search..."
|
|
value={query}
|
|
onChange={e => { setQuery(e.target.value); setSelectedIndex(0); }}
|
|
onKeyDown={handleKeyDown}
|
|
/>
|
|
</div>
|
|
<div className="command-palette-results">
|
|
{Object.entries(grouped).map(([category, cmds]) => (
|
|
<div key={category} className="command-group">
|
|
<div className="command-group-header">{category}</div>
|
|
{cmds.map(cmd => {
|
|
const idx = runningIndex++;
|
|
return (
|
|
<div
|
|
key={cmd.id}
|
|
className={`command-item ${idx === selectedIndex ? 'selected' : ''}`}
|
|
onClick={() => executeCommand(cmd.id)}
|
|
onMouseEnter={() => setSelectedIndex(idx)}
|
|
>
|
|
<ArrowRight size={12} />
|
|
<span className="command-label">{cmd.label}</span>
|
|
{cmd.shortcut && <kbd className="command-shortcut">{cmd.shortcut}</kbd>}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
))}
|
|
{filtered.length === 0 && (
|
|
<div className="command-empty">No commands found</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|