Added dark modes

This commit is contained in:
2025-06-07 03:01:36 -03:00
parent 239c9cf313
commit d37509338e
6 changed files with 856 additions and 639 deletions

View File

@@ -1,275 +1,295 @@
import React, { useEffect, useState } from 'react';
import { supabase, type Debt } from '../lib/supabase';
import { Button } from '@/components/ui/button';
import { DebtCard } from './DebtCard';
import { DebtTimeline } from './DebtTimeline';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import {
DollarSign,
TrendingUp,
Mail,
CheckCircle,
AlertTriangle,
RefreshCw,
BarChart3,
LogOut
} from 'lucide-react';
import React, { useEffect, useState } from "react";
import { supabase, type Debt } from "../lib/supabase";
import { Button } from "@/components/ui/button";
import { DebtCard } from "./DebtCard";
import { DebtTimeline } from "./DebtTimeline";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import {
DollarSign,
TrendingUp,
Mail,
CheckCircle,
AlertTriangle,
RefreshCw,
BarChart3,
LogOut,
} from "lucide-react";
export function Dashboard() {
const [debts, setDebts] = useState<Debt[]>([]);
const [loading, setLoading] = useState(true);
const [stats, setStats] = useState({
totalDebts: 0,
totalAmount: 0,
projectedSavings: 0,
settledCount: 0
});
const [debts, setDebts] = useState<Debt[]>([]);
const [loading, setLoading] = useState(true);
const [stats, setStats] = useState({
totalDebts: 0,
totalAmount: 0,
projectedSavings: 0,
settledCount: 0,
});
useEffect(() => {
fetchDebts();
setupRealtimeSubscription();
}, []);
useEffect(() => {
fetchDebts();
setupRealtimeSubscription();
}, []);
useEffect(() => {
calculateStats();
}, [debts]);
useEffect(() => {
calculateStats();
}, [debts]);
const fetchDebts = async () => {
try {
const { data, error } = await supabase
.from('debts')
.select('*')
.order('created_at', { ascending: false });
const fetchDebts = async () => {
try {
const { data, error } = await supabase
.from("debts")
.select("*")
.order("created_at", { ascending: false });
if (error) throw error;
setDebts(data || []);
} catch (error) {
console.error('Error fetching debts:', error);
} finally {
setLoading(false);
}
};
if (error) throw error;
setDebts(data || []);
} catch (error) {
console.error("Error fetching debts:", error);
} finally {
setLoading(false);
}
};
const setupRealtimeSubscription = () => {
const subscription = supabase
.channel('debts_changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'debts'
},
(payload) => {
if (payload.eventType === 'INSERT') {
setDebts(prev => [payload.new as Debt, ...prev]);
} else if (payload.eventType === 'UPDATE') {
setDebts(prev =>
prev.map(debt =>
debt.id === payload.new.id ? payload.new as Debt : debt
)
);
} else if (payload.eventType === 'DELETE') {
setDebts(prev => prev.filter(debt => debt.id !== payload.old.id));
}
}
)
.subscribe();
const setupRealtimeSubscription = () => {
const subscription = supabase
.channel("debts_changes")
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "debts",
},
(payload) => {
if (payload.eventType === "INSERT") {
setDebts((prev) => [payload.new as Debt, ...prev]);
} else if (payload.eventType === "UPDATE") {
setDebts((prev) =>
prev.map((debt) =>
debt.id === payload.new.id ? (payload.new as Debt) : debt
)
);
} else if (payload.eventType === "DELETE") {
setDebts((prev) =>
prev.filter((debt) => debt.id !== payload.old.id)
);
}
}
)
.subscribe();
return () => {
subscription.unsubscribe();
};
};
return () => {
subscription.unsubscribe();
};
};
const calculateStats = () => {
const totalDebts = debts.length;
const totalAmount = debts.reduce((sum, debt) => sum + debt.amount, 0);
const projectedSavings = debts.reduce((sum, debt) => sum + debt.projected_savings, 0);
const settledCount = debts.filter(debt => debt.status === 'settled').length;
const calculateStats = () => {
const totalDebts = debts.length;
const totalAmount = debts.reduce((sum, debt) => sum + debt.amount, 0);
const projectedSavings = debts.reduce(
(sum, debt) => sum + debt.projected_savings,
0
);
const settledCount = debts.filter(
(debt) => debt.status === "settled"
).length;
setStats({
totalDebts,
totalAmount,
projectedSavings,
settledCount
});
};
setStats({
totalDebts,
totalAmount,
projectedSavings,
settledCount,
});
};
const handleSignOut = async () => {
await supabase.auth.signOut();
window.location.href = '/';
};
const handleSignOut = async () => {
await supabase.auth.signOut();
window.location.href = "/";
};
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
};
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount);
};
const groupedDebts = {
all: debts,
active: debts.filter(debt => ['received', 'negotiating'].includes(debt.status)),
settled: debts.filter(debt => debt.status === 'settled'),
failed: debts.filter(debt => ['failed', 'opted_out'].includes(debt.status))
};
const groupedDebts = {
all: debts,
active: debts.filter((debt) =>
["received", "negotiating"].includes(debt.status)
),
settled: debts.filter((debt) => debt.status === "settled"),
failed: debts.filter((debt) =>
["failed", "opted_out"].includes(debt.status)
),
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="flex items-center gap-2 text-lg">
<RefreshCw className="h-5 w-5 animate-spin" />
Loading dashboard...
</div>
</div>
);
}
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-background">
<div className="flex items-center gap-2 text-lg text-gray-900 dark:text-foreground">
<RefreshCw className="h-5 w-5 animate-spin" />
Loading dashboard...
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="mb-8 flex justify-between items-start">
<div>
<h1 className="text-3xl font-bold text-gray-900 flex items-center gap-3">
<BarChart3 className="h-8 w-8 text-primary" />
InboxNegotiator Dashboard
</h1>
<p className="text-gray-600 mt-2">
AI-powered debt resolution platform with real-time updates
</p>
</div>
</div>
return (
<div className="min-h-screen bg-gray-50 dark:bg-background">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="mb-8 flex justify-between items-start">
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-foreground flex items-center gap-3">
<BarChart3 className="h-8 w-8 text-primary" />
InboxNegotiator Dashboard
</h1>
<p className="text-gray-600 dark:text-gray-300 mt-2">
AI-powered debt resolution platform with real-time updates
</p>
</div>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Debts</CardTitle>
<Mail className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats.totalDebts}</div>
<p className="text-xs text-muted-foreground">
Emails processed
</p>
</CardContent>
</Card>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Debts</CardTitle>
<Mail className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats.totalDebts}</div>
<p className="text-xs text-muted-foreground">Emails processed</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Amount</CardTitle>
<DollarSign className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{formatCurrency(stats.totalAmount)}</div>
<p className="text-xs text-muted-foreground">
Across all debts
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Total Amount
</CardTitle>
<DollarSign className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{formatCurrency(stats.totalAmount)}
</div>
<p className="text-xs text-muted-foreground">Across all debts</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Projected Savings</CardTitle>
<TrendingUp className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">
{formatCurrency(stats.projectedSavings)}
</div>
<p className="text-xs text-muted-foreground">
From negotiations
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Projected Savings
</CardTitle>
<TrendingUp className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600 dark:text-green-400">
{formatCurrency(stats.projectedSavings)}
</div>
<p className="text-xs text-muted-foreground">From negotiations</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Settled Cases</CardTitle>
<CheckCircle className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats.settledCount}</div>
<p className="text-xs text-muted-foreground">
Successfully resolved
</p>
</CardContent>
</Card>
</div>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Settled Cases
</CardTitle>
<CheckCircle className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats.settledCount}</div>
<p className="text-xs text-muted-foreground">
Successfully resolved
</p>
</CardContent>
</Card>
</div>
{/* Main Content */}
<Tabs defaultValue="all" className="space-y-6">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="all" className="flex items-center gap-2">
All Debts
<Badge variant="secondary" className="ml-1">
{groupedDebts.all.length}
</Badge>
</TabsTrigger>
<TabsTrigger value="active" className="flex items-center gap-2">
Active
<Badge variant="secondary" className="ml-1">
{groupedDebts.active.length}
</Badge>
</TabsTrigger>
<TabsTrigger value="settled" className="flex items-center gap-2">
Settled
<Badge variant="secondary" className="ml-1">
{groupedDebts.settled.length}
</Badge>
</TabsTrigger>
<TabsTrigger value="failed" className="flex items-center gap-2">
Failed/Opted Out
<Badge variant="secondary" className="ml-1">
{groupedDebts.failed.length}
</Badge>
</TabsTrigger>
</TabsList>
{/* Main Content */}
<Tabs defaultValue="all" className="space-y-6">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="all" className="flex items-center gap-2">
All Debts
<Badge variant="secondary" className="ml-1">
{groupedDebts.all.length}
</Badge>
</TabsTrigger>
<TabsTrigger value="active" className="flex items-center gap-2">
Active
<Badge variant="secondary" className="ml-1">
{groupedDebts.active.length}
</Badge>
</TabsTrigger>
<TabsTrigger value="settled" className="flex items-center gap-2">
Settled
<Badge variant="secondary" className="ml-1">
{groupedDebts.settled.length}
</Badge>
</TabsTrigger>
<TabsTrigger value="failed" className="flex items-center gap-2">
Failed/Opted Out
<Badge variant="secondary" className="ml-1">
{groupedDebts.failed.length}
</Badge>
</TabsTrigger>
</TabsList>
{Object.entries(groupedDebts).map(([key, debtList]) => (
<TabsContent key={key} value={key} className="space-y-6">
{debtList.length === 0 ? (
<Card>
<CardContent className="flex flex-col items-center justify-center py-12">
<AlertTriangle className="h-12 w-12 text-gray-400 mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">No debts found</h3>
<p className="text-gray-600 text-center max-w-md">
{key === 'all'
? 'Forward your first debt email to get started with automated negotiations.'
: `No debts with ${key} status found.`
}
</p>
</CardContent>
</Card>
) : (
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
{debtList.map((debt) => (
<div key={debt.id} className="space-y-4">
<DebtCard debt={debt} />
<Card className="bg-gray-50">
<CardContent className="p-4">
<DebtTimeline debt={debt} />
</CardContent>
</Card>
</div>
))}
</div>
)}
</TabsContent>
))}
</Tabs>
{Object.entries(groupedDebts).map(([key, debtList]) => (
<TabsContent key={key} value={key} className="space-y-6">
{debtList.length === 0 ? (
<Card>
<CardContent className="flex flex-col items-center justify-center py-12">
<AlertTriangle className="h-12 w-12 text-gray-400 dark:text-gray-500 mb-4" />
<h3 className="text-lg font-medium text-gray-900 dark:text-foreground mb-2">
No debts found
</h3>
<p className="text-gray-600 dark:text-gray-300 text-center max-w-md">
{key === "all"
? "Forward your first debt email to get started with automated negotiations."
: `No debts with ${key} status found.`}
</p>
</CardContent>
</Card>
) : (
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
{debtList.map((debt) => (
<div key={debt.id} className="space-y-4">
<DebtCard debt={debt} />
<Card className="bg-gray-50 dark:bg-gray-800/50">
<CardContent className="p-4">
<DebtTimeline debt={debt} />
</CardContent>
</Card>
</div>
))}
</div>
)}
</TabsContent>
))}
</Tabs>
{/* Footer */}
<Separator className="my-8" />
<div className="text-center text-sm text-gray-600">
<p>InboxNegotiator - FDCPA-compliant debt resolution platform</p>
<p className="mt-1">Real-time updates powered by Supabase</p>
</div>
</div>
</div>
);
}
{/* Footer */}
<Separator className="my-8" />
<div className="text-center text-sm text-gray-600 dark:text-gray-300">
<p>InboxNegotiator - FDCPA-compliant debt resolution platform</p>
<p className="mt-1">Real-time updates powered by Supabase</p>
</div>
</div>
</div>
);
}

View File

@@ -1,136 +1,154 @@
import React from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Calendar, DollarSign, Mail, FileText, TrendingUp } from 'lucide-react';
import type { Debt } from '../lib/supabase';
import React from "react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Calendar, DollarSign, Mail, FileText, TrendingUp } from "lucide-react";
import type { Debt } from "../lib/supabase";
interface DebtCardProps {
debt: Debt;
debt: Debt;
}
const statusColors = {
received: 'bg-blue-100 text-blue-800 border-blue-200',
negotiating: 'bg-yellow-100 text-yellow-800 border-yellow-200',
settled: 'bg-green-100 text-green-800 border-green-200',
failed: 'bg-red-100 text-red-800 border-red-200',
opted_out: 'bg-gray-100 text-gray-800 border-gray-200'
received:
"bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-900/20 dark:text-blue-300 dark:border-blue-800",
negotiating:
"bg-yellow-100 text-yellow-800 border-yellow-200 dark:bg-yellow-900/20 dark:text-yellow-300 dark:border-yellow-800",
settled:
"bg-green-100 text-green-800 border-green-200 dark:bg-green-900/20 dark:text-green-300 dark:border-green-800",
failed:
"bg-red-100 text-red-800 border-red-200 dark:bg-red-900/20 dark:text-red-300 dark:border-red-800",
opted_out:
"bg-gray-100 text-gray-800 border-gray-200 dark:bg-gray-800/20 dark:text-gray-300 dark:border-gray-700",
};
const statusLabels = {
received: 'Received',
negotiating: 'Negotiating',
settled: 'Settled',
failed: 'Failed',
opted_out: 'Opted Out'
received: "Received",
negotiating: "Negotiating",
settled: "Settled",
failed: "Failed",
opted_out: "Opted Out",
};
export function DebtCard({ debt }: DebtCardProps) {
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
};
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount);
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
};
return (
<Card className="w-full hover:shadow-lg transition-all duration-300 border-l-4 border-l-primary">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-lg font-semibold text-gray-900">
{debt.vendor}
</CardTitle>
<Badge
variant="outline"
className={`${statusColors[debt.status]} font-medium`}
>
{statusLabels[debt.status]}
</Badge>
</div>
<CardDescription className="flex items-center gap-2 text-sm text-gray-600">
<Calendar className="h-4 w-4" />
{formatDate(debt.created_at)}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<DollarSign className="h-5 w-5 text-gray-500" />
<span className="text-2xl font-bold text-gray-900">
{formatCurrency(debt.amount)}
</span>
</div>
{debt.projected_savings > 0 && (
<div className="flex items-center gap-1 text-green-600">
<TrendingUp className="h-4 w-4" />
<span className="text-sm font-medium">
Save {formatCurrency(debt.projected_savings)}
</span>
</div>
)}
</div>
return (
<Card className="w-full hover:shadow-lg transition-all duration-300 border-l-4 border-l-primary">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-lg font-semibold text-gray-900 dark:text-foreground">
{debt.vendor}
</CardTitle>
<Badge
variant="outline"
className={`${statusColors[debt.status]} font-medium`}
>
{statusLabels[debt.status]}
</Badge>
</div>
<CardDescription className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
<Calendar className="h-4 w-4" />
{formatDate(debt.created_at)}
</CardDescription>
</CardHeader>
<div className="flex gap-2">
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" size="sm" className="flex-1">
<Mail className="h-4 w-4 mr-2" />
View Email
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Original Email</DialogTitle>
<DialogDescription>
From: {debt.vendor} {formatDate(debt.created_at)}
</DialogDescription>
</DialogHeader>
<div className="mt-4">
<pre className="whitespace-pre-wrap text-sm bg-gray-50 p-4 rounded-lg border">
{debt.raw_email || 'No email content available'}
</pre>
</div>
</DialogContent>
</Dialog>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<DollarSign className="h-5 w-5 text-gray-500 dark:text-gray-400" />
<span className="text-2xl font-bold text-gray-900 dark:text-foreground">
{formatCurrency(debt.amount)}
</span>
</div>
{debt.negotiated_plan && (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" size="sm" className="flex-1">
<FileText className="h-4 w-4 mr-2" />
View Response
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>AI-Generated Negotiation Response</DialogTitle>
<DialogDescription>
FDCPA-compliant response ready to send
</DialogDescription>
</DialogHeader>
<div className="mt-4">
<pre className="whitespace-pre-wrap text-sm bg-gray-50 p-4 rounded-lg border">
{debt.negotiated_plan}
</pre>
</div>
</DialogContent>
</Dialog>
)}
</div>
</CardContent>
</Card>
);
}
{debt.projected_savings > 0 && (
<div className="flex items-center gap-1 text-green-600 dark:text-green-400">
<TrendingUp className="h-4 w-4" />
<span className="text-sm font-medium">
Save {formatCurrency(debt.projected_savings)}
</span>
</div>
)}
</div>
<div className="flex gap-2">
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" size="sm" className="flex-1">
<Mail className="h-4 w-4 mr-2" />
View Email
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Original Email</DialogTitle>
<DialogDescription>
From: {debt.vendor} {formatDate(debt.created_at)}
</DialogDescription>
</DialogHeader>
<div className="mt-4">
<pre className="whitespace-pre-wrap text-sm bg-gray-50 dark:bg-gray-800 p-4 rounded-lg border dark:border-gray-700">
{debt.raw_email || "No email content available"}
</pre>
</div>
</DialogContent>
</Dialog>
{debt.negotiated_plan && (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" size="sm" className="flex-1">
<FileText className="h-4 w-4 mr-2" />
View Response
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>AI-Generated Negotiation Response</DialogTitle>
<DialogDescription>
FDCPA-compliant response ready to send
</DialogDescription>
</DialogHeader>
<div className="mt-4">
<pre className="whitespace-pre-wrap text-sm bg-gray-50 dark:bg-gray-800 p-4 rounded-lg border dark:border-gray-700">
{debt.negotiated_plan}
</pre>
</div>
</DialogContent>
</Dialog>
)}
</div>
</CardContent>
</Card>
);
}

View File

@@ -1,113 +1,139 @@
import React from 'react';
import { CheckCircle, Clock, AlertCircle, XCircle, StopCircle } from 'lucide-react';
import type { Debt } from '../lib/supabase';
import React from "react";
import {
CheckCircle,
Clock,
AlertCircle,
XCircle,
StopCircle,
} from "lucide-react";
import type { Debt } from "../lib/supabase";
interface DebtTimelineProps {
debt: Debt;
debt: Debt;
}
const timelineSteps = [
{ key: 'received', label: 'Email Received', icon: CheckCircle },
{ key: 'negotiating', label: 'Negotiating', icon: Clock },
{ key: 'settled', label: 'Settled', icon: CheckCircle },
{ key: "received", label: "Email Received", icon: CheckCircle },
{ key: "negotiating", label: "Negotiating", icon: Clock },
{ key: "settled", label: "Settled", icon: CheckCircle },
];
const statusIcons = {
received: CheckCircle,
negotiating: Clock,
settled: CheckCircle,
failed: XCircle,
opted_out: StopCircle
received: CheckCircle,
negotiating: Clock,
settled: CheckCircle,
failed: XCircle,
opted_out: StopCircle,
};
const statusColors = {
received: 'text-blue-600',
negotiating: 'text-yellow-600',
settled: 'text-green-600',
failed: 'text-red-600',
opted_out: 'text-gray-600'
received: "text-blue-600 dark:text-blue-400",
negotiating: "text-yellow-600 dark:text-yellow-400",
settled: "text-green-600 dark:text-green-400",
failed: "text-red-600 dark:text-red-400",
opted_out: "text-gray-600 dark:text-gray-400",
};
export function DebtTimeline({ debt }: DebtTimelineProps) {
const currentStepIndex = timelineSteps.findIndex(step => step.key === debt.status);
return (
<div className="space-y-4">
<h3 className="text-lg font-semibold">Progress Timeline</h3>
<div className="space-y-4">
{timelineSteps.map((step, index) => {
const isCompleted = index <= currentStepIndex;
const isActive = index === currentStepIndex;
const Icon = step.icon;
return (
<div key={step.key} className="flex items-center gap-3">
<div className={`
const currentStepIndex = timelineSteps.findIndex(
(step) => step.key === debt.status
);
return (
<div className="space-y-4">
<h3 className="text-lg font-semibold">Progress Timeline</h3>
<div className="space-y-4">
{timelineSteps.map((step, index) => {
const isCompleted = index <= currentStepIndex;
const isActive = index === currentStepIndex;
const Icon = step.icon;
return (
<div key={step.key} className="flex items-center gap-3">
<div
className={`
flex items-center justify-center w-8 h-8 rounded-full border-2
${isCompleted
? 'bg-primary border-primary text-white'
: 'border-gray-300 text-gray-300'
}
${isActive ? 'ring-2 ring-primary/20' : ''}
`}>
<Icon className="h-4 w-4" />
</div>
<div className="flex-1">
<div className={`
${
isCompleted
? "bg-primary border-primary text-white"
: "border-gray-300 dark:border-gray-600 text-gray-300 dark:text-gray-600"
}
${isActive ? "ring-2 ring-primary/20" : ""}
`}
>
<Icon className="h-4 w-4" />
</div>
<div className="flex-1">
<div
className={`
font-medium
${isCompleted ? 'text-gray-900' : 'text-gray-400'}
`}>
{step.label}
</div>
{isActive && (
<div className="text-sm text-gray-600 mt-1">
{debt.status === 'received' && 'Processing email and generating response...'}
{debt.status === 'negotiating' && 'Response generated, waiting for creditor reply'}
{debt.status === 'settled' && 'Payment plan agreed upon'}
</div>
)}
</div>
{isActive && (
<div className="text-sm text-gray-500">
{new Date(debt.updated_at).toLocaleDateString()}
</div>
)}
</div>
);
})}
{/* Special cases for failed and opted_out */}
{(debt.status === 'failed' || debt.status === 'opted_out') && (
<div className="flex items-center gap-3">
<div className={`
${
isCompleted
? "text-gray-900 dark:text-foreground"
: "text-gray-400 dark:text-gray-500"
}
`}
>
{step.label}
</div>
{isActive && (
<div className="text-sm text-gray-600 dark:text-gray-300 mt-1">
{debt.status === "received" &&
"Processing email and generating response..."}
{debt.status === "negotiating" &&
"Response generated, waiting for creditor reply"}
{debt.status === "settled" && "Payment plan agreed upon"}
</div>
)}
</div>
{isActive && (
<div className="text-sm text-gray-500 dark:text-gray-400">
{new Date(debt.updated_at).toLocaleDateString()}
</div>
)}
</div>
);
})}
{/* Special cases for failed and opted_out */}
{(debt.status === "failed" || debt.status === "opted_out") && (
<div className="flex items-center gap-3">
<div
className={`
flex items-center justify-center w-8 h-8 rounded-full border-2
${debt.status === 'failed' ? 'border-red-500 text-red-500' : 'border-gray-500 text-gray-500'}
`}>
{React.createElement(statusIcons[debt.status], { className: 'h-4 w-4' })}
</div>
<div className="flex-1">
<div className={`font-medium ${statusColors[debt.status]}`}>
{debt.status === 'failed' ? 'Negotiation Failed' : 'Opted Out'}
</div>
<div className="text-sm text-gray-600 mt-1">
{debt.status === 'failed'
? 'Creditor declined the negotiation proposal'
: 'User requested to stop communication'
}
</div>
</div>
<div className="text-sm text-gray-500">
{new Date(debt.updated_at).toLocaleDateString()}
</div>
</div>
)}
</div>
</div>
);
}
${
debt.status === "failed"
? "border-red-500 text-red-500 dark:border-red-400 dark:text-red-400"
: "border-gray-500 text-gray-500 dark:border-gray-400 dark:text-gray-400"
}
`}
>
{React.createElement(statusIcons[debt.status], {
className: "h-4 w-4",
})}
</div>
<div className="flex-1">
<div className={`font-medium ${statusColors[debt.status]}`}>
{debt.status === "failed" ? "Negotiation Failed" : "Opted Out"}
</div>
<div className="text-sm text-gray-600 dark:text-gray-300 mt-1">
{debt.status === "failed"
? "Creditor declined the negotiation proposal"
: "User requested to stop communication"}
</div>
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{new Date(debt.updated_at).toLocaleDateString()}
</div>
</div>
)}
</div>
</div>
);
}