feat: add estimated completion time to cleaner progress dialog
- Display remaining time estimate (in minutes) when progress >= 5% - Show estimated completion time in 12-hour format (上午/下午 HH:MM:SS) - Update every second during execution - Add startTime state tracking in useCleaner hook
This commit is contained in:
@@ -31,6 +31,7 @@ interface ExecutionReportDialogProps {
|
||||
dryRun?: boolean
|
||||
isExecuting?: boolean
|
||||
progress?: CleanerProgress | null
|
||||
startTime?: number | null
|
||||
}
|
||||
|
||||
export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
@@ -42,15 +43,55 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
errors = [],
|
||||
dryRun = false,
|
||||
isExecuting = false,
|
||||
progress = null
|
||||
progress = null,
|
||||
startTime = null
|
||||
}) => {
|
||||
if (!isOpen) return null
|
||||
|
||||
const hasErrors = errors.length > 0
|
||||
const showProgress = isExecuting && progress
|
||||
|
||||
const [now, setNow] = React.useState(Date.now())
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!showProgress || !startTime) return
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setNow(Date.now())
|
||||
}, 1000)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [showProgress, startTime])
|
||||
|
||||
const estimatedTime = React.useMemo(() => {
|
||||
if (!showProgress || !startTime || !progress) return null
|
||||
|
||||
const currentProgress = progress.progress
|
||||
if (currentProgress < 5) return null
|
||||
|
||||
const elapsedMs = now - startTime
|
||||
const elapsedMinutes = elapsedMs / 60000
|
||||
|
||||
const totalEstimatedMinutes = elapsedMinutes / (currentProgress / 100)
|
||||
const remainingMinutes = Math.max(0, totalEstimatedMinutes - elapsedMinutes)
|
||||
const remainingRounded = Math.round(remainingMinutes)
|
||||
|
||||
const endTime = new Date(now + remainingMinutes * 60000)
|
||||
const hours = endTime.getHours()
|
||||
const minutes = endTime.getMinutes()
|
||||
const seconds = endTime.getSeconds()
|
||||
const period = hours >= 12 ? '下午' : '上午'
|
||||
const displayHours = hours % 12 || 12
|
||||
const formattedTime = `${period} ${displayHours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
|
||||
|
||||
return {
|
||||
remainingMinutes: remainingRounded,
|
||||
formattedTime
|
||||
}
|
||||
}, [showProgress, startTime, progress, now])
|
||||
|
||||
return (
|
||||
<div className="execution-report-overlay" onClick={onClose}>
|
||||
<div className="execution-report-overlay" onClick={showProgress ? undefined : onClose}>
|
||||
<div
|
||||
className="execution-report-dialog"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
@@ -130,6 +171,20 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
<span className="order-number">{progress.currentOrderNumber}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{estimatedTime && (
|
||||
<div className="estimated-time-info">
|
||||
<div className="estimated-time-item">
|
||||
<span className="time-label">预估还要</span>
|
||||
<span className="time-value">{estimatedTime.remainingMinutes} 分钟</span>
|
||||
</div>
|
||||
<div className="estimated-time-item">
|
||||
<span className="time-label">将会在</span>
|
||||
<span className="time-value">{estimatedTime.formattedTime}</span>
|
||||
<span className="time-label">执行完毕</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
// Result View Content
|
||||
@@ -443,6 +498,34 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.estimated-time-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
background: #f6ffed;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #b7eb8f;
|
||||
}
|
||||
|
||||
.estimated-time-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.estimated-time-item .time-label {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.estimated-time-item .time-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
/* Result View Styles */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
|
||||
@@ -67,6 +67,7 @@ export function useCleaner() {
|
||||
|
||||
// Progress state
|
||||
const [progress, setProgress] = useState<CleanerProgress | null>(null)
|
||||
const [startTime, setStartTime] = useState<number | null>(null)
|
||||
|
||||
// Execution settings state
|
||||
const [headless, setHeadless] = useState(() => {
|
||||
@@ -316,6 +317,7 @@ export function useCleaner() {
|
||||
setIsRunning(true)
|
||||
setIsExecuting(true)
|
||||
setProgress(null)
|
||||
setStartTime(Date.now())
|
||||
setIsReportDialogOpen(true)
|
||||
|
||||
try {
|
||||
@@ -355,6 +357,7 @@ export function useCleaner() {
|
||||
setIsRunning(false)
|
||||
setIsExecuting(false)
|
||||
setProgress(null)
|
||||
setStartTime(null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,6 +433,7 @@ export function useCleaner() {
|
||||
cancelEdit,
|
||||
handleAssignManagerOnSelect,
|
||||
progress,
|
||||
startTime,
|
||||
handleValidation,
|
||||
handleCheckboxToggle,
|
||||
handleConfirmDeletion,
|
||||
|
||||
@@ -57,6 +57,7 @@ const CleanerPage: React.FC = () => {
|
||||
cancelEdit,
|
||||
handleAssignManagerOnSelect,
|
||||
progress,
|
||||
startTime,
|
||||
handleValidation,
|
||||
handleCheckboxToggle,
|
||||
handleConfirmDeletion,
|
||||
@@ -485,6 +486,7 @@ const CleanerPage: React.FC = () => {
|
||||
dryRun={dryRun}
|
||||
isExecuting={isExecuting}
|
||||
progress={progress}
|
||||
startTime={startTime}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user