Á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 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
| 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 |
-
{/*
|
| 101 |
-
<div className=
|
| 102 |
<div className="mb-6 flex justify-between items-start">
|
| 103 |
<div>
|
| 104 |
-
<
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 119 |
-
<
|
| 120 |
-
<
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
<
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
<
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
<
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
|
|
|
|
|
|
| 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-
|
|
|
|
| 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-
|
| 332 |
<div className="text-[10px] uppercase text-slate-500 font-black mb-2 tracking-widest">Estimated Investment</div>
|
| 333 |
-
<div className="text-
|
| 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-
|
| 338 |
<div className="text-[10px] uppercase text-slate-500 font-black mb-2 tracking-widest">Industry Classification</div>
|
| 339 |
-
<div className="text-
|
| 340 |
</div>
|
| 341 |
</div>
|
| 342 |
</div>
|
| 343 |
|
| 344 |
{/* Sidebar Column */}
|
| 345 |
<div className="space-y-12">
|
| 346 |
-
<div className="p-
|
| 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-
|
| 350 |
{selectedTenderForModal.closing_date ? new Date(selectedTenderForModal.closing_date).toLocaleDateString() : "---"}
|
| 351 |
</div>
|
| 352 |
-
<p className="text-
|
| 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>
|