Álvaro Valenzuela Valdes commited on
Commit
8432649
·
1 Parent(s): 2bc1093

feat: Full-width layout, Portfolio UI differentiation, and Agent resilience fix

Browse files
frontend/components/AgentAnalysis.tsx CHANGED
@@ -34,20 +34,21 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
34
  if (!approved || !tender) return;
35
  setIsRunning(true);
36
  let extractedText = documentText;
37
- if (file && !extractedText) {
38
- setIsUploading(true);
39
- try {
 
40
  const uploadResult = await uploadDocument(file);
41
  extractedText = uploadResult.text;
42
  setDocumentText(extractedText);
43
- } catch (error) {
44
- console.error("Error uploading document:", error);
45
- } finally {
46
- setIsUploading(false);
47
  }
 
 
 
 
 
 
48
  }
49
- await onAnalyze(extractedText);
50
- setIsRunning(false);
51
  };
52
 
53
  if (!tender && !analysis) {
@@ -118,6 +119,18 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
118
  <div className="flex flex-col gap-4 lg:w-80">
119
  <div className="glass-card rounded-2xl p-6 bg-white/5 border border-white/10">
120
  <h4 className="text-[10px] font-bold uppercase text-slate-400 mb-4 tracking-widest">Administrative Bases (PDF)</h4>
 
 
 
 
 
 
 
 
 
 
 
 
121
  <input type="file" accept=".pdf" onChange={handleFileChange} className="w-full text-xs text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-[10px] file:font-bold file:bg-white/10 file:text-purple-300 hover:file:bg-white/20 transition cursor-pointer" />
122
  {file && <p className="mt-2 text-[10px] text-green-400 font-bold">✓ Ready for extraction</p>}
123
  </div>
 
34
  if (!approved || !tender) return;
35
  setIsRunning(true);
36
  let extractedText = documentText;
37
+
38
+ try {
39
+ if (file && !extractedText) {
40
+ setIsUploading(true);
41
  const uploadResult = await uploadDocument(file);
42
  extractedText = uploadResult.text;
43
  setDocumentText(extractedText);
 
 
 
 
44
  }
45
+ await onAnalyze(extractedText);
46
+ } catch (error) {
47
+ console.error("Error during analysis flow:", error);
48
+ } finally {
49
+ setIsUploading(false);
50
+ setIsRunning(false);
51
  }
 
 
52
  };
53
 
54
  if (!tender && !analysis) {
 
119
  <div className="flex flex-col gap-4 lg:w-80">
120
  <div className="glass-card rounded-2xl p-6 bg-white/5 border border-white/10">
121
  <h4 className="text-[10px] font-bold uppercase text-slate-400 mb-4 tracking-widest">Administrative Bases (PDF)</h4>
122
+
123
+ {tender?.attachments && tender.attachments.length > 0 && (
124
+ <div className="mb-4 space-y-2">
125
+ {tender.attachments.map((att, i) => (
126
+ <div key={i} className="flex items-center gap-2 p-2 rounded-lg bg-purple-500/10 border border-purple-500/20 text-[10px] text-purple-300">
127
+ <span>📄</span>
128
+ <span className="truncate">{att.name}</span>
129
+ </div>
130
+ ))}
131
+ </div>
132
+ )}
133
+
134
  <input type="file" accept=".pdf" onChange={handleFileChange} className="w-full text-xs text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-[10px] file:font-bold file:bg-white/10 file:text-purple-300 hover:file:bg-white/20 transition cursor-pointer" />
135
  {file && <p className="mt-2 text-[10px] text-green-400 font-bold">✓ Ready for extraction</p>}
136
  </div>
frontend/components/TenderSearch.tsx CHANGED
@@ -97,13 +97,23 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
97
  <div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700">
98
  {!selectedTenderForModal ? (
99
  <>
100
- {/* Search Bar Section */}
101
- <div className="glass-card rounded-3xl p-8">
102
  <div className="mb-6 flex justify-between items-start">
103
  <div>
104
- <h2 className="text-2xl font-bold text-white mb-2">Tender Discovery</h2>
105
- <p className="text-slate-400 text-sm">Real-time access to the Chilean public procurement market.</p>
 
 
 
 
 
 
 
 
 
106
  </div>
 
107
  {selectedCodes.length > 0 && (
108
  <button
109
  onClick={handleSyncToAgents}
@@ -115,46 +125,48 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
115
  )}
116
  </div>
117
 
118
- <form onSubmit={handleSubmit} className="grid grid-cols-1 md:grid-cols-4 gap-6">
119
- <div className="space-y-2">
120
- <label className="text-[10px] uppercase tracking-wider text-slate-500 font-bold px-1">Keyword</label>
121
- <input
122
- type="text"
123
- placeholder="e.g. Software, Cloud..."
124
- className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder:text-slate-600 focus:outline-none focus:ring-2 focus:ring-purple-500/40 transition-all"
125
- value={keyword}
126
- onChange={(e) => setKeyword(e.target.value)}
127
- />
128
- </div>
129
- <div className="space-y-2">
130
- <label className="text-[10px] uppercase tracking-wider text-slate-500 font-bold px-1">Buyer Code</label>
131
- <input
132
- type="text"
133
- placeholder="e.g. 6945"
134
- className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder:text-slate-600 focus:outline-none focus:ring-2 focus:ring-purple-500/40 transition-all"
135
- value={buyerCode}
136
- onChange={(e) => setBuyerCode(e.target.value)}
137
- />
138
- </div>
139
- <div className="space-y-2">
140
- <label className="text-[10px] uppercase tracking-wider text-slate-500 font-bold px-1">Date Limit</label>
141
- <input
142
- type="date"
143
- className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-purple-500/40 transition-all [color-scheme:dark]"
144
- value={date}
145
- onChange={(e) => setDate(e.target.value)}
146
- />
147
- </div>
148
- <div className="flex items-end">
149
- <button
150
- type="submit"
151
- disabled={isLoading}
152
- className="w-full premium-gradient hover:opacity-90 text-white font-bold py-3.5 rounded-xl transition-all shadow-lg shadow-purple-500/20 active:scale-[0.98] disabled:opacity-50"
153
- >
154
- {isLoading ? "Searching..." : "Fetch Opportunities"}
155
- </button>
156
- </div>
157
- </form>
 
 
158
  </div>
159
 
160
  {/* Results Controls */}
@@ -262,7 +274,8 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
262
  </>
263
  ) : (
264
  /* Immersive Detail View (Replaces List) */
265
- <div className="animate-in slide-in-from-right-8 duration-500">
 
266
  <div className="flex items-center justify-between mb-8">
267
  <button
268
  onClick={() => setSelectedTenderForModal(null)}
@@ -298,7 +311,8 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
298
  {selectedTenderForModal.status}
299
  </span>
300
  </div>
301
- <h3 className="text-4xl md:text-5xl font-black text-white leading-tight tracking-tight mb-4">{selectedTenderForModal.name}</h3>
 
302
  <div className="flex flex-wrap items-center gap-x-8 gap-y-4">
303
  <div className="flex items-center gap-3">
304
  <div className="w-8 h-8 rounded-full bg-white/5 flex items-center justify-center text-lg">🏢</div>
@@ -328,28 +342,28 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
328
  </section>
329
 
330
  <div className="grid grid-cols-2 gap-6">
331
- <div className="p-8 rounded-3xl bg-white/[0.03] border border-white/5 group hover:bg-white/[0.05] transition-colors">
332
  <div className="text-[10px] uppercase text-slate-500 font-black mb-2 tracking-widest">Estimated Investment</div>
333
- <div className="text-2xl text-white font-bold tracking-tight">
334
  {selectedTenderForModal.estimated_amount ? new Intl.NumberFormat("es-CL", { style: "currency", currency: "CLP" }).format(selectedTenderForModal.estimated_amount) : "Not Disclosed"}
335
  </div>
336
  </div>
337
- <div className="p-8 rounded-3xl bg-white/[0.03] border border-white/5 group hover:bg-white/[0.05] transition-colors">
338
  <div className="text-[10px] uppercase text-slate-500 font-black mb-2 tracking-widest">Industry Classification</div>
339
- <div className="text-2xl text-white font-bold tracking-tight">{selectedTenderForModal.sector || "General Procurement"}</div>
340
  </div>
341
  </div>
342
  </div>
343
 
344
  {/* Sidebar Column */}
345
  <div className="space-y-12">
346
- <div className="p-10 rounded-[2rem] bg-purple-600/10 border border-purple-500/20 shadow-2xl shadow-purple-500/5 relative overflow-hidden group">
347
  <div className="absolute top-0 right-0 w-32 h-32 bg-purple-500/20 blur-[60px] opacity-0 group-hover:opacity-100 transition-opacity" />
348
  <h4 className="text-[10px] font-black uppercase tracking-[0.3em] text-purple-400 mb-6">Submission Deadline</h4>
349
- <div className="text-4xl font-black text-white mb-2 font-mono">
350
  {selectedTenderForModal.closing_date ? new Date(selectedTenderForModal.closing_date).toLocaleDateString() : "---"}
351
  </div>
352
- <p className="text-xs text-purple-400/60 font-bold uppercase tracking-tighter">Final Window for Proposal Submission</p>
353
  </div>
354
 
355
  <div>
 
97
  <div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700">
98
  {!selectedTenderForModal ? (
99
  <>
100
+ {/* Header Section */}
101
+ <div className={`glass-card rounded-3xl p-8 mb-4 border transition-all duration-500 ${forceShowFollowed ? 'border-purple-500/30 bg-purple-500/5 shadow-[0_0_50px_rgba(168,85,247,0.1)]' : 'border-white/10'}`}>
102
  <div className="mb-6 flex justify-between items-start">
103
  <div>
104
+ <div className="flex items-center gap-3 mb-2">
105
+ <div className={`w-10 h-10 rounded-2xl flex items-center justify-center text-xl ${forceShowFollowed ? 'bg-purple-500 text-white' : 'bg-white/5 text-slate-400'}`}>
106
+ {forceShowFollowed ? "★" : "📡"}
107
+ </div>
108
+ <h2 className="text-3xl font-black text-white tracking-tight">
109
+ {forceShowFollowed ? "My Portfolio" : "Tender Discovery"}
110
+ </h2>
111
+ </div>
112
+ <p className="text-slate-400 text-sm">
113
+ {forceShowFollowed ? "Your curated list of high-value opportunities." : "Real-time access to the Chilean public procurement market."}
114
+ </p>
115
  </div>
116
+
117
  {selectedCodes.length > 0 && (
118
  <button
119
  onClick={handleSyncToAgents}
 
125
  )}
126
  </div>
127
 
128
+ {!forceShowFollowed && (
129
+ <form onSubmit={handleSubmit} className="grid grid-cols-1 md:grid-cols-4 gap-6 animate-in slide-in-from-top-4 duration-500">
130
+ <div className="space-y-2">
131
+ <label className="text-[10px] uppercase tracking-wider text-slate-500 font-bold px-1">Keyword</label>
132
+ <input
133
+ type="text"
134
+ placeholder="e.g. Software, Cloud..."
135
+ className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder:text-slate-600 focus:outline-none focus:ring-2 focus:ring-purple-500/40 transition-all"
136
+ value={keyword}
137
+ onChange={(e) => setKeyword(e.target.value)}
138
+ />
139
+ </div>
140
+ <div className="space-y-2">
141
+ <label className="text-[10px] uppercase tracking-wider text-slate-500 font-bold px-1">Buyer Code</label>
142
+ <input
143
+ type="text"
144
+ placeholder="e.g. 6945"
145
+ className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder:text-slate-600 focus:outline-none focus:ring-2 focus:ring-purple-500/40 transition-all"
146
+ value={buyerCode}
147
+ onChange={(e) => setBuyerCode(e.target.value)}
148
+ />
149
+ </div>
150
+ <div className="space-y-2">
151
+ <label className="text-[10px] uppercase tracking-wider text-slate-500 font-bold px-1">Date Limit</label>
152
+ <input
153
+ type="date"
154
+ className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-purple-500/40 transition-all [color-scheme:dark]"
155
+ value={date}
156
+ onChange={(e) => setDate(e.target.value)}
157
+ />
158
+ </div>
159
+ <div className="flex items-end">
160
+ <button
161
+ type="submit"
162
+ disabled={isLoading}
163
+ className="w-full premium-gradient hover:opacity-90 text-white font-bold py-3.5 rounded-xl transition-all shadow-lg shadow-purple-500/20 active:scale-[0.98] disabled:opacity-50"
164
+ >
165
+ {isLoading ? "Searching..." : "Fetch Opportunities"}
166
+ </button>
167
+ </div>
168
+ </form>
169
+ )}
170
  </div>
171
 
172
  {/* Results Controls */}
 
274
  </>
275
  ) : (
276
  /* Immersive Detail View (Replaces List) */
277
+ <div className="animate-in slide-in-from-right-8 duration-500 w-full max-w-[1600px] mx-auto">
278
+
279
  <div className="flex items-center justify-between mb-8">
280
  <button
281
  onClick={() => setSelectedTenderForModal(null)}
 
311
  {selectedTenderForModal.status}
312
  </span>
313
  </div>
314
+ <h3 className="text-3xl md:text-4xl font-black text-white leading-tight tracking-tight mb-4">{selectedTenderForModal.name}</h3>
315
+
316
  <div className="flex flex-wrap items-center gap-x-8 gap-y-4">
317
  <div className="flex items-center gap-3">
318
  <div className="w-8 h-8 rounded-full bg-white/5 flex items-center justify-center text-lg">🏢</div>
 
342
  </section>
343
 
344
  <div className="grid grid-cols-2 gap-6">
345
+ <div className="p-6 rounded-3xl bg-white/[0.03] border border-white/5 group hover:bg-white/[0.05] transition-colors">
346
  <div className="text-[10px] uppercase text-slate-500 font-black mb-2 tracking-widest">Estimated Investment</div>
347
+ <div className="text-xl text-white font-bold tracking-tight">
348
  {selectedTenderForModal.estimated_amount ? new Intl.NumberFormat("es-CL", { style: "currency", currency: "CLP" }).format(selectedTenderForModal.estimated_amount) : "Not Disclosed"}
349
  </div>
350
  </div>
351
+ <div className="p-6 rounded-3xl bg-white/[0.03] border border-white/5 group hover:bg-white/[0.05] transition-colors">
352
  <div className="text-[10px] uppercase text-slate-500 font-black mb-2 tracking-widest">Industry Classification</div>
353
+ <div className="text-xl text-white font-bold tracking-tight">{selectedTenderForModal.sector || "General Procurement"}</div>
354
  </div>
355
  </div>
356
  </div>
357
 
358
  {/* Sidebar Column */}
359
  <div className="space-y-12">
360
+ <div className="p-8 rounded-[2rem] bg-purple-600/10 border border-purple-500/20 shadow-2xl shadow-purple-500/5 relative overflow-hidden group">
361
  <div className="absolute top-0 right-0 w-32 h-32 bg-purple-500/20 blur-[60px] opacity-0 group-hover:opacity-100 transition-opacity" />
362
  <h4 className="text-[10px] font-black uppercase tracking-[0.3em] text-purple-400 mb-6">Submission Deadline</h4>
363
+ <div className="text-2xl font-black text-white mb-2 font-mono">
364
  {selectedTenderForModal.closing_date ? new Date(selectedTenderForModal.closing_date).toLocaleDateString() : "---"}
365
  </div>
366
+ <p className="text-[10px] text-purple-400/60 font-bold uppercase tracking-tighter">Final Window for Submission</p>
367
  </div>
368
 
369
  <div>