121 lines
4.2 KiB
TypeScript
121 lines
4.2 KiB
TypeScript
import {
|
|
ArrowPathIcon,
|
|
CheckCircleIcon,
|
|
ExclamationTriangleIcon,
|
|
XMarkIcon,
|
|
} from '@heroicons/react/24/outline';
|
|
import { useEffect, useState } from 'react';
|
|
import { ExchangesTable } from './components/ExchangesTable';
|
|
import { useExchanges } from './hooks/useExchanges';
|
|
|
|
export function ExchangesPage() {
|
|
const { syncExchanges } = useExchanges();
|
|
const [syncing, setSyncing] = useState(false);
|
|
const [syncStatus, setSyncStatus] = useState<{
|
|
type: 'success' | 'error' | null;
|
|
message: string;
|
|
}>({ type: null, message: '' });
|
|
|
|
// Auto-dismiss success messages after 5 seconds
|
|
useEffect(() => {
|
|
if (syncStatus.type === 'success') {
|
|
const timer = setTimeout(() => {
|
|
setSyncStatus({ type: null, message: '' });
|
|
}, 5000);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [syncStatus.type]);
|
|
|
|
const handleSync = async () => {
|
|
setSyncing(true);
|
|
setSyncStatus({ type: null, message: '' });
|
|
|
|
try {
|
|
const result = await syncExchanges();
|
|
if (result) {
|
|
setSyncStatus({
|
|
type: 'success',
|
|
message: `Exchange sync completed successfully! Job ID: ${result.jobId || 'Unknown'}`,
|
|
});
|
|
} else {
|
|
setSyncStatus({
|
|
type: 'error',
|
|
message: 'Sync failed - no result returned from server',
|
|
});
|
|
}
|
|
} catch (error) {
|
|
setSyncStatus({
|
|
type: 'error',
|
|
message: error instanceof Error ? error.message : 'Sync failed with unknown error',
|
|
});
|
|
} finally {
|
|
setSyncing(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-full space-y-6">
|
|
<div className="flex items-center justify-between flex-shrink-0">
|
|
<div>
|
|
<h1 className="text-lg font-bold text-text-primary mb-2">Exchange Management</h1>
|
|
<p className="text-text-secondary text-sm">
|
|
Configure and manage master exchanges with their data sources and providers.
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={handleSync}
|
|
disabled={syncing}
|
|
className="inline-flex items-center gap-2 px-4 py-2 bg-primary-500 text-white rounded-lg hover:bg-primary-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
>
|
|
<ArrowPathIcon className={`h-4 w-4 ${syncing ? 'animate-spin' : ''}`} />
|
|
{syncing ? 'Syncing...' : 'Sync Exchanges'}
|
|
</button>
|
|
</div>
|
|
|
|
{syncing && (
|
|
<div className="bg-primary-50 border border-primary-200 rounded-lg p-4 flex-shrink-0">
|
|
<div className="flex items-center gap-2">
|
|
<ArrowPathIcon className="h-4 w-4 text-primary-500 animate-spin" />
|
|
<span className="text-primary-700 text-sm">
|
|
Syncing exchanges from Interactive Brokers data...
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{syncStatus.type && (
|
|
<div className="fixed bottom-4 right-4 z-50 max-w-sm">
|
|
<div className="bg-surface-secondary rounded-lg border border-border p-4 shadow-xl transform transition-all duration-300 ease-out">
|
|
<div className="flex items-start gap-3">
|
|
{syncStatus.type === 'success' ? (
|
|
<CheckCircleIcon className="h-5 w-5 text-success flex-shrink-0 mt-0.5" />
|
|
) : (
|
|
<ExclamationTriangleIcon className="h-5 w-5 text-danger flex-shrink-0 mt-0.5" />
|
|
)}
|
|
<div className="flex-1 min-w-0">
|
|
<p
|
|
className={`text-sm font-medium ${
|
|
syncStatus.type === 'success' ? 'text-success' : 'text-danger'
|
|
}`}
|
|
>
|
|
{syncStatus.type === 'success' ? 'Sync Completed' : 'Sync Failed'}
|
|
</p>
|
|
<p className="text-xs mt-1 text-text-secondary">{syncStatus.message}</p>
|
|
</div>
|
|
<button
|
|
onClick={() => setSyncStatus({ type: null, message: '' })}
|
|
className="flex-shrink-0 p-1 rounded-full text-text-muted hover:text-text-primary hover:bg-surface transition-colors"
|
|
>
|
|
<XMarkIcon className="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex-1 min-h-0">
|
|
<ExchangesTable />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|