Álvaro Valenzuela Valdes commited on
Commit
cb1e16d
·
1 Parent(s): 2fc224f

feat: add Portfolio metric to Dashboard and toggle button to Detail View

Browse files
frontend/app/page.tsx CHANGED
@@ -50,7 +50,26 @@ export default function HomePage() {
50
  const [status, setStatus] = useState("listening");
51
  const [searchKeyword, setSearchKeyword] = useState("");
52
  const [lang, setLang] = useState<Language>("en");
 
53
  const contentRef = useRef<HTMLDivElement>(null);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  const t = translations[lang];
56
 
@@ -254,7 +273,8 @@ export default function HomePage() {
254
  tendersFound={tenders.length}
255
  recommendedOpportunities={analysisResult?.decision === "Recommended" ? 1 : 0}
256
  highRiskItems={analysisResult?.risks.filter(r => r.severity === "High").length ?? 0}
257
- reportsGenerated={analysisResult ? 1 : 0}
 
258
  tenders={tenders}
259
  onFilterClick={handleFilterClick}
260
  lang={lang}
 
50
  const [status, setStatus] = useState("listening");
51
  const [searchKeyword, setSearchKeyword] = useState("");
52
  const [lang, setLang] = useState<Language>("en");
53
+ const [followedCount, setFollowedCount] = useState(0);
54
  const contentRef = useRef<HTMLDivElement>(null);
55
+
56
+ // Sync followed count from localStorage
57
+ useEffect(() => {
58
+ const updateFollowed = () => {
59
+ const saved = localStorage.getItem('andes_followed_tenders_full');
60
+ if (saved) {
61
+ setFollowedCount(JSON.parse(saved).length);
62
+ }
63
+ };
64
+ updateFollowed();
65
+ window.addEventListener('storage', updateFollowed);
66
+ // Also poll slightly for local changes if needed
67
+ const interval = setInterval(updateFollowed, 2000);
68
+ return () => {
69
+ window.removeEventListener('storage', updateFollowed);
70
+ clearInterval(interval);
71
+ };
72
+ }, []);
73
 
74
  const t = translations[lang];
75
 
 
273
  tendersFound={tenders.length}
274
  recommendedOpportunities={analysisResult?.decision === "Recommended" ? 1 : 0}
275
  highRiskItems={analysisResult?.risks.filter(r => r.severity === "High").length ?? 0}
276
+ reportsGenerated={analysisHistory.length}
277
+ followedTendersCount={followedCount}
278
  tenders={tenders}
279
  onFilterClick={handleFilterClick}
280
  lang={lang}
frontend/components/Dashboard.tsx CHANGED
@@ -11,6 +11,7 @@ type Props = {
11
  recommendedOpportunities: number;
12
  highRiskItems: number;
13
  reportsGenerated: number;
 
14
  tenders: Tender[];
15
  onFilterClick?: (type: "sector" | "region", value: string) => void;
16
  lang: Language;
@@ -21,6 +22,7 @@ export default function Dashboard({
21
  recommendedOpportunities,
22
  highRiskItems,
23
  reportsGenerated,
 
24
  tenders,
25
  onFilterClick,
26
  lang
@@ -133,6 +135,7 @@ export default function Dashboard({
133
  <StatCard title={t.tendersFound} value={tendersFound} subtitle={t.activeOpps} />
134
  <StatCard title={t.recommended} value={recommendedOpportunities} subtitle="Fit score > 80%" />
135
  <StatCard title={t.highRisk} value={highRiskItems} subtitle="Riesgos críticos" />
 
136
  <StatCard title={t.totalPipeline} value={formatAmount(totalAmount)} subtitle="Monto total proyectado" />
137
  </div>
138
 
 
11
  recommendedOpportunities: number;
12
  highRiskItems: number;
13
  reportsGenerated: number;
14
+ followedTendersCount: number;
15
  tenders: Tender[];
16
  onFilterClick?: (type: "sector" | "region", value: string) => void;
17
  lang: Language;
 
22
  recommendedOpportunities,
23
  highRiskItems,
24
  reportsGenerated,
25
+ followedTendersCount,
26
  tenders,
27
  onFilterClick,
28
  lang
 
135
  <StatCard title={t.tendersFound} value={tendersFound} subtitle={t.activeOpps} />
136
  <StatCard title={t.recommended} value={recommendedOpportunities} subtitle="Fit score > 80%" />
137
  <StatCard title={t.highRisk} value={highRiskItems} subtitle="Riesgos críticos" />
138
+ <StatCard title="Portfolio" value={followedTendersCount} subtitle="Opps. guardadas" />
139
  <StatCard title={t.totalPipeline} value={formatAmount(totalAmount)} subtitle="Monto total proyectado" />
140
  </div>
141
 
frontend/components/TenderSearch.tsx CHANGED
@@ -220,7 +220,15 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
220
  <span className="text-2xl group-hover:-translate-x-1 transition-transform">←</span>
221
  <span className="text-xs font-black uppercase tracking-widest">Back to search</span>
222
  </button>
223
- <div className="flex bg-white/5 p-1 rounded-2xl border border-white/10">
 
 
 
 
 
 
 
 
224
  <button onClick={() => setActiveDetailTab("Overview")} className={`px-6 py-2.5 rounded-xl text-xs font-black uppercase transition-all ${activeDetailTab === "Overview" ? "bg-purple-600 text-white shadow-lg" : "text-slate-500"}`}>Overview</button>
225
  <button onClick={() => setActiveDetailTab("Agent Chat")} className={`px-6 py-2.5 rounded-xl text-xs font-black uppercase transition-all ${activeDetailTab === "Agent Chat" ? "bg-purple-600 text-white shadow-lg" : "text-slate-500"}`}>Agent Chat</button>
226
  </div>
 
220
  <span className="text-2xl group-hover:-translate-x-1 transition-transform">←</span>
221
  <span className="text-xs font-black uppercase tracking-widest">Back to search</span>
222
  </button>
223
+ <div className="flex items-center gap-3 bg-white/5 p-1 rounded-2xl border border-white/10">
224
+ <button
225
+ onClick={() => toggleFollow(tender)}
226
+ className={`px-4 py-2.5 rounded-xl text-[10px] font-black uppercase transition-all flex items-center gap-2 ${followedCodes.includes(tender.code) ? "bg-amber-500/20 text-amber-400 border border-amber-500/30" : "bg-white/5 text-slate-400 hover:bg-white/10"}`}
227
+ >
228
+ <span>{followedCodes.includes(tender.code) ? "★" : "☆"}</span>
229
+ <span>{followedCodes.includes(tender.code) ? "In Portfolio" : "Add to Portfolio"}</span>
230
+ </button>
231
+ <div className="w-px h-6 bg-white/10 mx-1" />
232
  <button onClick={() => setActiveDetailTab("Overview")} className={`px-6 py-2.5 rounded-xl text-xs font-black uppercase transition-all ${activeDetailTab === "Overview" ? "bg-purple-600 text-white shadow-lg" : "text-slate-500"}`}>Overview</button>
233
  <button onClick={() => setActiveDetailTab("Agent Chat")} className={`px-6 py-2.5 rounded-xl text-xs font-black uppercase transition-all ${activeDetailTab === "Agent Chat" ? "bg-purple-600 text-white shadow-lg" : "text-slate-500"}`}>Agent Chat</button>
234
  </div>