Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect } from 'react'; | |
| import { Button } from '@/components/ui/button'; | |
| import { Textarea } from '@/components/ui/textarea'; | |
| import { CheckCircle, Loader2, AlertCircle } from 'lucide-react'; | |
| import { ASSET_REQUEST_CONFIG } from '@/types/asset-request'; | |
| interface AcknowledgeDialogProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| onConfirm: (comments: string) => Promise<void>; | |
| requestId: number; | |
| employeeName: string; | |
| } | |
| const AcknowledgeDialog: React.FC<AcknowledgeDialogProps> = ({ | |
| isOpen, | |
| onClose, | |
| onConfirm, | |
| requestId, | |
| employeeName, | |
| }) => { | |
| const [comments, setComments] = useState(''); | |
| const [isSubmitting, setIsSubmitting] = useState(false); | |
| const [error, setError] = useState(''); | |
| // Reset state when dialog opens | |
| useEffect(() => { | |
| if (isOpen) { | |
| setComments(''); | |
| setError(''); | |
| setIsSubmitting(false); | |
| } | |
| }, [isOpen]); | |
| // Handle escape key and body scroll | |
| useEffect(() => { | |
| if (!isOpen) return; | |
| const handleEscape = (e: KeyboardEvent) => { | |
| if (e.key === 'Escape' && !isSubmitting) { | |
| onClose(); | |
| } | |
| }; | |
| document.addEventListener('keydown', handleEscape); | |
| document.body.style.overflow = 'hidden'; | |
| return () => { | |
| document.removeEventListener('keydown', handleEscape); | |
| document.body.style.overflow = 'unset'; | |
| }; | |
| }, [isOpen, isSubmitting, onClose]); | |
| const handleSubmit = async () => { | |
| setError(''); | |
| setIsSubmitting(true); | |
| try { | |
| await onConfirm(comments.trim()); | |
| onClose(); | |
| } catch (err) { | |
| setError(err instanceof Error ? err.message : 'An error occurred'); | |
| } finally { | |
| setIsSubmitting(false); | |
| } | |
| }; | |
| const handleOverlayClick = (e: React.MouseEvent) => { | |
| if (!isSubmitting && e.target === e.currentTarget) { | |
| onClose(); | |
| } | |
| }; | |
| if (!isOpen) return null; | |
| return ( | |
| <div | |
| className="fixed inset-0 z-[999] flex items-center justify-center p-4 overflow-y-auto" | |
| role="dialog" | |
| aria-modal="true" | |
| aria-labelledby="dialog-title" | |
| > | |
| {/* Overlay */} | |
| <div | |
| className="fixed inset-0 bg-black/80 backdrop-blur-sm transition-opacity" | |
| onClick={handleOverlayClick} | |
| aria-hidden="true" | |
| /> | |
| {/* Modal Content */} | |
| <div | |
| className="relative z-[1000] w-full max-w-md rounded-lg border bg-background shadow-lg animate-in fade-in-0 zoom-in-95 duration-200" | |
| onClick={(e) => e.stopPropagation()} | |
| > | |
| {/* Header */} | |
| <div className="flex items-start gap-3 p-6 border-b"> | |
| <div className="p-2 rounded-full flex-shrink-0 bg-purple-100"> | |
| <CheckCircle className="h-5 w-5 text-purple-600" /> | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <h2 | |
| id="dialog-title" | |
| className="text-lg font-semibold text-foreground" | |
| > | |
| Acknowledge Asset Receipt | |
| </h2> | |
| <p className="text-sm text-muted-foreground mt-1"> | |
| Confirm that you have received the assigned assets for request #{requestId} | |
| </p> | |
| </div> | |
| </div> | |
| {/* Content */} | |
| <div className="p-6 space-y-4"> | |
| {/* Request Info */} | |
| <div className="bg-muted rounded-md p-3 text-sm space-y-1"> | |
| <div className="flex justify-between"> | |
| <span className="text-muted-foreground">Request ID:</span> | |
| <span className="font-medium">#{requestId}</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span className="text-muted-foreground">Employee:</span> | |
| <span className="font-medium">{employeeName}</span> | |
| </div> | |
| </div> | |
| {/* Comments Input (Optional) */} | |
| <div> | |
| <label htmlFor="comments" className="text-sm font-medium mb-2 block"> | |
| Comments (Optional) | |
| </label> | |
| <Textarea | |
| id="comments" | |
| placeholder="Add any comments about the received assets (optional)..." | |
| value={comments} | |
| onChange={(e) => { | |
| setComments(e.target.value); | |
| setError(''); | |
| }} | |
| maxLength={ASSET_REQUEST_CONFIG.MAX_ADMIN_COMMENTS_LENGTH} | |
| rows={3} | |
| className="resize-none w-full" | |
| disabled={isSubmitting} | |
| autoFocus | |
| /> | |
| <div className="flex justify-between items-center mt-1"> | |
| <div className="text-xs text-muted-foreground"> | |
| {comments.length}/{ASSET_REQUEST_CONFIG.MAX_ADMIN_COMMENTS_LENGTH} characters | |
| </div> | |
| {error && ( | |
| <div className="flex items-center gap-1 text-xs text-destructive"> | |
| <AlertCircle className="h-3 w-3" /> | |
| {error} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| {/* Info Message */} | |
| <div className="bg-blue-50 border border-blue-200 rounded-md p-3 text-sm text-blue-800"> | |
| <p>By acknowledging, you confirm that you have received all assigned assets in good condition.</p> | |
| </div> | |
| {/* Action Buttons */} | |
| <div className="flex justify-end gap-3 pt-2"> | |
| <Button | |
| type="button" | |
| variant="outline" | |
| onClick={onClose} | |
| disabled={isSubmitting} | |
| > | |
| Cancel | |
| </Button> | |
| <Button | |
| type="button" | |
| onClick={handleSubmit} | |
| disabled={isSubmitting} | |
| className="bg-purple-600 hover:bg-purple-700 text-white" | |
| > | |
| {isSubmitting ? ( | |
| <> | |
| <Loader2 className="h-4 w-4 mr-2 animate-spin" /> | |
| Acknowledging... | |
| </> | |
| ) : ( | |
| <> | |
| <CheckCircle className="h-4 w-4 mr-2" /> | |
| Acknowledge Receipt | |
| </> | |
| )} | |
| </Button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default AcknowledgeDialog; | |