195 lines
6.8 KiB
TypeScript
195 lines
6.8 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog';
|
|
import { Button } from '../ui/button';
|
|
import { Textarea } from '../ui/textarea';
|
|
import { Label } from '../ui/label';
|
|
import { CheckCircle, XCircle, AlertTriangle } from 'lucide-react';
|
|
import { Badge } from '../ui/badge';
|
|
|
|
interface ApprovalActionModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
action: 'approve' | 'reject';
|
|
requestId: string;
|
|
requestTitle: string;
|
|
onSubmit: (action: 'approve' | 'reject', comment: string) => void;
|
|
}
|
|
|
|
export function ApprovalActionModal({
|
|
isOpen,
|
|
onClose,
|
|
action,
|
|
requestId,
|
|
requestTitle,
|
|
onSubmit
|
|
}: ApprovalActionModalProps) {
|
|
const [comment, setComment] = useState('');
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
const handleSubmit = async () => {
|
|
if (!comment.trim() || comment.length > 500) {
|
|
return;
|
|
}
|
|
|
|
setIsSubmitting(true);
|
|
try {
|
|
await onSubmit(action, comment.trim());
|
|
setComment('');
|
|
onClose();
|
|
} catch (error) {
|
|
console.error('Error submitting approval action:', error);
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const handleClose = () => {
|
|
setComment('');
|
|
onClose();
|
|
};
|
|
|
|
const getActionConfig = () => {
|
|
if (action === 'approve') {
|
|
return {
|
|
title: 'Approve Request',
|
|
description: 'Please provide your approval comments and remarks',
|
|
icon: CheckCircle,
|
|
iconColor: 'text-green-600',
|
|
buttonColor: 'bg-green-600 hover:bg-green-700',
|
|
badgeColor: 'bg-green-100 text-green-800 border-green-200',
|
|
placeholder: 'Enter your approval comments and any conditions or notes...'
|
|
};
|
|
} else {
|
|
return {
|
|
title: 'Reject Request',
|
|
description: 'Please provide detailed reasons for rejection',
|
|
icon: XCircle,
|
|
iconColor: 'text-red-600',
|
|
buttonColor: 'bg-red-600 hover:bg-red-700',
|
|
badgeColor: 'bg-red-100 text-red-800 border-red-200',
|
|
placeholder: 'Enter detailed reasons for rejection and any suggestions for improvement...'
|
|
};
|
|
}
|
|
};
|
|
|
|
const config = getActionConfig();
|
|
const IconComponent = config.icon;
|
|
|
|
return (
|
|
<Dialog open={isOpen} onOpenChange={handleClose}>
|
|
<DialogContent className="max-w-2xl">
|
|
<DialogHeader>
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<div className={`p-2 rounded-lg ${action === 'approve' ? 'bg-green-100' : 'bg-red-100'}`}>
|
|
<IconComponent className={`w-6 h-6 ${config.iconColor}`} />
|
|
</div>
|
|
<div className="flex-1">
|
|
<DialogTitle className="text-xl">{config.title}</DialogTitle>
|
|
<DialogDescription className="mt-1">
|
|
{config.description}
|
|
</DialogDescription>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-3 p-4 bg-gray-50 rounded-lg border">
|
|
<div className="flex items-center justify-between">
|
|
<span className="font-medium text-gray-900">Request ID:</span>
|
|
<Badge variant="outline" className="font-mono">
|
|
{requestId}
|
|
</Badge>
|
|
</div>
|
|
<div>
|
|
<span className="font-medium text-gray-900">Title:</span>
|
|
<p className="text-gray-700 mt-1">{requestTitle}</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="font-medium text-gray-900">Action:</span>
|
|
<Badge className={config.badgeColor} variant="outline">
|
|
<IconComponent className="w-3 h-3 mr-1" />
|
|
{action === 'approve' ? 'APPROVE' : 'REJECT'}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="comment" className="text-sm font-semibold text-gray-900">
|
|
Comments & Remarks *
|
|
</Label>
|
|
<Textarea
|
|
id="comment"
|
|
placeholder={config.placeholder}
|
|
value={comment}
|
|
onChange={(e) => setComment(e.target.value.slice(0, 500))}
|
|
className="min-h-[120px] resize-none"
|
|
rows={6}
|
|
/>
|
|
<div className="flex items-center justify-between text-xs text-gray-500">
|
|
<div className="flex items-center gap-1">
|
|
<AlertTriangle className="w-3 h-3" />
|
|
Comments are required and will be visible to all participants
|
|
</div>
|
|
<span>{comment.length}/500</span>
|
|
</div>
|
|
</div>
|
|
|
|
{action === 'reject' && (
|
|
<div className="p-3 bg-orange-50 border border-orange-200 rounded-lg">
|
|
<div className="flex items-start gap-2">
|
|
<AlertTriangle className="w-4 h-4 text-orange-600 mt-0.5 flex-shrink-0" />
|
|
<div className="text-sm">
|
|
<p className="font-medium text-orange-800">Rejection Guidelines</p>
|
|
<p className="text-orange-700 mt-1">
|
|
Please provide specific, actionable feedback to help the initiator improve their request.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{action === 'approve' && (
|
|
<div className="p-3 bg-green-50 border border-green-200 rounded-lg">
|
|
<div className="flex items-start gap-2">
|
|
<CheckCircle className="w-4 h-4 text-green-600 mt-0.5 flex-shrink-0" />
|
|
<div className="text-sm">
|
|
<p className="font-medium text-green-800">Approval Confirmation</p>
|
|
<p className="text-green-700 mt-1">
|
|
This request will be forwarded to the next approver or completed if this is the final step.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<DialogFooter className="gap-2">
|
|
<Button
|
|
variant="outline"
|
|
onClick={handleClose}
|
|
disabled={isSubmitting}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={handleSubmit}
|
|
disabled={!comment.trim() || isSubmitting || comment.length > 500}
|
|
className={config.buttonColor}
|
|
>
|
|
{isSubmitting ? (
|
|
<>
|
|
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
|
|
Processing...
|
|
</>
|
|
) : (
|
|
<>
|
|
<IconComponent className="w-4 h-4 mr-2" />
|
|
{action === 'approve' ? 'Approve Request' : 'Reject Request'}
|
|
</>
|
|
)}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
} |