Technical Documentation: ApplicationDetails Component
Overview
The ApplicationDetails component is a React-based front-end module designed to allow academic administrators (e.g., at IIT Guwahati) to review and manage a specific document application (e.g., bonafide certificate or passport). Rendered as a modal dialog, it displays application details, allows status updates (approve/reject), and supports adding remarks. It integrates with Tanstack Query for data fetching and mutations, React Hot Toast for notifications, React Icons for UI elements, and is styled with Tailwind CSS for a modern, responsive design.
Dependencies
- React: For building the component and managing state (
useState). - React Icons: Provides icons (
FaTimesCircle,FaEye) for buttons. - Tanstack Query (@tanstack/react-query): For fetching application details and handling status/comment mutations.
- newRequest: A custom utility for HTTP requests (assumed to be an Axios wrapper).
- React Hot Toast: For success and error notifications.
- Tailwind CSS: For styling the component.
Component Structure
The ApplicationDetails component is structured as a modal with the following sections:
- Header: Displays the application ID and includes buttons to view full details or close the modal.
- Status Badge: Shows the current application status (Approved, Rejected, or Pending).
- Quick Info Cards: Summarizes student name, document type, and submission date.
- Approval History: Lists existing remarks with an option to add new ones.
- Action Footer: Provides buttons to approve or reject the application.
Code Explanation
Imports
import React, { useState } from 'react';
import { FaTimesCircle, FaEye } from 'react-icons/fa';
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import newRequest from '../../../utils/newRequest';
import { toast } from 'react-hot-toast';
- React: Provides
useStatefor managing local state (newComment,loading,applicationStatus). - React Icons: Imports
FaTimesCircle(close button) andFaEye(view full details button). - Tanstack Query: Imports
useQueryfor fetching data,useMutationfor status updates and comments, anduseQueryClientfor cache management. - newRequest: Utility for API requests (likely Axios-based).
- toast: For displaying notifications.
Component Definition
const ApplicationDetails = ({ application, onClose, onViewFull }) => {
- Props:
application: Object containing application data (e.g.,id,status,studentName,rollNo,type,submittedDate).onClose: Callback to close the modal.onViewFull: Callback to view full application details.
State Management
const [newComment, setNewComment] = useState('');
const [loading, setLoading] = useState(false);
const [applicationStatus, setApplicationStatus] = useState(application.status);
newComment: Stores the text input for a new remark.loading: Tracks the status update process to prevent multiple submissions.applicationStatus: Tracks the current status locally, initialized withapplication.status.
Query Client
const queryClient = useQueryClient();
- Initializes
queryClientfor invalidating queries after mutations.
Data Fetching
const { data: details } = useQuery({
queryKey: ['application-details', application.id],
queryFn: () => newRequest.get(`/acadAdmin/documents/applications/${application.id}`)
.then(res => res.data),
onError: (error) => {
toast.error(error?.response?.data?.message || 'Error fetching application details');
}
});
- Uses
useQueryto fetch detailed application data, including remarks. queryKey:['application-details', application.id]ensures caching per application.queryFn: Makes a GET request to/acadAdmin/documents/applications/${application.id}.onError: Shows a toast with the server error message or a fallback.
Status Update Mutation
const updateStatusMutation = useMutation({
mutationFn: ({ newStatus, remarks }) => {
return newRequest.patch(`/acadAdmin/documents/applications/${application.id}/status`, {
status: newStatus,
remarks
});
},
onSuccess: () => {
queryClient.invalidateQueries(['applications']);
queryClient.invalidateQueries(['application-details', application.id]);
toast.success('Status updated successfully');
},
onError: (error) => {
toast.error(error?.response?.data?.message || 'Error updating status');
}
});
- Defines a mutation to update the application status.
mutationFn: Sends a PATCH request with the new status and a remark.onSuccess: Invalidatesapplicationsandapplication-detailsqueries, shows a success toast.onError: Shows an error toast.
Add Comment Mutation
const addCommentMutation = useMutation({
mutationFn: (comment) => {
return newRequest.post(`/acadAdmin/documents/applications/${application.id}/comment`, {
comment
});
},
onSuccess: () => {
queryClient.invalidateQueries(['applications']);
queryClient.invalidateQueries(['application-details', application.id]);
setNewComment('');
toast.success('Comment added successfully');
},
onError: (error) => {
toast.error(error?.response?.data?.message || 'Error adding comment');
}
});
- Defines a mutation to add a comment.
mutationFn: Sends a POST request with the comment text.onSuccess: Invalidates queries, clearsnewComment, shows a success toast.onError: Shows an error toast.
Status Change Handler
const handleStatusChange = async (newStatus) => {
if (loading || applicationStatus === newStatus) return;
setLoading(true);
try {
const properStatus = newStatus.charAt(0).toUpperCase() + newStatus.slice(1).toLowerCase();
await updateStatusMutation.mutateAsync({
newStatus: properStatus,
remarks: `Application ${properStatus.toLowerCase()}`
});
setApplicationStatus(properStatus);
} catch (error) {
console.error('Error updating status:', error);
} finally {
setLoading(false);
}
};
- Handles status updates (approve/reject).
- Prevents action if already
loadingor ifnewStatusmatchesapplicationStatus. - Capitalizes
newStatus(e.g.,approved→Approved). - Calls the status mutation with a remark.
- Updates local
applicationStatuson success. - Logs errors and resets
loading.
Comment Handler
const handleAddComment = async () => {
if (!newComment.trim()) return;
addCommentMutation.mutate(newComment);
};
- Adds a new comment if
newCommentis non-empty. - Triggers the comment mutation.
Remarks Fallback
const remarks = details?.approvalDetails?.remarks || [];
- Ensures
remarksis an array, defaulting to empty ifapprovalDetailsis undefined.
Rendering
return (
<div className="fixed inset-0 bg-gray-900/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-xl w-full max-w-4xl max-h-[90vh] overflow-y-auto">
...
</div>
</div>
);
- Renders a full-screen modal with a semi-transparent blurred background (
bg-gray-900/50 backdrop-blur-sm). - Inner container is a white card (
bg-white rounded-xl) with max-width (max-w-4xl), max-height (max-h-[90vh]), and auto-scrolling (overflow-y-auto).
Header
<div className="sticky top-0 bg-white border-b border-gray-200 p-6 flex justify-between items-center">
<div>
<h2 className="text-2xl font-bold text-gray-900">Review Application</h2>
<p className="text-sm text-gray-500">Application #{application.id}</p>
</div>
<div className="flex gap-2">
<button
onClick={onViewFull}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
title="View Full Application"
>
<FaEye className="text-gray-500" />
</button>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<FaTimesCircle className="text-gray-500" />
</button>
</div>
</div>
- A sticky header with a bottom border.
- Left: Shows "Review Application" and the application ID.
- Right: Buttons for viewing full details (
FaEye) and closing (FaTimesCircle), with hover effects.
Status Badge
<div className="flex justify-center">
<span className={`inline-flex items-center px-4 py-2 rounded-full text-sm font-medium
${applicationStatus === 'Approved' ? 'bg-emerald-100 text-emerald-800 border border-emerald-300' :
applicationStatus === 'Rejected' ? 'bg-rose-100 text-rose-800 border border-rose-300' :
'bg-amber-100 text-amber-800 border border-amber-300'}`}>
{applicationStatus.charAt(0).toUpperCase() + applicationStatus.slice(1)}
</span>
</div>
- Displays the current
applicationStatusas a centered badge. - Uses conditional Tailwind classes: green for Approved, red for Rejected, yellow for Pending.
- Capitalizes the status text.
Quick Info Cards
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-gray-50 p-4 rounded-lg">
<p className="text-sm text-gray-500 mb-1">Student</p>
<p className="font-medium">{application.studentName}</p>
<p className="text-sm text-gray-500">{application.rollNo}</p>
</div>
<div className="bg-gray-50 p-4 rounded-lg">
<p className="text-sm text-gray-500 mb-1">Document Type</p>
<p className="font-medium capitalize">{application.type}</p>
</div>
<div className="bg-gray-50 p-4 rounded-lg">
<p className="text-sm text-gray-500 mb-1">Submitted On</p>
<p className="font-medium">{application.submittedDate}</p>
</div>
</div>
- Shows student name/roll number, document type, and submission date in a responsive grid (1 column on mobile, 3 on medium screens).
- Styled as light gray cards (
bg-gray-50 p-4 rounded-lg).
Approval History
<div className="space-y-4">
<div className="flex justify-between items-center">
<h3 className="text-lg font-semibold">Approval History</h3>
<div className="flex gap-2">
<input
type="text"
value={newComment}
onChange={(e) => setNewComment(e.target.value)}
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Add a remark..."
/>
<button
onClick={handleAddComment}
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50"
disabled={!newComment.trim() || loading}
>
Add Remark
</button>
</div>
</div>
{remarks.length > 0 ? (
<div className="space-y-3">
{remarks.map((remark, index) => (
<div key={index} className="bg-gray-50 p-4 rounded-lg">
<p className="text-gray-900">{remark}</p>
<div className="mt-2 text-sm text-gray-500">
By: {details?.approvalDetails?.approvedBy?.name || 'Admin'}
</div>
</div>
))}
</div>
) : (
<div className="text-gray-500 italic">No remarks available</div>
)}
</div>
- Displays remarks with an input and button to add new ones.
- Input is bound to
newComment, and the button is disabled if empty or loading. - Remarks are shown in cards with the approver’s name (defaults to "Admin").
- Shows an empty state message if no remarks exist.