/** * ApprovalsPanel - ZCLAW Execution Approvals UI * * Displays pending, approved, and rejected approval requests * for Hand executions that require human approval. * * Design based on ZCLAW Dashboard v0.4.0 */ import { useState, useEffect, useCallback } from 'react'; import { useHandStore } from '../store/handStore'; import type { Approval, ApprovalStatus } from '../store/handStore'; import { CheckCircle, XCircle, Clock, RefreshCw, AlertCircle, Loader2, } from 'lucide-react'; // === Status Badge Component === type FilterStatus = 'all' | ApprovalStatus; interface StatusFilterConfig { label: string; className: string; } const STATUS_FILTER_CONFIG: Record = { all: { label: '全部', className: 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300', }, pending: { label: '待审批', className: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400', }, approved: { label: '已批准', className: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400', }, rejected: { label: '已拒绝', className: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400', }, expired: { label: '已过期', className: 'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400', }, }; function StatusFilterButton({ status, isActive, count, onClick, }: { status: FilterStatus; isActive: boolean; count?: number; onClick: () => void; }) { const config = STATUS_FILTER_CONFIG[status]; return ( ); } // === Approval Status Icon === function ApprovalStatusIcon({ status }: { status: ApprovalStatus }) { switch (status) { case 'pending': return ; case 'approved': return ; case 'rejected': return ; case 'expired': return ; default: return null; } } // === Approval Card Component === interface ApprovalCardProps { approval: Approval; onApprove: (id: string) => void; onReject: (id: string, reason: string) => void; isProcessing: boolean; } function ApprovalCard({ approval, onApprove, onReject, isProcessing, }: ApprovalCardProps) { const [showRejectInput, setShowRejectInput] = useState(false); const [rejectReason, setRejectReason] = useState(''); const isPending = approval.status === 'pending'; const handleReject = () => { if (showRejectInput && rejectReason.trim()) { onReject(approval.id, rejectReason.trim()); setRejectReason(''); setShowRejectInput(false); } else { setShowRejectInput(true); } }; const handleCancelReject = () => { setShowRejectInput(false); setRejectReason(''); }; return (
{/* Header */}

{approval.handName}

{approval.action || '执行'} •{' '} {new Date(approval.requestedAt).toLocaleString()}

{approval.status}
{/* Reason */} {approval.reason && (

{approval.reason}

)} {/* Params Preview */} {approval.params && Object.keys(approval.params).length > 0 && (
{JSON.stringify(approval.params, null, 2)}
)} {/* Response Info (if responded) */} {approval.status !== 'pending' && approval.respondedAt && (

响应时间: {new Date(approval.respondedAt).toLocaleString()} {approval.respondedBy && ` 由 ${approval.respondedBy}`}

{approval.responseReason && (

"{approval.responseReason}"

)}
)} {/* Reject Input */} {showRejectInput && (