160 lines
5.2 KiB
TypeScript
160 lines
5.2 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { Play, Copy, Trash2, Download, Clock } from 'lucide-react';
|
|
import { getGlobalReplaySystem } from '@/lib/infrastructure/ErrorReplay';
|
|
|
|
interface ReplayEntry {
|
|
id: string;
|
|
timestamp: string;
|
|
error: string;
|
|
type: string;
|
|
}
|
|
|
|
export function ReplaySection() {
|
|
const [replays, setReplays] = useState<ReplayEntry[]>([]);
|
|
const [selectedReplay, setSelectedReplay] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
loadReplays();
|
|
}, []);
|
|
|
|
const loadReplays = () => {
|
|
const system = getGlobalReplaySystem();
|
|
const index = system.getReplayIndex();
|
|
setReplays(index);
|
|
};
|
|
|
|
const handleReplay = async (replayId: string) => {
|
|
setLoading(true);
|
|
try {
|
|
const system = getGlobalReplaySystem();
|
|
await system.replay(replayId);
|
|
} catch (error) {
|
|
console.error('Replay failed:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleExport = (replayId: string) => {
|
|
const system = getGlobalReplaySystem();
|
|
const data = system.exportReplay(replayId, 'json');
|
|
|
|
const blob = new Blob([data], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `replay_${replayId}.json`;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
const handleCopy = async (replayId: string) => {
|
|
const system = getGlobalReplaySystem();
|
|
const data = system.exportReplay(replayId, 'json');
|
|
|
|
try {
|
|
await navigator.clipboard.writeText(data);
|
|
console.log('Replay data copied to clipboard');
|
|
} catch (err) {
|
|
console.error('Failed to copy:', err);
|
|
}
|
|
};
|
|
|
|
const handleDelete = (replayId: string) => {
|
|
if (confirm('Delete this replay?')) {
|
|
const system = getGlobalReplaySystem();
|
|
system.deleteReplay(replayId);
|
|
loadReplays();
|
|
}
|
|
};
|
|
|
|
const handleClearAll = () => {
|
|
if (confirm('Clear all replays?')) {
|
|
const system = getGlobalReplaySystem();
|
|
system.clearAll();
|
|
loadReplays();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs font-semibold text-gray-400">Error Replay</span>
|
|
<div className="flex gap-1">
|
|
<button
|
|
onClick={loadReplays}
|
|
className="p-1 hover:bg-charcoal-outline rounded"
|
|
title="Refresh"
|
|
>
|
|
<Clock className="w-3 h-3 text-gray-400" />
|
|
</button>
|
|
<button
|
|
onClick={handleClearAll}
|
|
className="p-1 hover:bg-charcoal-outline rounded"
|
|
title="Clear All"
|
|
>
|
|
<Trash2 className="w-3 h-3 text-red-400" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{replays.length === 0 ? (
|
|
<div className="text-xs text-gray-500 text-center py-2">
|
|
No replays available
|
|
</div>
|
|
) : (
|
|
<div className="space-y-1 max-h-48 overflow-auto">
|
|
{replays.map((replay) => (
|
|
<div
|
|
key={replay.id}
|
|
className="bg-deep-graphite border border-charcoal-outline rounded p-2 text-xs"
|
|
>
|
|
<div className="flex items-start justify-between gap-2 mb-1">
|
|
<div className="flex-1 min-w-0">
|
|
<div className="font-mono text-red-400 font-bold truncate">
|
|
{replay.type}
|
|
</div>
|
|
<div className="text-gray-300 truncate">{replay.error}</div>
|
|
<div className="text-gray-500 text-[10px]">
|
|
{new Date(replay.timestamp).toLocaleTimeString()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-1 mt-1">
|
|
<button
|
|
onClick={() => handleReplay(replay.id)}
|
|
disabled={loading}
|
|
className="flex items-center gap-1 px-2 py-1 bg-green-600 hover:bg-green-700 text-white rounded"
|
|
>
|
|
<Play className="w-3 h-3" />
|
|
Replay
|
|
</button>
|
|
<button
|
|
onClick={() => handleExport(replay.id)}
|
|
className="flex items-center gap-1 px-2 py-1 bg-iron-gray hover:bg-charcoal-outline text-gray-300 rounded border border-charcoal-outline"
|
|
>
|
|
<Download className="w-3 h-3" />
|
|
Export
|
|
</button>
|
|
<button
|
|
onClick={() => handleCopy(replay.id)}
|
|
className="flex items-center gap-1 px-2 py-1 bg-iron-gray hover:bg-charcoal-outline text-gray-300 rounded border border-charcoal-outline"
|
|
>
|
|
<Copy className="w-3 h-3" />
|
|
Copy
|
|
</button>
|
|
<button
|
|
onClick={() => handleDelete(replay.id)}
|
|
className="flex items-center gap-1 px-2 py-1 bg-iron-gray hover:bg-charcoal-outline text-gray-300 rounded border border-charcoal-outline"
|
|
>
|
|
<Trash2 className="w-3 h-3" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
} |