162 lines
5.5 KiB
TypeScript
162 lines
5.5 KiB
TypeScript
/**
|
|
* ProposalDetailsCard Component
|
|
* Displays proposal details submitted by dealer for Claim Management requests
|
|
*/
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
|
import { Receipt, Calendar } from 'lucide-react';
|
|
import { format } from 'date-fns';
|
|
|
|
// Minimal local types to avoid missing imports during runtime
|
|
interface ProposalCostItem {
|
|
description: string;
|
|
amount?: number | null;
|
|
}
|
|
|
|
interface ProposalDetails {
|
|
costBreakup: ProposalCostItem[];
|
|
estimatedBudgetTotal?: number | null;
|
|
timelineForClosure?: string | null;
|
|
dealerComments?: string | null;
|
|
submittedOn?: string | null;
|
|
}
|
|
|
|
interface ProposalDetailsCardProps {
|
|
proposalDetails: ProposalDetails;
|
|
className?: string;
|
|
}
|
|
|
|
export function ProposalDetailsCard({ proposalDetails, className }: ProposalDetailsCardProps) {
|
|
// Calculate estimated total from costBreakup if not provided
|
|
const calculateEstimatedTotal = () => {
|
|
if (proposalDetails.estimatedBudgetTotal !== undefined && proposalDetails.estimatedBudgetTotal !== null) {
|
|
return proposalDetails.estimatedBudgetTotal;
|
|
}
|
|
|
|
// Calculate sum from costBreakup items
|
|
if (proposalDetails.costBreakup && proposalDetails.costBreakup.length > 0) {
|
|
const total = proposalDetails.costBreakup.reduce((sum, item) => {
|
|
const amount = item.amount || 0;
|
|
return sum + (Number.isNaN(amount) ? 0 : amount);
|
|
}, 0);
|
|
return total;
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
const estimatedTotal = calculateEstimatedTotal();
|
|
|
|
const formatCurrency = (amount?: number | null) => {
|
|
if (amount === undefined || amount === null || Number.isNaN(amount)) {
|
|
return '₹0.00';
|
|
}
|
|
return `₹${amount.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
};
|
|
|
|
const formatDate = (dateString?: string | null) => {
|
|
if (!dateString) return '';
|
|
try {
|
|
return format(new Date(dateString), 'MMM d, yyyy, h:mm a');
|
|
} catch {
|
|
return dateString || '';
|
|
}
|
|
};
|
|
|
|
const formatTimelineDate = (dateString?: string | null) => {
|
|
if (!dateString) return '-';
|
|
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">
|
|
<Receipt className="w-5 h-5 text-green-600" />
|
|
Proposal Details
|
|
</CardTitle>
|
|
{proposalDetails.submittedOn && (
|
|
<CardDescription>
|
|
Submitted on {formatDate(proposalDetails.submittedOn)}
|
|
</CardDescription>
|
|
)}
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{/* Cost Breakup */}
|
|
<div>
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide mb-3 block">
|
|
Cost Breakup
|
|
</label>
|
|
<div className="border rounded-lg overflow-hidden">
|
|
<table className="w-full">
|
|
<thead className="bg-gray-50">
|
|
<tr>
|
|
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-700 uppercase tracking-wide">
|
|
Item Description
|
|
</th>
|
|
<th className="px-4 py-2 text-right text-xs font-semibold text-gray-700 uppercase tracking-wide">
|
|
Amount
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-200">
|
|
{(proposalDetails.costBreakup || []).map((item, index) => (
|
|
<tr key={index} className="hover:bg-gray-50">
|
|
<td className="px-4 py-3 text-sm text-gray-900">
|
|
{item.description}
|
|
</td>
|
|
<td className="px-4 py-3 text-sm text-gray-900 text-right font-medium">
|
|
{formatCurrency(item.amount)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
<tr className="bg-green-50 font-semibold">
|
|
<td className="px-4 py-3 text-sm text-gray-900">
|
|
Estimated Budget (Total)
|
|
</td>
|
|
<td className="px-4 py-3 text-sm text-green-700 text-right">
|
|
{formatCurrency(estimatedTotal)}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Timeline for Closure */}
|
|
<div className="pt-2">
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
|
Timeline for Closure
|
|
</label>
|
|
<div className="mt-2 bg-blue-50 border border-blue-200 rounded-lg p-3">
|
|
<div className="flex items-center gap-2">
|
|
<Calendar className="w-4 h-4 text-blue-600" />
|
|
<span className="text-sm font-medium text-gray-900">
|
|
Expected completion by: {formatTimelineDate(proposalDetails.timelineForClosure)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Dealer Comments */}
|
|
{proposalDetails.dealerComments && (
|
|
<div className="pt-2">
|
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
|
Dealer Comments
|
|
</label>
|
|
<p className="text-sm text-gray-700 mt-2 bg-gray-50 p-3 rounded-lg whitespace-pre-line">
|
|
{proposalDetails.dealerComments}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
|