Skip to content

Commit c5d9d58

Browse files
authored
fix: csv completed handling in processing documents (#55)
1 parent d3ed476 commit c5d9d58

3 files changed

Lines changed: 324 additions & 303 deletions

File tree

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import { Badge } from '../ui/badge';
2+
import { Button } from '../ui/button';
3+
import { Progress } from '../../../shared/components/ui/progress';
4+
import { FileText, Sparkles, RefreshCw, CheckCircle, X, Loader2, Clock, AlertCircle } from 'lucide-react';
5+
6+
export interface ProcessingDocument {
7+
id: string;
8+
filename: string;
9+
status: string;
10+
progress_percentage?: number;
11+
error_message?: string;
12+
field_mapping_suggestions?: any;
13+
}
14+
15+
export interface DocumentProcessingListProps {
16+
documents: ProcessingDocument[];
17+
isLoading?: boolean;
18+
onRefresh?: () => void;
19+
onDocumentClick?: (doc: ProcessingDocument) => void;
20+
onCancelDocument?: (documentId: string, filename: string) => void;
21+
onReviewDocument?: (documentId: string, filename: string) => void;
22+
onCsvMappingReview?: (doc: ProcessingDocument) => void;
23+
variant?: 'portfolio' | 'dashboard';
24+
className?: string;
25+
}
26+
27+
export function DocumentProcessingList({
28+
documents,
29+
isLoading = false,
30+
onRefresh,
31+
onDocumentClick,
32+
onCancelDocument,
33+
onReviewDocument,
34+
onCsvMappingReview,
35+
variant = 'portfolio',
36+
className = '',
37+
}: DocumentProcessingListProps) {
38+
// Filter out completed and failed documents (defensive check)
39+
const activeDocuments = documents.filter(
40+
doc => doc.status !== 'complete' && doc.status !== 'failed'
41+
);
42+
43+
if (activeDocuments.length === 0) {
44+
return null;
45+
}
46+
47+
const getStatusIcon = (status: string) => {
48+
switch (status) {
49+
case 'uploaded':
50+
case 'extracting':
51+
case 'ocr_processing':
52+
case 'sectioning':
53+
case 'inserting':
54+
return <Loader2 className="h-5 w-5 text-blue-600 dark:text-blue-400 animate-spin" />;
55+
case 'pending_user_review':
56+
case 'pending_mapping_confirmation':
57+
return <Clock className="h-5 w-5 text-yellow-600 dark:text-yellow-400" />;
58+
case 'complete':
59+
return <CheckCircle className="h-5 w-5 text-green-600 dark:text-green-400" />;
60+
case 'failed':
61+
return <AlertCircle className="h-5 w-5 text-red-600 dark:text-red-400" />;
62+
default:
63+
return <FileText className="h-5 w-5 text-gray-600 dark:text-gray-400" />;
64+
}
65+
};
66+
67+
const bgColorClass = variant === 'portfolio'
68+
? 'bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-950/30 dark:to-indigo-950/30 border-blue-200 dark:border-blue-800'
69+
: 'bg-gradient-to-br from-purple-50 to-blue-50 dark:from-purple-950/30 dark:to-blue-950/30 border-purple-200 dark:border-purple-800';
70+
71+
const iconBgClass = variant === 'portfolio'
72+
? 'bg-blue-100 dark:bg-blue-900'
73+
: 'bg-purple-100 dark:bg-purple-900';
74+
75+
const iconColorClass = variant === 'portfolio'
76+
? 'text-blue-600 dark:text-blue-400'
77+
: 'text-purple-600 dark:text-purple-400';
78+
79+
const titleColorClass = variant === 'portfolio'
80+
? 'text-blue-900 dark:text-blue-100'
81+
: 'text-purple-900 dark:text-purple-100';
82+
83+
const subtitleColorClass = variant === 'portfolio'
84+
? 'text-blue-700 dark:text-blue-300'
85+
: 'text-purple-700 dark:text-purple-300';
86+
87+
const cardBorderClass = variant === 'portfolio'
88+
? 'border-blue-200 dark:border-blue-800'
89+
: 'border-purple-200 dark:border-purple-800';
90+
91+
const title = variant === 'portfolio' ? 'Pending Ingestions' : 'Documents Being Processed';
92+
const IconComponent = variant === 'portfolio' ? Sparkles : Loader2;
93+
const iconAnimationClass = variant === 'portfolio' ? '' : 'animate-spin';
94+
95+
return (
96+
<div className={`${bgColorClass} border rounded-lg p-6 ${className}`}>
97+
<div className="flex items-center justify-between mb-4">
98+
<div className="flex items-center gap-3">
99+
<div className={`${iconBgClass} rounded-full p-2`}>
100+
<IconComponent className={`h-5 w-5 ${iconColorClass} ${iconAnimationClass}`} />
101+
</div>
102+
<div>
103+
<h3 className={`text-lg font-semibold ${titleColorClass}`}>
104+
{title}
105+
</h3>
106+
<p className={`text-sm ${subtitleColorClass}`}>
107+
{activeDocuments.length} document{activeDocuments.length !== 1 ? 's' : ''} {variant === 'portfolio' ? 'awaiting review or processing' : 'in progress'}
108+
</p>
109+
</div>
110+
</div>
111+
{onRefresh && (
112+
<Button
113+
size="sm"
114+
variant="ghost"
115+
onClick={onRefresh}
116+
disabled={isLoading}
117+
className={subtitleColorClass}
118+
>
119+
<RefreshCw className={`h-4 w-4 ${isLoading ? 'animate-spin' : ''}`} />
120+
</Button>
121+
)}
122+
</div>
123+
124+
<div className="space-y-2">
125+
{activeDocuments.map((doc) => (
126+
<div
127+
key={doc.id}
128+
onClick={() => onDocumentClick?.(doc)}
129+
className={`bg-white dark:bg-slate-900 border ${cardBorderClass} rounded-lg p-4 flex items-center justify-between gap-4 hover:shadow-md transition-shadow ${onDocumentClick ? 'cursor-pointer' : ''}`}
130+
>
131+
<div className="flex-1 min-w-0 space-y-2">
132+
<div className="flex items-center gap-3">
133+
{getStatusIcon(doc.status)}
134+
<p className="text-sm font-medium truncate">{doc.filename}</p>
135+
</div>
136+
137+
{/* Progress Bar for Processing Documents (only show if not pending review/mapping) */}
138+
{doc.status !== 'pending_user_review' &&
139+
doc.status !== 'pending_mapping_confirmation' &&
140+
doc.status !== 'complete' &&
141+
variant === 'portfolio' && (
142+
<div className="ml-8 space-y-1">
143+
<div className="flex items-center justify-between text-xs">
144+
<span className="text-muted-foreground">
145+
{doc.status.replace(/_/g, ' ')}
146+
</span>
147+
<span className={`${iconColorClass} font-medium`}>
148+
{doc.progress_percentage || 0}%
149+
</span>
150+
</div>
151+
<Progress value={doc.progress_percentage || 0} className="h-2" />
152+
</div>
153+
)}
154+
155+
<div className="flex items-center gap-3 text-xs text-muted-foreground ml-8">
156+
<span className="font-mono bg-slate-100 dark:bg-slate-800 px-2 py-0.5 rounded">
157+
{doc.id.substring(0, 8)}...
158+
</span>
159+
<Badge
160+
variant={
161+
doc.status === 'pending_user_review' || doc.status === 'pending_mapping_confirmation'
162+
? 'default'
163+
: 'secondary'
164+
}
165+
className={
166+
doc.status === 'pending_user_review'
167+
? 'bg-green-500'
168+
: doc.status === 'pending_mapping_confirmation'
169+
? 'bg-yellow-500'
170+
: ''
171+
}
172+
>
173+
{doc.status.replace(/_/g, ' ')}
174+
</Badge>
175+
{doc.progress_percentage !== undefined && variant === 'dashboard' && (
176+
<span className={`${iconColorClass} font-medium`}>
177+
{doc.progress_percentage}%
178+
</span>
179+
)}
180+
</div>
181+
</div>
182+
183+
<div className="flex items-center gap-2">
184+
{/* Review buttons - only show on Portfolio variant */}
185+
{variant === 'portfolio' && onReviewDocument && onCsvMappingReview && (
186+
<Button
187+
size="sm"
188+
onClick={(e) => {
189+
e.stopPropagation();
190+
if (doc.status === 'pending_mapping_confirmation') {
191+
onCsvMappingReview(doc);
192+
} else {
193+
onReviewDocument(doc.id, doc.filename);
194+
}
195+
}}
196+
disabled={doc.status !== 'pending_user_review' && doc.status !== 'pending_mapping_confirmation'}
197+
className={
198+
(doc.status === 'pending_user_review' || doc.status === 'pending_mapping_confirmation')
199+
? 'bg-green-600 hover:bg-green-700'
200+
: ''
201+
}
202+
>
203+
{doc.status === 'pending_user_review' ? (
204+
<>
205+
<CheckCircle className="h-4 w-4 mr-1" />
206+
Review & Confirm
207+
</>
208+
) : doc.status === 'pending_mapping_confirmation' ? (
209+
<>
210+
<FileText className="h-4 w-4 mr-1" />
211+
Review Mappings
212+
</>
213+
) : (
214+
'Processing...'
215+
)}
216+
</Button>
217+
)}
218+
219+
{/* View status hint - only show on Dashboard variant */}
220+
{variant === 'dashboard' && !onCancelDocument && (
221+
<div className="text-xs text-muted-foreground">
222+
Click to view status
223+
</div>
224+
)}
225+
226+
{/* Cancel button */}
227+
{onCancelDocument && (
228+
<Button
229+
size="sm"
230+
variant="ghost"
231+
onClick={(e) => {
232+
e.stopPropagation();
233+
onCancelDocument(doc.id, doc.filename);
234+
}}
235+
className="text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-950"
236+
title="Cancel processing"
237+
>
238+
<X className="h-4 w-4" />
239+
</Button>
240+
)}
241+
</div>
242+
</div>
243+
))}
244+
</div>
245+
</div>
246+
);
247+
}
248+

0 commit comments

Comments
 (0)