224 lines
8.8 KiB
TypeScript
224 lines
8.8 KiB
TypeScript
/**
|
|
* ActivityInformationCard Component
|
|
* Displays activity details for Claim Management requests
|
|
*/
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Calendar, MapPin, DollarSign, Receipt } from 'lucide-react';
|
|
import { ClaimActivityInfo } from '@/pages/RequestDetail/types/claimManagement.types';
|
|
import { format } from 'date-fns';
|
|
import { formatDateTime } from '@/utils/dateFormatter';
|
|
import { FormattedDescription } from '@/components/common/FormattedDescription';
|
|
|
|
interface ActivityInformationCardProps {
|
|
activityInfo: ClaimActivityInfo;
|
|
className?: string;
|
|
// Plug-and-play: Pass timestamps from module's business logic
|
|
createdAt?: string | Date;
|
|
updatedAt?: string | Date;
|
|
}
|
|
|
|
export function ActivityInformationCard({
|
|
activityInfo,
|
|
className,
|
|
createdAt,
|
|
updatedAt
|
|
}: ActivityInformationCardProps) {
|
|
// Defensive check: Ensure activityInfo exists
|
|
if (!activityInfo) {
|
|
console.warn('[ActivityInformationCard] activityInfo is missing');
|
|
return (
|
|
<Card className={className}>
|
|
<CardContent className="py-8 text-center text-gray-500">
|
|
<p>Activity information not available</p>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const formatCurrency = (amount: string | number) => {
|
|
const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount;
|
|
if (isNaN(numAmount)) return 'N/A';
|
|
return `₹${numAmount.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
};
|
|
|
|
const formatDate = (dateString?: string) => {
|
|
if (!dateString) return 'N/A';
|
|
try {
|
|
return format(new Date(dateString), 'MMM d, yyyy');
|
|
} catch {
|
|
return dateString;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card className={className}>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<Calendar className="w-5 h-5 text-blue-600" />
|
|
Activity Information
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{/* Activity Name */}
|
|
<div>
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
|
Activity Name
|
|
</label>
|
|
<p className="text-sm text-gray-900 font-medium mt-1">
|
|
{activityInfo.activityName}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Activity Type */}
|
|
<div>
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
|
Activity Type
|
|
</label>
|
|
<p className="text-sm text-gray-900 font-medium mt-1">
|
|
{activityInfo.activityType}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Location */}
|
|
<div>
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
|
Location
|
|
</label>
|
|
<p className="text-sm text-gray-900 font-medium mt-1 flex items-center gap-2">
|
|
<MapPin className="w-4 h-4 text-gray-400" />
|
|
{activityInfo.location}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Requested Date */}
|
|
<div>
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
|
Requested Date
|
|
</label>
|
|
<p className="text-sm text-gray-900 font-medium mt-1">
|
|
{formatDate(activityInfo.requestedDate)}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Estimated Budget */}
|
|
<div>
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
|
Estimated Budget
|
|
</label>
|
|
<p className="text-sm text-gray-900 font-medium mt-1 flex items-center gap-2">
|
|
<DollarSign className="w-4 h-4 text-green-600" />
|
|
{activityInfo.estimatedBudget
|
|
? formatCurrency(activityInfo.estimatedBudget)
|
|
: 'TBD'}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Closed Expenses - Show if value exists (including 0) */}
|
|
{activityInfo.closedExpenses !== undefined && activityInfo.closedExpenses !== null && (
|
|
<div>
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
|
Closed Expenses
|
|
</label>
|
|
<p className="text-sm text-gray-900 font-medium mt-1 flex items-center gap-2">
|
|
<Receipt className="w-4 h-4 text-blue-600" />
|
|
{formatCurrency(activityInfo.closedExpenses)}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Period */}
|
|
{activityInfo.period && (
|
|
<div className="col-span-2">
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
|
Period
|
|
</label>
|
|
<p className="text-sm text-gray-900 font-medium mt-1">
|
|
{formatDate(activityInfo.period.startDate)} - {formatDate(activityInfo.period.endDate)}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Closed Expenses Breakdown */}
|
|
{activityInfo.closedExpensesBreakdown && activityInfo.closedExpensesBreakdown.length > 0 && (
|
|
<div className="pt-4 border-t">
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide mb-3 block">
|
|
Closed Expenses Breakdown
|
|
</label>
|
|
<div className="bg-blue-50 border border-blue-200 rounded-lg overflow-hidden">
|
|
<table className="w-full text-xs sm:text-sm">
|
|
<thead className="bg-blue-100/50">
|
|
<tr>
|
|
<th className="px-3 py-2 text-left font-semibold text-blue-900">Description</th>
|
|
<th className="px-3 py-2 text-right font-semibold text-blue-900 w-24">Base</th>
|
|
<th className="px-3 py-2 text-right font-semibold text-blue-900 w-24">GST</th>
|
|
<th className="px-3 py-2 text-right font-semibold text-blue-900 w-28">Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-blue-200/50">
|
|
{activityInfo.closedExpensesBreakdown.map((item: any, index: number) => (
|
|
<tr key={index} className="hover:bg-blue-100/30">
|
|
<td className="px-3 py-2 text-gray-700">
|
|
{item.description}
|
|
{item.gstRate ? <span className="text-[10px] text-gray-400 block">{item.gstRate}% GST</span> : null}
|
|
</td>
|
|
<td className="px-3 py-2 text-right text-gray-900">{formatCurrency(item.amount)}</td>
|
|
<td className="px-3 py-2 text-right text-gray-900">{formatCurrency(item.gstAmt || 0)}</td>
|
|
<td className="px-3 py-2 text-right font-medium text-gray-900">
|
|
{formatCurrency(item.totalAmt || (item.amount + (item.gstAmt || 0)))}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
<tr className="bg-blue-100/50 font-bold">
|
|
<td colSpan={3} className="px-3 py-2 text-blue-900">Final Claim Amount</td>
|
|
<td className="px-3 py-2 text-right text-blue-700">
|
|
{formatCurrency(
|
|
activityInfo.closedExpensesBreakdown.reduce((sum: number, item: any) => sum + (item.totalAmt || (item.amount + (item.gstAmt || 0))), 0)
|
|
)}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Description */}
|
|
{activityInfo.description && (
|
|
<div className="pt-4 border-t">
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
|
Description
|
|
</label>
|
|
<div className="mt-2 bg-gray-50 p-3 rounded-lg border border-gray-200">
|
|
<FormattedDescription
|
|
content={activityInfo.description || ''}
|
|
className="text-sm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Timestamps - Similar to Request Details Card */}
|
|
{(createdAt || updatedAt) && (
|
|
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-300">
|
|
{createdAt && (
|
|
<div>
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Created</label>
|
|
<p className="text-sm text-gray-900 font-medium mt-1">{formatDateTime(createdAt)}</p>
|
|
</div>
|
|
)}
|
|
{updatedAt && (
|
|
<div>
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Last Updated</label>
|
|
<p className="text-sm text-gray-900 font-medium mt-1">{formatDateTime(updatedAt)}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|