mirror of
https://github.com/FranP-code/inbox-negotiator.git
synced 2025-10-13 00:42:26 +00:00
refactor: Simplify ConversationTimeline component structure and improve readability
This commit is contained in:
@@ -262,330 +262,327 @@ export function ConversationTimeline({
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<MessageSquare className="h-5 w-5" />
|
||||
Conversation Timeline
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className={statusColors[debt.status]}>
|
||||
{statusLabels[debt.status]}
|
||||
</Badge>
|
||||
<span className="text-sm text-gray-500">
|
||||
Round {debt.negotiation_round || 1}
|
||||
</span>
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{messages.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
<MessageSquare className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||
<p>No conversation messages yet</p>
|
||||
<p className="text-sm">
|
||||
Messages will appear here as the negotiation progresses
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{messages.map((message, index) => (
|
||||
<div key={message.id} className="relative">
|
||||
{/* Timeline line */}
|
||||
{index < messages.length - 1 && (
|
||||
<div className="absolute left-4 top-8 bottom-0 w-0.5 bg-gray-200 dark:bg-gray-700" />
|
||||
)}
|
||||
<>
|
||||
{/* <CardHeader> */}
|
||||
{/* <CardTitle className="flex items-center justify-between"> */}
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<MessageSquare className="h-5 w-5" />
|
||||
<h2 className="text-lg font-semibold">Conversation Timeline</h2>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* <Badge variant="outline" className={statusColors[debt.status]}>
|
||||
{statusLabels[debt.status]}
|
||||
</Badge> */}
|
||||
<span className="text-sm text-gray-500">
|
||||
Round {debt.negotiation_round || 1}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* </CardTitle> */}
|
||||
{/* </CardHeader> */}
|
||||
{/* <CardContent className="space-y-4"> */}
|
||||
{messages.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
<MessageSquare className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||
<p>No conversation messages yet</p>
|
||||
<p className="text-sm">
|
||||
Messages will appear here as the negotiation progresses
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{messages.map((message, index) => (
|
||||
<div key={message.id} className="relative">
|
||||
{/* Timeline line */}
|
||||
{index < messages.length - 1 && (
|
||||
<div className="absolute left-4 top-8 bottom-0 w-0.5 bg-gray-200 dark:bg-gray-700" />
|
||||
)}
|
||||
|
||||
<div className="flex gap-4">
|
||||
{/* Icon */}
|
||||
<div
|
||||
className={`
|
||||
<div className="flex gap-4">
|
||||
{/* Icon */}
|
||||
<div
|
||||
className={`
|
||||
flex items-center justify-center w-8 h-8 rounded-full border-2 bg-white dark:bg-gray-800
|
||||
${getMessageColor(message)} border-current
|
||||
`}
|
||||
>
|
||||
{getMessageIcon(message)}
|
||||
>
|
||||
{getMessageIcon(message)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="font-medium text-gray-900 dark:text-foreground">
|
||||
{messageTypeLabels[message.message_type] ||
|
||||
message.message_type}
|
||||
</h4>
|
||||
{message.ai_analysis?.sentiment &&
|
||||
getSentimentIcon(message.ai_analysis.sentiment)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<Calendar className="h-3 w-3" />
|
||||
{formatDate(message.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="font-medium text-gray-900 dark:text-foreground">
|
||||
{messageTypeLabels[message.message_type] ||
|
||||
message.message_type}
|
||||
</h4>
|
||||
{message.ai_analysis?.sentiment &&
|
||||
getSentimentIcon(message.ai_analysis.sentiment)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<Calendar className="h-3 w-3" />
|
||||
{formatDate(message.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300 mb-2">
|
||||
{message.direction === "outbound" ? (
|
||||
<>
|
||||
<User className="h-3 w-3" />
|
||||
<span>You → {message.to_email}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Building2 className="h-3 w-3" />
|
||||
<span>{message.from_email} → You</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{message.subject && (
|
||||
<p className="text-sm font-medium text-gray-800 dark:text-gray-200 mb-2">
|
||||
Subject: {message.subject}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300 mb-2">
|
||||
{message.direction === "outbound" ? (
|
||||
<>
|
||||
<User className="h-3 w-3" />
|
||||
<span>You → {message.to_email}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Building2 className="h-3 w-3" />
|
||||
<span>{message.from_email} → You</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-3 text-sm">
|
||||
<p className="whitespace-pre-wrap text-gray-700 dark:text-gray-300">
|
||||
{message.body.length > 200
|
||||
? `${message.body.substring(0, 200)}...`
|
||||
: message.body}
|
||||
</p>
|
||||
</div>
|
||||
{message.subject && (
|
||||
<p className="text-sm font-medium text-gray-800 dark:text-gray-200 mb-2">
|
||||
Subject: {message.subject}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* AI Analysis */}
|
||||
{message.ai_analysis && (
|
||||
<div className="mt-2 space-y-2">
|
||||
{message.ai_analysis.intent && (
|
||||
<div className="flex items-center gap-2 my-2">
|
||||
<Badge
|
||||
variant={
|
||||
message.ai_analysis.intent === "acceptance"
|
||||
? "default"
|
||||
: "secondary"
|
||||
}
|
||||
className={`text-xs ${
|
||||
message.ai_analysis.intent === "acceptance"
|
||||
? "bg-green-600 text-white"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
Intent: {message.ai_analysis.intent}
|
||||
</Badge>
|
||||
{message.ai_analysis.confidence && (
|
||||
<span className="text-xs text-gray-500">
|
||||
{Math.round(
|
||||
message.ai_analysis.confidence * 100
|
||||
)}
|
||||
% confidence
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-3 text-sm">
|
||||
<p className="whitespace-pre-wrap text-gray-700 dark:text-gray-300">
|
||||
{message.body.length > 200
|
||||
? `${message.body.substring(0, 200)}...`
|
||||
: message.body}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!!message.ai_analysis.extractedTerms
|
||||
?.proposedAmount && (
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400">
|
||||
Proposed Amount: $
|
||||
{formatCurrency(
|
||||
message.ai_analysis.extractedTerms.proposedAmount
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* AI Analysis */}
|
||||
{message.ai_analysis && (
|
||||
<div className="mt-2 space-y-2">
|
||||
{message.ai_analysis.intent && (
|
||||
<div className="flex items-center gap-2 my-2">
|
||||
<Badge
|
||||
variant={
|
||||
message.ai_analysis.intent === "acceptance"
|
||||
? "default"
|
||||
: "secondary"
|
||||
}
|
||||
className={`text-xs ${
|
||||
message.ai_analysis.intent === "acceptance"
|
||||
? "bg-green-600 text-white"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
Intent: {message.ai_analysis.intent}
|
||||
</Badge>
|
||||
{message.ai_analysis.confidence && (
|
||||
<span className="text-xs text-gray-500">
|
||||
{Math.round(message.ai_analysis.confidence * 100)}
|
||||
% confidence
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Show financial outcome for accepted offers */}
|
||||
{message.ai_analysis.intent === "acceptance" &&
|
||||
debt.metadata?.financialOutcome && (
|
||||
<div className="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-3 mt-2">
|
||||
{debt.metadata.financialOutcome.financialBenefit
|
||||
?.type === "principal_reduction" ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<TrendingUp className="h-4 w-4 text-green-600" />
|
||||
<span className="font-medium text-green-800 dark:text-green-200">
|
||||
Principal Reduction Achieved
|
||||
{!!message.ai_analysis.extractedTerms?.proposedAmount && (
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400">
|
||||
Proposed Amount: $
|
||||
{formatCurrency(
|
||||
message.ai_analysis.extractedTerms.proposedAmount
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Show financial outcome for accepted offers */}
|
||||
{message.ai_analysis.intent === "acceptance" &&
|
||||
debt.metadata?.financialOutcome && (
|
||||
<div className="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-3 mt-2">
|
||||
{debt.metadata.financialOutcome.financialBenefit
|
||||
?.type === "principal_reduction" ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<TrendingUp className="h-4 w-4 text-green-600" />
|
||||
<span className="font-medium text-green-800 dark:text-green-200">
|
||||
Principal Reduction Achieved
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Original Debt:
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Original Debt:
|
||||
</span>
|
||||
<div className="font-medium">
|
||||
$
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.originalAmount
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Settlement Amount:
|
||||
</span>
|
||||
<div className="font-medium">
|
||||
$
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.acceptedAmount
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Total Savings:
|
||||
</span>
|
||||
<div className="font-medium text-green-600">
|
||||
$
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.actualSavings
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Reduction:
|
||||
</span>
|
||||
<div className="font-medium text-green-600">
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.financialBenefit.percentage
|
||||
}
|
||||
%
|
||||
</div>
|
||||
<div className="font-medium">
|
||||
$
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.originalAmount
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : debt.metadata.financialOutcome
|
||||
.financialBenefit?.type ===
|
||||
"payment_restructuring" ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Calendar className="h-4 w-4 text-blue-600" />
|
||||
<span className="font-medium text-blue-800 dark:text-blue-200">
|
||||
Payment Plan Restructured
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Settlement Amount:
|
||||
</span>
|
||||
<div className="font-medium">
|
||||
$
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.acceptedAmount
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2 text-xs">
|
||||
<div className="bg-blue-50 dark:bg-blue-900/30 rounded p-2">
|
||||
<div className="font-medium text-blue-800 dark:text-blue-200 mb-1">
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.financialBenefit.description
|
||||
}
|
||||
</div>
|
||||
{debt.metadata.financialOutcome
|
||||
.financialBenefit.cashFlowBenefit && (
|
||||
<div className="text-blue-600 dark:text-blue-300">
|
||||
💰{" "}
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.financialBenefit.cashFlowBenefit
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Total Savings:
|
||||
</span>
|
||||
<div className="font-medium text-green-600">
|
||||
$
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.actualSavings
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Reduction:
|
||||
</span>
|
||||
<div className="font-medium text-green-600">
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.financialBenefit.percentage
|
||||
}
|
||||
%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : debt.metadata.financialOutcome.financialBenefit
|
||||
?.type === "payment_restructuring" ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Calendar className="h-4 w-4 text-blue-600" />
|
||||
<span className="font-medium text-blue-800 dark:text-blue-200">
|
||||
Payment Plan Restructured
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-2 text-xs">
|
||||
<div className="bg-blue-50 dark:bg-blue-900/30 rounded p-2">
|
||||
<div className="font-medium text-blue-800 dark:text-blue-200 mb-1">
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.financialBenefit.description
|
||||
}
|
||||
</div>
|
||||
{debt.metadata.financialOutcome
|
||||
.paymentStructure && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Monthly Payment:
|
||||
</span>
|
||||
<div className="font-medium">
|
||||
$
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.paymentStructure.monthlyAmount
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Term Length:
|
||||
</span>
|
||||
<div className="font-medium">
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.paymentStructure
|
||||
.numberOfPayments
|
||||
}{" "}
|
||||
months
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Total Amount:
|
||||
</span>
|
||||
<div className="font-medium">
|
||||
$
|
||||
{formatCurrency(
|
||||
debt.metadata.financialOutcome
|
||||
.paymentStructure.totalAmount
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Frequency:
|
||||
</span>
|
||||
<div className="font-medium capitalize">
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.paymentStructure.frequency
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
.financialBenefit.cashFlowBenefit && (
|
||||
<div className="text-blue-600 dark:text-blue-300">
|
||||
💰{" "}
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.financialBenefit.cashFlowBenefit
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
<span className="font-medium text-green-800 dark:text-green-200">
|
||||
Offer Accepted
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400">
|
||||
Settlement terms have been agreed upon.
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{debt.metadata.financialOutcome
|
||||
.paymentStructure && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Monthly Payment:
|
||||
</span>
|
||||
<div className="font-medium">
|
||||
$
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.paymentStructure.monthlyAmount
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Term Length:
|
||||
</span>
|
||||
<div className="font-medium">
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.paymentStructure.numberOfPayments
|
||||
}{" "}
|
||||
months
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Total Amount:
|
||||
</span>
|
||||
<div className="font-medium">
|
||||
$
|
||||
{formatCurrency(
|
||||
debt.metadata.financialOutcome
|
||||
.paymentStructure.totalAmount
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
Frequency:
|
||||
</span>
|
||||
<div className="font-medium capitalize">
|
||||
{
|
||||
debt.metadata.financialOutcome
|
||||
.paymentStructure.frequency
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
<span className="font-medium text-green-800 dark:text-green-200">
|
||||
Offer Accepted
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400">
|
||||
Settlement terms have been agreed upon.
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Separator className="my-4" />
|
||||
|
||||
{/* Summary */}
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-gray-600 dark:text-gray-300">
|
||||
Total Messages: {messages.length}
|
||||
</span>
|
||||
<span className="text-gray-600 dark:text-gray-300">
|
||||
Negotiation Round: {debt.negotiation_round || 1}
|
||||
</span>
|
||||
</div>
|
||||
{debt.last_message_at && (
|
||||
<span className="text-gray-500 dark:text-gray-400">
|
||||
Last Activity: {formatDate(debt.last_message_at)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Separator className="my-4" />
|
||||
|
||||
{/* Summary */}
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-gray-600 dark:text-gray-300">
|
||||
Total Messages: {messages.length}
|
||||
</span>
|
||||
<span className="text-gray-600 dark:text-gray-300">
|
||||
Negotiation Round: {debt.negotiation_round || 1}
|
||||
</span>
|
||||
</div>
|
||||
{debt.last_message_at && (
|
||||
<span className="text-gray-500 dark:text-gray-400">
|
||||
Last Activity: {formatDate(debt.last_message_at)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* </CardContent> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user