ge-tool/components/HistoryItem.tsx
2025-12-10 13:41:43 +07:00

147 lines
7.1 KiB
TypeScript
Executable File

import React, { useState, memo } from 'react';
import type { Submission, GeIdResult } from '../types';
import GeIdResultItem from './GeIdResultItem';
import TrashIcon from './TrashIcon';
import RetryIcon from './RetryIcon';
import ConfirmModal from './ConfirmModal';
interface HistoryItemProps {
submission: Submission;
onErrorClick: (details: string) => void;
onDelete: (id: string) => void;
onRetry: (submission: Submission, errorGeIds: string[], errorUsernames: string[]) => void;
onPaste: (username: string, geIdAndLang: string) => void;
hideNonErrors: boolean;
}
const HistoryItem: React.FC<HistoryItemProps> = ({ submission, onErrorClick, onDelete, onRetry, onPaste, hideNonErrors }) => {
const [showRetryConfirm, setShowRetryConfirm] = useState(false);
// Find GE IDs with errors
const errorResults = submission.results?.filter(r => r.details?.some(d => d.status === 'error')) ?? [];
const errorGeIds = errorResults.map(r => r.geIdAndLang);
const hasError = errorGeIds.length > 0;
// Get unique usernames from error results
const errorUsernames = [...new Set(
errorResults.flatMap(r => r.details?.filter(d => d.status === 'error').map(d => d.username) ?? [])
)];
// Filter results based on hideNonErrors toggle
const filteredResults = hideNonErrors
? submission.results?.filter(r => r.details?.some(d => d.status === 'error'))
: submission.results;
const handlePaste = () => {
onPaste(submission.username, submission.geIdAndLang);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
const handleRetryConfirm = () => {
setShowRetryConfirm(false);
onRetry(submission, errorGeIds, errorUsernames);
};
// If hideNonErrors is on and no errors, don't render
if (hideNonErrors && !hasError) {
return null;
}
return (
<>
<div className="bg-slate-800/50 border border-slate-700 rounded-lg shadow-md animate-fade-in overflow-hidden">
<div className="w-full flex justify-between items-center p-3 bg-slate-800">
<div className="flex items-center gap-3">
<span className="text-xs text-slate-400 flex-shrink-0">
{submission.timestamp.toLocaleString('vi-VN')}
</span>
</div>
<div className="flex items-center gap-2">
<button
onClick={handlePaste}
className="flex items-center gap-1.5 text-xs font-semibold text-cyan-400 bg-cyan-500/10 hover:bg-cyan-500/20 px-2 py-1 rounded-md transition-colors"
title="Dán username và GE ID & LANG vào form"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
Dán
</button>
{hasError && (
<button
onClick={() => setShowRetryConfirm(true)}
className="flex items-center gap-1.5 text-xs font-semibold text-amber-400 bg-amber-500/10 hover:bg-amber-500/20 px-2 py-1 rounded-md transition-colors"
>
<RetryIcon className="w-4 h-4" />
Retry
</button>
)}
<button
onClick={() => onDelete(submission.id)}
className="flex items-center gap-1.5 text-xs font-semibold text-rose-400 bg-rose-500/10 hover:bg-rose-500/20 px-2 py-1 rounded-md transition-colors"
>
<TrashIcon className="w-4 h-4" />
Xoá
</button>
</div>
</div>
<div className="p-4 space-y-4 border-t border-slate-700">
{filteredResults && filteredResults.length > 0 ? (
filteredResults.map((result, i) => (
<GeIdResultItem key={i} result={result} onErrorClick={onErrorClick} />
))
) : (
<div className="text-center py-4">
<p className="text-slate-500 text-sm">
{hideNonErrors ? 'Không có lỗi trong lần submit này.' : 'Không có dữ liệu kết quả chi tiết cho lần submit này.'}
</p>
</div>
)}
</div>
<style>{`
@keyframes fade-in {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in {
animation: fade-in 0.3s ease-out forwards;
}
`}</style>
</div>
{/* Retry Confirmation Modal */}
<ConfirmModal
isOpen={showRetryConfirm}
title="Xác nhận Retry"
message={
<div className="space-y-4">
<p className="text-slate-300">Bạn muốn retry các GE ID & LANG sau?</p>
<div className="bg-slate-700/50 rounded-lg p-3 max-h-32 overflow-y-auto">
<p className="text-xs text-slate-400 mb-2">GE ID & LANG bị lỗi:</p>
{errorGeIds.map((geId, i) => (
<div key={i} className="text-sm text-indigo-300 font-mono">{geId}</div>
))}
</div>
<div className="bg-slate-700/50 rounded-lg p-3 max-h-32 overflow-y-auto">
<p className="text-xs text-slate-400 mb-2">Usernames:</p>
{errorUsernames.map((username, i) => (
<div key={i} className="text-sm text-cyan-300 font-mono">{username}</div>
))}
</div>
<p className="text-xs text-slate-400">
Các GE ID & LANG lỗi sẽ đưc tách thành record mới bắt đu cấp quyền ngay.
</p>
</div>
}
confirmText="Retry"
cancelText="Huỷ"
onConfirm={handleRetryConfirm}
onCancel={() => setShowRetryConfirm(false)}
/>
</>
);
};
export default memo(HistoryItem);