Álvaro Valenzuela Valdes commited on
Commit
3139b66
·
1 Parent(s): 0700cbe

🚀 Production UI Polish: Fixed Portfolio persistence, Checkboxes, Star buttons, and Reports data mapping

Browse files
frontend/components/ProposalDraft.tsx CHANGED
@@ -4,13 +4,38 @@ type Props = {
4
 
5
  export default function ProposalDraft({ proposal }: Props) {
6
  return (
7
- <div className="space-y-6">
8
- <div className="rounded-3xl border border-slate-800 bg-slate-950/80 p-6">
9
- <h2 className="text-2xl font-semibold text-white">Proposal Draft</h2>
10
- <p className="mt-3 text-slate-400">Este borrador se genera automáticamente una vez que se completa el análisis del tender.</p>
 
11
  </div>
12
- <div className="rounded-3xl border border-slate-800 bg-slate-900/80 p-6 text-slate-200">
13
- <pre className="whitespace-pre-wrap break-words text-slate-100">{proposal || "Run agent analysis to generate a proposal draft."}</pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  </div>
15
  </div>
16
  );
 
4
 
5
  export default function ProposalDraft({ proposal }: Props) {
6
  return (
7
+ <div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-700">
8
+ <div className="glass-card rounded-[2rem] p-8 border border-white/10 relative overflow-hidden">
9
+ <div className="absolute top-0 right-0 w-32 h-32 bg-purple-500/10 blur-[60px]" />
10
+ <h2 className="text-2xl font-bold text-white mb-2">Technical Proposal Draft</h2>
11
+ <p className="text-slate-500 text-sm">Automatically generated framework based on expert agent consensus.</p>
12
  </div>
13
+
14
+ <div className="glass-card rounded-[2.5rem] border border-white/5 bg-slate-950/40 p-10 text-slate-300 min-h-[500px] shadow-2xl">
15
+ {proposal ? (
16
+ <div className="prose prose-invert max-w-none">
17
+ <div className="flex items-center justify-between mb-8 pb-4 border-b border-white/5">
18
+ <span className="text-[10px] font-black uppercase tracking-widest text-purple-400">Generated Strategy Document</span>
19
+ <button
20
+ onClick={() => {
21
+ navigator.clipboard.writeText(proposal);
22
+ alert("Copied!");
23
+ }}
24
+ className="text-[10px] font-black uppercase tracking-widest text-slate-500 hover:text-white transition"
25
+ >
26
+ Copy Text 📋
27
+ </button>
28
+ </div>
29
+ <pre className="whitespace-pre-wrap break-words text-slate-100 font-serif leading-relaxed text-lg italic">
30
+ {proposal}
31
+ </pre>
32
+ </div>
33
+ ) : (
34
+ <div className="flex flex-col items-center justify-center h-full text-slate-500 py-20">
35
+ <div className="text-4xl mb-4 opacity-20">📝</div>
36
+ <p className="text-sm font-medium">Run a specialized analysis to generate a custom proposal.</p>
37
+ </div>
38
+ )}
39
  </div>
40
  </div>
41
  );
frontend/components/Reports.tsx CHANGED
@@ -20,30 +20,34 @@ export default function Reports({ reportMarkdown }: Props) {
20
  };
21
 
22
  return (
23
- <div className="space-y-6">
24
- <div className="rounded-3xl border border-slate-800 bg-slate-950/80 p-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
 
25
  <div>
26
- <h2 className="text-2xl font-semibold text-white">Export & Reports</h2>
27
- <p className="mt-2 text-slate-400">Exporta el análisis completo en formato Markdown para tu equipo legal y comercial.</p>
28
  </div>
29
  <button
30
  type="button"
31
  onClick={handleCopy}
32
- className="rounded-3xl bg-cyan px-8 py-4 font-semibold text-slate-950 transition hover:bg-sky shadow-lg shadow-cyan/10"
 
33
  >
34
- Copy Markdown Report
35
  </button>
36
  </div>
37
 
38
- <div className="rounded-3xl border border-slate-800 bg-slate-900/40 p-8 text-slate-200 min-h-[400px] shadow-inner">
39
  {reportMarkdown ? (
40
  <div className="prose prose-invert max-w-none">
41
- <pre className="whitespace-pre-wrap break-words text-slate-100 font-mono text-sm leading-relaxed">{reportMarkdown}</pre>
 
 
42
  </div>
43
  ) : (
44
  <div className="flex flex-col items-center justify-center h-full text-slate-500 py-20">
45
- <div className="text-4xl mb-4">📋</div>
46
- <p>Generate an analysis in 'Agent Analysis' to preview the report here.</p>
47
  </div>
48
  )}
49
  </div>
 
20
  };
21
 
22
  return (
23
+ <div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-700">
24
+ <div className="glass-card rounded-[2rem] p-8 border border-white/10 flex flex-col gap-6 md:flex-row md:items-center md:justify-between relative overflow-hidden">
25
+ <div className="absolute top-0 right-0 w-32 h-32 bg-cyan/10 blur-[60px]" />
26
  <div>
27
+ <h2 className="text-2xl font-bold text-white mb-2">Executive Intelligence Report</h2>
28
+ <p className="text-slate-500 text-sm">Exportable Markdown summary for decision makers and legal review.</p>
29
  </div>
30
  <button
31
  type="button"
32
  onClick={handleCopy}
33
+ disabled={!reportMarkdown}
34
+ className="rounded-2xl premium-gradient px-8 py-4 font-black text-xs uppercase tracking-widest text-white shadow-xl shadow-purple-500/20 transition hover:scale-105 active:scale-95 disabled:opacity-30"
35
  >
36
+ Copy Report 📋
37
  </button>
38
  </div>
39
 
40
+ <div className="glass-card rounded-[2.5rem] border border-white/5 bg-slate-950/40 p-10 text-slate-300 min-h-[500px] shadow-2xl">
41
  {reportMarkdown ? (
42
  <div className="prose prose-invert max-w-none">
43
+ <pre className="whitespace-pre-wrap break-words text-slate-100 font-mono text-[13px] leading-relaxed p-6 rounded-2xl bg-black/20 border border-white/5">
44
+ {reportMarkdown}
45
+ </pre>
46
  </div>
47
  ) : (
48
  <div className="flex flex-col items-center justify-center h-full text-slate-500 py-20">
49
+ <div className="text-4xl mb-4 opacity-20">📋</div>
50
+ <p className="text-sm font-medium tracking-tight">Complete an agentic analysis to compile the final report.</p>
51
  </div>
52
  )}
53
  </div>
frontend/components/TenderSearch.tsx CHANGED
@@ -23,14 +23,16 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
23
  const [selectedCodes, setSelectedCodes] = useState<string[]>([]);
24
  const [isSyncingToAgents, setIsSyncingToAgents] = useState(false);
25
 
26
- const [followedCodes, setFollowedCodes] = useState<string[]>(() => {
27
  if (typeof window !== 'undefined') {
28
- const saved = localStorage.getItem('andes_followed_codes');
29
  return saved ? JSON.parse(saved) : [];
30
  }
31
  return [];
32
  });
33
 
 
 
34
  const [showOnlyFollowed, setShowOnlyFollowed] = useState(forceShowFollowed);
35
  const [isLoading, setIsLoading] = useState(false);
36
  const [isAgileMode, setIsAgileMode] = useState(false);
@@ -45,13 +47,18 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
45
  }, [initialKeyword]);
46
 
47
  useEffect(() => {
48
- localStorage.setItem('andes_followed_codes', JSON.stringify(followedCodes));
49
- }, [followedCodes]);
50
 
51
- const toggleFollow = (code: string) => {
52
- setFollowedCodes(prev =>
53
- prev.includes(code) ? prev.filter(c => c !== code) : [...prev, code]
54
- );
 
 
 
 
 
55
  };
56
 
57
  const toggleSelect = (code: string) => {
@@ -60,6 +67,14 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
60
  );
61
  };
62
 
 
 
 
 
 
 
 
 
63
  const handleSyncToAgents = () => {
64
  setIsSyncingToAgents(true);
65
  setTimeout(() => {
@@ -94,10 +109,11 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
94
  };
95
 
96
  const filteredTenders = useMemo(() => {
97
- let list = tenders;
98
  if (showOnlyFollowed) {
99
- list = list.filter(t => followedCodes.includes(t.code));
100
  }
 
 
101
  if (isAgileMode) {
102
  list = list.filter(t =>
103
  t.code.includes('COT26') ||
@@ -108,7 +124,7 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
108
  );
109
  }
110
  return list;
111
- }, [tenders, showOnlyFollowed, followedCodes, isAgileMode]);
112
 
113
  return (
114
  <div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700">
@@ -242,7 +258,17 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
242
  <table className="w-full text-left text-sm table-fixed border-collapse">
243
  <thead className="bg-white/5 text-slate-500 uppercase text-[10px] tracking-widest font-bold border-b border-white/5">
244
  <tr>
245
- <th className="px-4 py-5 w-[100px]">{t.idSelect}</th>
 
 
 
 
 
 
 
 
 
 
246
  <th className="px-4 py-5 w-[250px]">{t.opportunity}</th>
247
  <th className="px-4 py-5 w-[180px]">{t.buyer}</th>
248
  <th className="px-4 py-5 text-center w-[100px]">{t.status}</th>
@@ -260,8 +286,8 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
260
  <input
261
  type="checkbox"
262
  checked={selectedCodes.includes(tender.code)}
 
263
  onChange={(e) => {
264
- e.stopPropagation();
265
  toggleSelect(tender.code);
266
  }}
267
  className="w-3.5 h-3.5 rounded border-white/10 bg-white/5 text-purple-500 focus:ring-purple-500/40"
@@ -269,7 +295,7 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
269
  <button
270
  onClick={(e) => {
271
  e.stopPropagation();
272
- toggleFollow(tender.code);
273
  }}
274
  className={`text-base transition-all hover:scale-125 ${followedCodes.includes(tender.code) ? 'text-purple-400 drop-shadow-[0_0_8px_rgba(168,85,247,0.4)]' : 'text-slate-600 hover:text-slate-400'}`}
275
  >
@@ -317,7 +343,7 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
317
  </button>
318
  <div className="flex gap-4">
319
  <button
320
- onClick={() => toggleFollow(selectedTenderForModal.code)}
321
  className={`px-4 py-2 rounded-xl border font-bold text-xs transition-all ${
322
  followedCodes.includes(selectedTenderForModal.code)
323
  ? "bg-purple-500/20 border-purple-500/40 text-purple-300"
 
23
  const [selectedCodes, setSelectedCodes] = useState<string[]>([]);
24
  const [isSyncingToAgents, setIsSyncingToAgents] = useState(false);
25
 
26
+ const [followedTenders, setFollowedTenders] = useState<Tender[]>(() => {
27
  if (typeof window !== 'undefined') {
28
+ const saved = localStorage.getItem('andes_followed_tenders_full');
29
  return saved ? JSON.parse(saved) : [];
30
  }
31
  return [];
32
  });
33
 
34
+ const followedCodes = useMemo(() => followedTenders.map(t => t.code), [followedTenders]);
35
+
36
  const [showOnlyFollowed, setShowOnlyFollowed] = useState(forceShowFollowed);
37
  const [isLoading, setIsLoading] = useState(false);
38
  const [isAgileMode, setIsAgileMode] = useState(false);
 
47
  }, [initialKeyword]);
48
 
49
  useEffect(() => {
50
+ localStorage.setItem('andes_followed_tenders_full', JSON.stringify(followedTenders));
51
+ }, [followedTenders]);
52
 
53
+ const toggleFollow = (tender: Tender) => {
54
+ setFollowedTenders(prev => {
55
+ const isFollowing = prev.some(t => t.code === tender.code);
56
+ if (isFollowing) {
57
+ return prev.filter(t => t.code !== tender.code);
58
+ } else {
59
+ return [...prev, tender];
60
+ }
61
+ });
62
  };
63
 
64
  const toggleSelect = (code: string) => {
 
67
  );
68
  };
69
 
70
+ const toggleSelectAll = () => {
71
+ if (selectedCodes.length === filteredTenders.length) {
72
+ setSelectedCodes([]);
73
+ } else {
74
+ setSelectedCodes(filteredTenders.map(t => t.code));
75
+ }
76
+ };
77
+
78
  const handleSyncToAgents = () => {
79
  setIsSyncingToAgents(true);
80
  setTimeout(() => {
 
109
  };
110
 
111
  const filteredTenders = useMemo(() => {
 
112
  if (showOnlyFollowed) {
113
+ return followedTenders;
114
  }
115
+
116
+ let list = tenders;
117
  if (isAgileMode) {
118
  list = list.filter(t =>
119
  t.code.includes('COT26') ||
 
124
  );
125
  }
126
  return list;
127
+ }, [tenders, showOnlyFollowed, followedTenders, isAgileMode]);
128
 
129
  return (
130
  <div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700">
 
258
  <table className="w-full text-left text-sm table-fixed border-collapse">
259
  <thead className="bg-white/5 text-slate-500 uppercase text-[10px] tracking-widest font-bold border-b border-white/5">
260
  <tr>
261
+ <th className="px-4 py-5 w-[100px]">
262
+ <div className="flex items-center gap-2">
263
+ <input
264
+ type="checkbox"
265
+ checked={filteredTenders.length > 0 && selectedCodes.length === filteredTenders.length}
266
+ onChange={toggleSelectAll}
267
+ className="w-3.5 h-3.5 rounded border-white/10 bg-white/5 text-purple-500 focus:ring-purple-500/40"
268
+ />
269
+ {t.idSelect}
270
+ </div>
271
+ </th>
272
  <th className="px-4 py-5 w-[250px]">{t.opportunity}</th>
273
  <th className="px-4 py-5 w-[180px]">{t.buyer}</th>
274
  <th className="px-4 py-5 text-center w-[100px]">{t.status}</th>
 
286
  <input
287
  type="checkbox"
288
  checked={selectedCodes.includes(tender.code)}
289
+ onClick={(e) => e.stopPropagation()}
290
  onChange={(e) => {
 
291
  toggleSelect(tender.code);
292
  }}
293
  className="w-3.5 h-3.5 rounded border-white/10 bg-white/5 text-purple-500 focus:ring-purple-500/40"
 
295
  <button
296
  onClick={(e) => {
297
  e.stopPropagation();
298
+ toggleFollow(tender);
299
  }}
300
  className={`text-base transition-all hover:scale-125 ${followedCodes.includes(tender.code) ? 'text-purple-400 drop-shadow-[0_0_8px_rgba(168,85,247,0.4)]' : 'text-slate-600 hover:text-slate-400'}`}
301
  >
 
343
  </button>
344
  <div className="flex gap-4">
345
  <button
346
+ onClick={() => toggleFollow(selectedTenderForModal)}
347
  className={`px-4 py-2 rounded-xl border font-bold text-xs transition-all ${
348
  followedCodes.includes(selectedTenderForModal.code)
349
  ? "bg-purple-500/20 border-purple-500/40 text-purple-300"