Skip to content

Commit e23fd3c

Browse files
Implement portfolio import with debt/debtor creation and fix data consistency
1 parent d59c938 commit e23fd3c

9 files changed

Lines changed: 1010 additions & 164 deletions

src/components/debts/DebtTable.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ export default function DebtTable({
125125
<TableCell>{getPortfolioName(case_.portfolio_id)}</TableCell>
126126
<TableCell>
127127
${(() => {
128-
const balance = case_.current_balance;
128+
// Try current_balance first, then original_balance as fallback
129+
const balance = case_.current_balance || case_.original_balance;
129130
if (balance === null || balance === undefined || isNaN(balance)) {
130131
return '0';
131132
}

src/pages/Debts.jsx

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,29 @@ export default function Debts() {
5858
Payment.list()
5959
]);
6060

61-
setCases(caseData || []);
61+
// Combine and deduplicate cases from Supabase and localStorage
62+
const mockData = JSON.parse(localStorage.getItem('ccai_mock_data') || '{}');
63+
const localCases = mockData.cases || [];
64+
65+
// Use Map for deduplication
66+
const caseMap = new Map();
67+
68+
// Add Supabase cases first (they take priority)
69+
(caseData || []).forEach(case_ => {
70+
caseMap.set(case_.id, case_);
71+
});
72+
73+
// Add local cases only if not already present
74+
localCases.forEach(case_ => {
75+
if (!caseMap.has(case_.id)) {
76+
caseMap.set(case_.id, case_);
77+
}
78+
});
79+
80+
const allCases = Array.from(caseMap.values());
81+
console.log('Debts page - Supabase cases:', (caseData || []).length, 'Local cases:', localCases.length, 'Total unique:', allCases.length);
82+
83+
setCases(allCases);
6284
setPortfolios(portfolioData || []);
6385
setVendors(vendorData || []);
6486
setDebtors(debtorData || []);
@@ -152,12 +174,30 @@ export default function Debts() {
152174

153175
try {
154176
if (newStatus === 'deleted') {
155-
// Handle deletion
177+
// Handle deletion from both Supabase and localStorage
156178
await Case.delete(caseId);
157179

158-
// Refresh the cases list
159-
const updatedCases = await Case.list('-updated_date');
160-
setCases(updatedCases || []);
180+
// Also remove from localStorage
181+
const mockData = JSON.parse(localStorage.getItem('ccai_mock_data') || '{}');
182+
if (mockData.cases) {
183+
mockData.cases = mockData.cases.filter(c => c.id !== caseId);
184+
localStorage.setItem('ccai_mock_data', JSON.stringify(mockData));
185+
}
186+
187+
// Refresh the cases list with deduplication
188+
const supabaseCases = await Case.list('-updated_date');
189+
const localCases = mockData.cases || [];
190+
191+
// Deduplicate
192+
const caseMap = new Map();
193+
(supabaseCases || []).forEach(case_ => caseMap.set(case_.id, case_));
194+
localCases.forEach(case_ => {
195+
if (!caseMap.has(case_.id)) {
196+
caseMap.set(case_.id, case_);
197+
}
198+
});
199+
200+
setCases(Array.from(caseMap.values()));
161201

162202
// Clear selected case if it was the deleted one
163203
if (selectedCase && selectedCase.id === caseId) {
@@ -173,8 +213,29 @@ export default function Debts() {
173213
// Handle status update
174214
await Case.update(caseId, { status: newStatus });
175215

176-
const updatedCases = await Case.list('-updated_date');
177-
setCases(updatedCases || []);
216+
// Also update in localStorage if present
217+
const mockData = JSON.parse(localStorage.getItem('ccai_mock_data') || '{}');
218+
if (mockData.cases) {
219+
const caseIndex = mockData.cases.findIndex(c => c.id === caseId);
220+
if (caseIndex >= 0) {
221+
mockData.cases[caseIndex].status = newStatus;
222+
localStorage.setItem('ccai_mock_data', JSON.stringify(mockData));
223+
}
224+
}
225+
226+
// Refresh with deduplication
227+
const supabaseCases = await Case.list('-updated_date');
228+
const localCases = mockData.cases || [];
229+
230+
const caseMap = new Map();
231+
(supabaseCases || []).forEach(case_ => caseMap.set(case_.id, case_));
232+
localCases.forEach(case_ => {
233+
if (!caseMap.has(case_.id)) {
234+
caseMap.set(case_.id, case_);
235+
}
236+
});
237+
238+
setCases(Array.from(caseMap.values()));
178239

179240
if (selectedCase && selectedCase.id === caseId) {
180241
const newlyUpdatedCase = (updatedCases || []).find(c => c.id === caseId);

src/pages/PortfolioDetails.jsx

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
import React, { useState, useEffect, useCallback } from 'react';
33
import { useLocation, Link, useNavigate } from 'react-router-dom';
4-
import { Portfolio, Case, Payment } from '@/api/entities';
4+
import { Portfolio, Case, Payment, Vendor } from '@/api/entities';
55
import { createPageUrl } from '@/utils';
66
import { Button } from '@/components/ui/button';
77
import {
@@ -27,6 +27,7 @@ import {
2727
Tooltip as ChartTooltip,
2828
} from 'recharts';
2929
import { subDays, isAfter } from 'date-fns';
30+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
3031

3132
const COLORS = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#6b7280'];
3233

@@ -47,6 +48,8 @@ export default function PortfolioDetails() {
4748
const [casePaymentsMap, setCasePaymentsMap] = useState({});
4849
const [portfolioStats, setPortfolioStats] = useState({ totalCases: 0, totalCollected: 0, collectionRate: 0, paymentCount: 0 });
4950
const [statusDistribution, setStatusDistribution] = useState([]);
51+
const [vendors, setVendors] = useState([]);
52+
const [selectedVendor, setSelectedVendor] = useState('none');
5053
const [isLoading, setIsLoading] = useState(true);
5154
const [filters, setFilters] = useState(initialFilters);
5255

@@ -65,22 +68,63 @@ export default function PortfolioDetails() {
6568
const loadPortfolioDetails = async (id) => {
6669
setIsLoading(true);
6770
try {
68-
const [portfolioData, allDebts, allPayments] = await Promise.all([
71+
const [portfolioData, allDebts, allPayments, vendorList] = await Promise.all([
6972
Portfolio.get(id),
70-
Case.filter({ portfolio_id: id }),
73+
Case.list(),
7174
Payment.list(),
75+
Vendor.list()
7276
]);
77+
78+
console.log('Loading portfolio details for ID:', id);
79+
console.log('All cases from database:', allDebts.length);
80+
console.log('Sample cases:', allDebts.slice(0, 3));
81+
82+
// Combine and deduplicate debts from both sources
83+
const mockData = JSON.parse(localStorage.getItem('ccai_mock_data') || '{}');
84+
const localCases = mockData.cases || [];
85+
const localPortfolioDebts = localCases.filter(debt => debt.portfolio_id === id);
86+
87+
// Filter Supabase debts for this portfolio
88+
const supabasePortfolioDebts = allDebts.filter(debt => debt.portfolio_id === id);
89+
90+
// Use Map for better deduplication
91+
const debtMap = new Map();
92+
93+
// Add Supabase debts first (they take priority)
94+
supabasePortfolioDebts.forEach(debt => {
95+
debtMap.set(debt.id, debt);
96+
});
97+
98+
// Add local debts only if not already present
99+
localPortfolioDebts.forEach(debt => {
100+
if (!debtMap.has(debt.id)) {
101+
debtMap.set(debt.id, debt);
102+
}
103+
});
104+
105+
const allPortfolioDebts = Array.from(debtMap.values());
106+
107+
console.log('Portfolio ID:', id, 'Supabase debts:', supabasePortfolioDebts.length, 'Local debts:', localPortfolioDebts.length, 'Total unique:', allPortfolioDebts.length);
108+
console.log('Unique debt IDs:', allPortfolioDebts.map(d => d.id));
73109

74-
const portfolioPayments = allPayments.filter(p => allDebts.some(c => c.id === p.case_id) && p.status === 'completed');
110+
const portfolioPayments = allPayments.filter(p => allPortfolioDebts.some(c => c.id === p.case_id) && p.status === 'completed');
75111
const totalCollected = portfolioPayments.reduce((sum, p) => sum + (p.amount || 0), 0);
76-
const collectionRate = portfolioData.total_face_value > 0 ? (totalCollected / portfolioData.total_face_value) * 100 : 0;
77-
const totalCases = allDebts.length;
112+
const totalFaceValue = allPortfolioDebts.reduce((sum, debt) => sum + (debt.original_balance || 0), 0);
113+
const collectionRate = totalFaceValue > 0 ? (totalCollected / totalFaceValue) * 100 : 0;
114+
const totalCases = allPortfolioDebts.length;
78115
const paymentCount = portfolioPayments.length;
79116

80-
const statusCounts = allDebts.reduce((acc, curr) => {
117+
const statusCounts = allPortfolioDebts.reduce((acc, curr) => {
81118
acc[curr.status] = (acc[curr.status] || 0) + 1;
82119
return acc;
83120
}, {});
121+
122+
// Update portfolio with calculated values
123+
const updatedPortfolioData = {
124+
...portfolioData,
125+
account_count: totalCases,
126+
total_face_value: totalFaceValue
127+
};
84128

85129
const statusChartData = Object.entries(statusCounts).map(([name, value]) => ({ name, value }));
86130

@@ -95,14 +139,9 @@ export default function PortfolioDetails() {
95139
}, {});
96140
setCasePaymentsMap(newCasePaymentsMap);
97141

98-
// Ensure litigation_eligible exists for display, even if not directly returned by mock entity
99-
const processedPortfolioData = {
100-
...portfolioData,
101-
litigation_eligible: portfolioData.litigation_eligible !== undefined ? portfolioData.litigation_eligible : true, // Default to true for demo
102-
};
103-
104-
setPortfolio(processedPortfolioData);
105-
setDebts(allDebts);
142+
setPortfolio(updatedPortfolioData);
143+
setDebts(allPortfolioDebts);
144+
setVendors(vendorList || []);
106145
setPortfolioStats({ totalCases, totalCollected, collectionRate, paymentCount });
107146
setStatusDistribution(statusChartData);
108147
} catch (error) {
@@ -237,7 +276,20 @@ export default function PortfolioDetails() {
237276
<h1 className="text-3xl font-bold text-gray-900">{portfolio.name}</h1>
238277
<p className="text-gray-600 mt-1">{portfolio.original_creditor}</p>
239278
</div>
240-
<div className="flex gap-2">
279+
<div className="flex gap-2 items-center">
280+
<Select value={selectedVendor} onValueChange={setSelectedVendor}>
281+
<SelectTrigger className="w-48">
282+
<SelectValue placeholder="Select Vendor" />
283+
</SelectTrigger>
284+
<SelectContent>
285+
<SelectItem value="none">No Vendor</SelectItem>
286+
{vendors.map(vendor => (
287+
<SelectItem key={vendor.id} value={vendor.id}>
288+
{vendor.name}
289+
</SelectItem>
290+
))}
291+
</SelectContent>
292+
</Select>
241293
<Button variant="outline">
242294
<Download className="w-4 h-4 mr-2" />
243295
Export Debts

0 commit comments

Comments
 (0)