Álvaro Valenzuela Valdes commited on
Commit ·
bd7895c
1
Parent(s): d7c9d51
Optimize mobile UI, fix layout overlaps and add tender questions link
Browse files- frontend/components/AgentAnalysis.tsx +114 -166
- frontend/components/MarketMonitor.tsx +16 -13
- frontend/components/Sidebar.tsx +1 -1
- frontend/components/TenderSearch.tsx +19 -18
- frontend/globals.css +15 -0
frontend/components/AgentAnalysis.tsx
CHANGED
|
@@ -256,11 +256,11 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
|
|
| 256 |
) : null}
|
| 257 |
<span className="text-xs text-slate-500 font-mono">{tender?.code}</span>
|
| 258 |
</div>
|
| 259 |
-
<h2 className="text-4xl font-bold text-white tracking-tight leading-tight mb-4">{tender?.name}</h2>
|
| 260 |
-
<p className="text-slate-400 text-lg leading-relaxed">{tender?.buyer}</p>
|
| 261 |
|
| 262 |
{tender && (
|
| 263 |
-
<div className="mt-8 grid grid-cols-1
|
| 264 |
<div className="rounded-2xl bg-white/5 p-4 border border-white/5 group hover:bg-white/10 transition-colors">
|
| 265 |
<p className="text-[10px] uppercase text-slate-500 font-bold mb-1 tracking-widest">Investment</p>
|
| 266 |
<p className="text-lg font-black text-white">
|
|
@@ -345,24 +345,35 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
|
|
| 345 |
|
| 346 |
{/* Scraped Intelligence / Tabs */}
|
| 347 |
{tenderDetails && (
|
| 348 |
-
<div className="mt-8 flex flex-wrap gap-
|
| 349 |
{tenderDetails.tabs?.history?.found && (
|
| 350 |
-
<div className="flex items-center gap-2 px-
|
| 351 |
<span className="text-purple-400 text-xs">📜</span> History Available
|
| 352 |
</div>
|
| 353 |
)}
|
| 354 |
-
{tenderDetails.
|
| 355 |
-
<
|
| 356 |
-
|
| 357 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
)}
|
| 359 |
{tenderDetails.tabs?.opening?.found && (
|
| 360 |
-
<div className="flex items-center gap-2 px-
|
| 361 |
<span className="text-green-400 text-xs">🔓</span> Opening Log Found
|
| 362 |
</div>
|
| 363 |
)}
|
| 364 |
{tenderDetails.metadata?.has_adjudication && (
|
| 365 |
-
<div className="flex items-center gap-2 px-
|
| 366 |
<span className="text-xs">🏆</span> Adjudicated
|
| 367 |
</div>
|
| 368 |
)}
|
|
@@ -553,68 +564,50 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
|
|
| 553 |
<p className="text-slate-400 mb-6">{error}</p>
|
| 554 |
<button
|
| 555 |
onClick={handleAnalyzeClick}
|
| 556 |
-
className="px-6 py-3 rounded-2xl bg-red-500/20 text-red-400 font-bold border
|
| 557 |
-
>
|
| 558 |
-
Retry Analysis
|
| 559 |
-
</button>
|
| 560 |
-
</div>
|
| 561 |
-
)}
|
| 562 |
-
|
| 563 |
-
{/* Analysis Results View */}
|
| 564 |
{activeAnalysis && (
|
| 565 |
-
<div id="analysis-results" className="grid gap-8 lg:grid-cols-12 animate-in fade-in slide-in-from-bottom-8 duration-500 scroll-mt-20">
|
| 566 |
<div className="lg:col-span-8 space-y-8">
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
+ </div>
|
| 577 |
-
+ </div>
|
| 578 |
-
+
|
| 579 |
-
<div className="flex items-start justify-between mb-8">
|
| 580 |
<div>
|
| 581 |
<div className="text-[11px] font-bold uppercase tracking-[0.3em] text-purple-400 mb-2">Agent Consensus</div>
|
| 582 |
-
<h3 className="text-6xl font-black text-white">{activeAnalysis.fit_score}% <span className="text-2xl font-light text-slate-500">Fit Score</span></h3>
|
| 583 |
<div className="mt-2 flex items-center gap-2">
|
| 584 |
<span className="text-[10px] text-slate-500 font-mono">Analyzing:</span>
|
| 585 |
-
<span className="text-[10px] text-purple-300 font-bold">{corral.find(a => a.id === activeAnimalId)?.file.name || tender?.name}</span>
|
| 586 |
</div>
|
| 587 |
</div>
|
| 588 |
-
|
| 589 |
-
<div className={`rounded-2xl px-6 py-3 text-[10px] font-black uppercase tracking-widest shadow-lg ${activeAnalysis.decision === 'Recommended' ? 'bg-green-500/20 text-green-400 border border-green-500/30 shadow-green-500/10' : 'bg-amber-500/20 text-amber-400 border border-amber-500/30 shadow-amber-500/10'}`}>
|
| 590 |
{activeAnalysis.decision}
|
| 591 |
</div>
|
| 592 |
-
<div className="flex gap-2">
|
| 593 |
<button
|
| 594 |
onClick={() => window.print()}
|
| 595 |
-
className="px-4 py-2 rounded-xl bg-white/5 border border-white/10 text-[
|
| 596 |
>
|
| 597 |
Export PDF
|
| 598 |
</button>
|
| 599 |
<button
|
| 600 |
onClick={generateAnnexes}
|
| 601 |
disabled={isGeneratingAnnexes}
|
| 602 |
-
className={`px-4 py-2 rounded-xl border text-[
|
| 603 |
>
|
| 604 |
-
{isGeneratingAnnexes ? 'Generating...' : '✨ Anexos
|
| 605 |
</button>
|
| 606 |
-
<button
|
| 607 |
-
onClick={() => alert("Report sent to executive committee via REW Secure Channel.")}
|
| 608 |
-
className="px-4 py-2 rounded-xl bg-white/5 border border-white/10 text-xs text-slate-400 hover:text-white hover:bg-white/10 transition"
|
| 609 |
-
title="Share Analysis"
|
| 610 |
-
>
|
| 611 |
-
📧
|
| 612 |
-
</button>
|
| 613 |
</div>
|
| 614 |
</div>
|
| 615 |
</div>
|
| 616 |
<div className="prose prose-invert max-w-none">
|
| 617 |
-
<p className="text-slate-300 text-xl leading-relaxed italic border-l-4 border-purple-500 pl-8">{activeAnalysis.executive_summary}</p>
|
| 618 |
</div>
|
| 619 |
|
| 620 |
{/* Requirement Q&A Section */}
|
|
@@ -622,176 +615,131 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
|
|
| 622 |
<div className="mt-12 space-y-6">
|
| 623 |
<div className="flex items-center gap-3 border-b border-white/5 pb-4">
|
| 624 |
<span className="text-2xl">📋</span>
|
| 625 |
-
<h4 className="text-[11px] font-bold uppercase tracking-widest text-purple-400">Requirement Response
|
| 626 |
</div>
|
| 627 |
<div className="grid gap-4">
|
| 628 |
{activeAnalysis.requirement_responses.map((item, i) => (
|
| 629 |
-
<div key={i} className="rounded-2xl bg-white/[0.03] border border-white/5 p-6 hover:border-purple-500/30 transition-all group">
|
| 630 |
<div className="flex gap-4">
|
| 631 |
<span className="text-purple-500 font-bold font-mono">Q.</span>
|
| 632 |
-
<p className="text-white font-semibold text-sm">{item.question}</p>
|
| 633 |
</div>
|
| 634 |
-
<div className="mt-4 flex gap-4 pl-8 border-l border-white/10">
|
| 635 |
<span className="text-green-400 font-bold font-mono">A.</span>
|
| 636 |
-
<p className="text-slate-400 text-sm leading-relaxed">{item.answer}</p>
|
| 637 |
</div>
|
| 638 |
</div>
|
| 639 |
))}
|
| 640 |
</div>
|
| 641 |
</div>
|
| 642 |
)}
|
| 643 |
-
|
| 644 |
-
{/* Proposal Draft Section */}
|
| 645 |
-
{activeAnalysis.proposal_draft && (
|
| 646 |
-
<div className="mt-12 space-y-6">
|
| 647 |
-
<div className="flex items-center justify-between border-b border-white/5 pb-4">
|
| 648 |
-
<h4 className="text-[11px] font-bold uppercase tracking-widest text-purple-400">AI Generated Proposal Draft</h4>
|
| 649 |
-
<button
|
| 650 |
-
onClick={() => {
|
| 651 |
-
navigator.clipboard.writeText(activeAnalysis.proposal_draft);
|
| 652 |
-
alert("Proposal copied to clipboard!");
|
| 653 |
-
}}
|
| 654 |
-
className="text-[10px] font-bold uppercase text-slate-500 hover:text-white transition"
|
| 655 |
-
>
|
| 656 |
-
Copy to Clipboard 📋
|
| 657 |
-
</button>
|
| 658 |
-
</div>
|
| 659 |
-
<div className="p-8 rounded-3xl bg-white/[0.03] border border-white/10 font-serif text-slate-400 text-sm leading-relaxed whitespace-pre-wrap max-h-[500px] overflow-y-auto custom-scrollbar">
|
| 660 |
-
{activeAnalysis.proposal_draft}
|
| 661 |
-
</div>
|
| 662 |
-
</div>
|
| 663 |
-
)}
|
| 664 |
</div>
|
| 665 |
|
| 666 |
-
|
| 667 |
<div className="grid gap-6 md:grid-cols-2">
|
| 668 |
-
<div className="glass-card rounded-3xl p-8 bg-white/[0.01]">
|
| 669 |
<h4 className="text-[11px] font-bold uppercase tracking-widest text-amber-400 mb-6 flex items-center gap-2">
|
| 670 |
-
<span>⚠️</span>
|
| 671 |
</h4>
|
| 672 |
<ul className="space-y-4">
|
| 673 |
{activeAnalysis.compliance_gaps.map((gap, i) => (
|
| 674 |
-
<li key={i} className="flex gap-4 text-sm text-slate-400 leading-relaxed">
|
| 675 |
<span className="text-amber-500 font-bold">•</span> {gap}
|
| 676 |
</li>
|
| 677 |
))}
|
| 678 |
</ul>
|
| 679 |
</div>
|
| 680 |
-
<div className="glass-card rounded-3xl p-8 bg-white/[0.01]">
|
| 681 |
<h4 className="text-[11px] font-bold uppercase tracking-widest text-cyan mb-6 flex items-center gap-2">
|
| 682 |
-
<span>💎</span>
|
| 683 |
</h4>
|
| 684 |
<ul className="space-y-4">
|
| 685 |
{activeAnalysis.key_requirements.map((req, i) => (
|
| 686 |
-
<li key={i} className="flex gap-4 text-sm text-slate-400 leading-relaxed">
|
| 687 |
<span className="text-cyan font-bold">▹</span> {req}
|
| 688 |
</li>
|
| 689 |
))}
|
| 690 |
</ul>
|
| 691 |
</div>
|
| 692 |
</div>
|
| 693 |
-
|
| 694 |
-
<div className="glass-card rounded-3xl p-10 bg-white/[0.01]">
|
| 695 |
-
<h4 className="text-[11px] font-bold uppercase tracking-widest text-purple-400 mb-8 text-center">Neural Risk Matrix</h4>
|
| 696 |
-
<div className="grid gap-6 md:grid-cols-2 mb-12">
|
| 697 |
-
{activeAnalysis.risks.map((risk, i) => (
|
| 698 |
-
<div key={i} className="group rounded-3xl bg-white/[0.02] p-6 border border-white/5 hover:border-purple-500/30 transition-all duration-300">
|
| 699 |
-
<div className="flex items-center justify-between mb-4">
|
| 700 |
-
<span className="font-bold text-white text-lg group-hover:text-purple-400 transition">{risk.title}</span>
|
| 701 |
-
<span className={`text-[9px] font-black px-3 py-1 rounded-full uppercase tracking-widest ${risk.severity === 'High' ? 'bg-red-500/20 text-red-500 border border-red-500/20' : 'bg-white/5 text-slate-500 border border-white/5'}`}>{risk.severity}</span>
|
| 702 |
-
</div>
|
| 703 |
-
<p className="text-xs text-slate-500 leading-relaxed">{risk.explanation}</p>
|
| 704 |
-
</div>
|
| 705 |
-
))}
|
| 706 |
-
</div>
|
| 707 |
-
|
| 708 |
-
{activeAnalysis.strategic_roadmap && (
|
| 709 |
-
<div className="mt-8 pt-8 border-t border-white/5">
|
| 710 |
-
<h4 className="text-[11px] font-bold uppercase tracking-widest text-cyan mb-6 text-center">Winning Strategic Roadmap</h4>
|
| 711 |
-
<div className="p-8 rounded-3xl bg-cyan/5 border border-cyan/20 text-sm text-slate-300 leading-relaxed italic">
|
| 712 |
-
<div className="prose prose-invert prose-sm max-w-none">
|
| 713 |
-
{activeAnalysis.strategic_roadmap.split('\n').map((line, i) => (
|
| 714 |
-
<p key={i} className="mb-2">{line}</p>
|
| 715 |
-
))}
|
| 716 |
-
</div>
|
| 717 |
-
</div>
|
| 718 |
-
</div>
|
| 719 |
-
)}
|
| 720 |
-
</div>
|
| 721 |
-
|
| 722 |
</div>
|
| 723 |
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
<
|
|
|
|
| 729 |
</div>
|
| 730 |
-
<div className="space-y-
|
| 731 |
{activeAnalysis.audit_log?.map((log, i) => (
|
| 732 |
-
<div key={i} className="flex gap-
|
| 733 |
<div className="flex flex-col items-center">
|
| 734 |
-
<div className="h-
|
| 735 |
-
{i < (activeAnalysis.audit_log?.length ?? 0) - 1 && <div className="w-px flex-1 bg-
|
| 736 |
-
</div>
|
| 737 |
-
<div className="pb-6">
|
| 738 |
-
<p className="text-[13px] text-slate-400 leading-relaxed group-hover:text-white transition-colors duration-300">{log}</p>
|
| 739 |
</div>
|
|
|
|
| 740 |
</div>
|
| 741 |
))}
|
| 742 |
</div>
|
| 743 |
</div>
|
| 744 |
-
|
| 745 |
-
{/* Anexos Express Section */}
|
| 746 |
-
{generatedAnnexes.length > 0 && (
|
| 747 |
-
<div id="annexes-section" className="mt-8 glass-card rounded-3xl p-10 bg-purple-500/[0.03] border border-purple-500/20 animate-in fade-in slide-in-from-bottom-8 duration-700">
|
| 748 |
-
<div className="flex items-center gap-4 mb-8">
|
| 749 |
-
<div className="w-12 h-12 rounded-2xl bg-purple-500/20 flex items-center justify-center text-2xl shadow-lg shadow-purple-500/20">📄</div>
|
| 750 |
-
<div>
|
| 751 |
-
<h4 className="text-2xl font-black text-white tracking-tight">Compliance: Anexos Express</h4>
|
| 752 |
-
<p className="text-slate-500 text-sm">Official annexes pre-filled with company data and tender requirements.</p>
|
| 753 |
-
</div>
|
| 754 |
-
</div>
|
| 755 |
-
|
| 756 |
-
<div className="grid gap-6 md:grid-cols-1 lg:grid-cols-3">
|
| 757 |
-
{generatedAnnexes.map((annex, i) => (
|
| 758 |
-
<div key={i} className="group rounded-3xl bg-white/[0.02] border border-white/5 p-6 hover:border-purple-500/40 transition-all">
|
| 759 |
-
<div className="text-[10px] font-bold uppercase text-purple-400 mb-3 tracking-widest">Template Generated</div>
|
| 760 |
-
<h5 className="text-white font-bold mb-4 line-clamp-1">{annex.name}</h5>
|
| 761 |
-
<div className="bg-black/40 rounded-xl p-4 text-[9px] font-mono text-slate-500 mb-4 h-32 overflow-hidden relative">
|
| 762 |
-
<pre className="whitespace-pre-wrap">{annex.content}</pre>
|
| 763 |
-
<div className="absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-black/60 to-transparent" />
|
| 764 |
-
</div>
|
| 765 |
-
<button
|
| 766 |
-
onClick={() => {
|
| 767 |
-
const blob = new Blob([annex.content], { type: 'text/markdown' });
|
| 768 |
-
const url = window.URL.createObjectURL(blob);
|
| 769 |
-
const a = document.createElement('a');
|
| 770 |
-
a.href = url;
|
| 771 |
-
a.download = `${annex.name.replace(/ /g, '_')}.md`;
|
| 772 |
-
a.click();
|
| 773 |
-
}}
|
| 774 |
-
className="w-full py-2.5 rounded-xl bg-white/5 border border-white/10 text-[10px] font-bold text-slate-400 hover:text-white hover:bg-white/10 transition uppercase tracking-widest"
|
| 775 |
-
>
|
| 776 |
-
Download .md 📥
|
| 777 |
-
</button>
|
| 778 |
-
</div>
|
| 779 |
-
))}
|
| 780 |
-
</div>
|
| 781 |
-
</div>
|
| 782 |
-
)}
|
| 783 |
</div>
|
| 784 |
</div>
|
| 785 |
)}
|
| 786 |
|
| 787 |
-
{/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 788 |
{tender && (
|
| 789 |
<div className="mt-12 animate-in fade-in slide-in-from-bottom-8 duration-700">
|
| 790 |
-
<div className="flex items-center gap-4 mb-6 px-2">
|
| 791 |
<div className="w-10 h-10 rounded-2xl bg-purple-500/10 flex items-center justify-center text-xl shadow-lg shadow-purple-500/10">💬</div>
|
| 792 |
<div>
|
| 793 |
-
<h3 className="text-2xl font-black text-white tracking-tight">Expert Agent Consultation</h3>
|
| 794 |
-
<p className="text-slate-500 text-sm">Deep-dive into specific questions with our
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 795 |
</div>
|
| 796 |
</div>
|
| 797 |
<AgentChat tender={tender} companyProfile={companyProfile} />
|
|
|
|
| 256 |
) : null}
|
| 257 |
<span className="text-xs text-slate-500 font-mono">{tender?.code}</span>
|
| 258 |
</div>
|
| 259 |
+
<h2 className="text-3xl md:text-4xl font-bold text-white tracking-tight leading-tight mb-4">{tender?.name}</h2>
|
| 260 |
+
<p className="text-slate-400 text-base md:text-lg leading-relaxed">{tender?.buyer}</p>
|
| 261 |
|
| 262 |
{tender && (
|
| 263 |
+
<div className="mt-8 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
| 264 |
<div className="rounded-2xl bg-white/5 p-4 border border-white/5 group hover:bg-white/10 transition-colors">
|
| 265 |
<p className="text-[10px] uppercase text-slate-500 font-bold mb-1 tracking-widest">Investment</p>
|
| 266 |
<p className="text-lg font-black text-white">
|
|
|
|
| 345 |
|
| 346 |
{/* Scraped Intelligence / Tabs */}
|
| 347 |
{tenderDetails && (
|
| 348 |
+
<div className="mt-8 flex flex-wrap gap-3">
|
| 349 |
{tenderDetails.tabs?.history?.found && (
|
| 350 |
+
<div className="flex items-center gap-2 px-3 py-2 rounded-xl bg-white/5 border border-white/10 text-[9px] sm:text-[10px] font-bold text-slate-400">
|
| 351 |
<span className="text-purple-400 text-xs">📜</span> History Available
|
| 352 |
</div>
|
| 353 |
)}
|
| 354 |
+
{tenderDetails.metadata?.question_count && tenderDetails.metadata.question_count > 0 ? (
|
| 355 |
+
<a
|
| 356 |
+
href={`https://www.mercadopublico.cl/Procurement/Modules/RFB/DetailsAcquisition.aspx?codigo=${tender?.code}&tab=4`}
|
| 357 |
+
target="_blank"
|
| 358 |
+
rel="noopener noreferrer"
|
| 359 |
+
className="flex items-center gap-2 px-3 py-2 rounded-xl bg-cyan/10 border border-cyan/30 text-[9px] sm:text-[10px] font-bold text-cyan hover:bg-cyan/20 transition-all animate-pulse"
|
| 360 |
+
>
|
| 361 |
+
<span className="text-xs">❓</span> View {tenderDetails.metadata.question_count} Questions in Portal 🔗
|
| 362 |
+
</a>
|
| 363 |
+
) : (
|
| 364 |
+
tenderDetails.tabs?.questions?.found && (
|
| 365 |
+
<div className="flex items-center gap-2 px-3 py-2 rounded-xl bg-white/5 border border-white/10 text-[9px] sm:text-[10px] font-bold text-slate-400">
|
| 366 |
+
<span className="text-cyan text-xs">❓</span> Q&A Active
|
| 367 |
+
</div>
|
| 368 |
+
)
|
| 369 |
)}
|
| 370 |
{tenderDetails.tabs?.opening?.found && (
|
| 371 |
+
<div className="flex items-center gap-2 px-3 py-2 rounded-xl bg-white/5 border border-white/10 text-[9px] sm:text-[10px] font-bold text-slate-400">
|
| 372 |
<span className="text-green-400 text-xs">🔓</span> Opening Log Found
|
| 373 |
</div>
|
| 374 |
)}
|
| 375 |
{tenderDetails.metadata?.has_adjudication && (
|
| 376 |
+
<div className="flex items-center gap-2 px-3 py-2 rounded-xl bg-green-500/10 border border-green-500/20 text-[9px] sm:text-[10px] font-bold text-green-400">
|
| 377 |
<span className="text-xs">🏆</span> Adjudicated
|
| 378 |
</div>
|
| 379 |
)}
|
|
|
|
| 564 |
<p className="text-slate-400 mb-6">{error}</p>
|
| 565 |
<button
|
| 566 |
onClick={handleAnalyzeClick}
|
| 567 |
+
className="px-6 py-3 rounded-2xl bg-red-500/20 text-red-400 font-bold border b {/* Analysis Results & Intelligent Sections */}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 568 |
{activeAnalysis && (
|
| 569 |
+
<div id="analysis-results" className="grid gap-8 grid-cols-1 lg:grid-cols-12 animate-in fade-in slide-in-from-bottom-8 duration-500 scroll-mt-20">
|
| 570 |
<div className="lg:col-span-8 space-y-8">
|
| 571 |
+
{/* Main Analysis Card */}
|
| 572 |
+
<div className="glass-card rounded-3xl p-6 sm:p-10 bg-white/[0.02]">
|
| 573 |
+
{/* Professional Print Header */}
|
| 574 |
+
<div className="hidden print-only mb-12 border-b-4 border-slate-900 pb-8 text-center">
|
| 575 |
+
<h1 className="text-4xl font-black text-slate-900 mb-2">ANDESOPS AI</h1>
|
| 576 |
+
<p className="text-sm font-bold uppercase tracking-[0.5em] text-slate-500">Intelligent Bidding Analysis Report</p>
|
| 577 |
+
</div>
|
| 578 |
+
|
| 579 |
+
<div className="flex flex-col sm:flex-row items-start justify-between gap-6 mb-8">
|
|
|
|
|
|
|
|
|
|
|
|
|
| 580 |
<div>
|
| 581 |
<div className="text-[11px] font-bold uppercase tracking-[0.3em] text-purple-400 mb-2">Agent Consensus</div>
|
| 582 |
+
<h3 className="text-4xl sm:text-6xl font-black text-white">{activeAnalysis.fit_score}% <span className="text-xl sm:text-2xl font-light text-slate-500">Fit Score</span></h3>
|
| 583 |
<div className="mt-2 flex items-center gap-2">
|
| 584 |
<span className="text-[10px] text-slate-500 font-mono">Analyzing:</span>
|
| 585 |
+
<span className="text-[10px] text-purple-300 font-bold truncate max-w-[200px]">{corral.find(a => a.id === activeAnimalId)?.file.name || tender?.name}</span>
|
| 586 |
</div>
|
| 587 |
</div>
|
| 588 |
+
<div className="flex flex-col items-end gap-3 w-full sm:w-auto">
|
| 589 |
+
<div className={`w-full sm:w-auto text-center rounded-2xl px-6 py-3 text-[10px] font-black uppercase tracking-widest shadow-lg ${activeAnalysis.decision === 'Recommended' ? 'bg-green-500/20 text-green-400 border border-green-500/30 shadow-green-500/10' : 'bg-amber-500/20 text-amber-400 border border-amber-500/30 shadow-amber-500/10'}`}>
|
| 590 |
{activeAnalysis.decision}
|
| 591 |
</div>
|
| 592 |
+
<div className="flex flex-wrap gap-2 w-full sm:w-auto justify-end">
|
| 593 |
<button
|
| 594 |
onClick={() => window.print()}
|
| 595 |
+
className="flex-1 sm:flex-none px-4 py-2 rounded-xl bg-white/5 border border-white/10 text-[9px] font-bold text-slate-400 hover:text-white hover:bg-white/10 transition uppercase tracking-widest"
|
| 596 |
>
|
| 597 |
Export PDF
|
| 598 |
</button>
|
| 599 |
<button
|
| 600 |
onClick={generateAnnexes}
|
| 601 |
disabled={isGeneratingAnnexes}
|
| 602 |
+
className={`flex-1 sm:flex-none px-4 py-2 rounded-xl border text-[9px] font-bold transition uppercase tracking-widest ${isGeneratingAnnexes ? 'bg-purple-500/20 border-purple-500/50 text-purple-300 animate-pulse' : 'bg-purple-500/10 border-purple-500/20 text-purple-400 hover:bg-purple-500/20'}`}
|
| 603 |
>
|
| 604 |
+
{isGeneratingAnnexes ? 'Generating...' : '✨ Anexos'}
|
| 605 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 606 |
</div>
|
| 607 |
</div>
|
| 608 |
</div>
|
| 609 |
<div className="prose prose-invert max-w-none">
|
| 610 |
+
<p className="text-slate-300 text-lg sm:text-xl leading-relaxed italic border-l-4 border-purple-500 pl-4 sm:pl-8">{activeAnalysis.executive_summary}</p>
|
| 611 |
</div>
|
| 612 |
|
| 613 |
{/* Requirement Q&A Section */}
|
|
|
|
| 615 |
<div className="mt-12 space-y-6">
|
| 616 |
<div className="flex items-center gap-3 border-b border-white/5 pb-4">
|
| 617 |
<span className="text-2xl">📋</span>
|
| 618 |
+
<h4 className="text-[10px] sm:text-[11px] font-bold uppercase tracking-widest text-purple-400">Requirement Response</h4>
|
| 619 |
</div>
|
| 620 |
<div className="grid gap-4">
|
| 621 |
{activeAnalysis.requirement_responses.map((item, i) => (
|
| 622 |
+
<div key={i} className="rounded-2xl bg-white/[0.03] border border-white/5 p-4 sm:p-6 hover:border-purple-500/30 transition-all group">
|
| 623 |
<div className="flex gap-4">
|
| 624 |
<span className="text-purple-500 font-bold font-mono">Q.</span>
|
| 625 |
+
<p className="text-white font-semibold text-xs sm:text-sm">{item.question}</p>
|
| 626 |
</div>
|
| 627 |
+
<div className="mt-4 flex gap-4 pl-4 sm:pl-8 border-l border-white/10">
|
| 628 |
<span className="text-green-400 font-bold font-mono">A.</span>
|
| 629 |
+
<p className="text-slate-400 text-xs sm:text-sm leading-relaxed">{item.answer}</p>
|
| 630 |
</div>
|
| 631 |
</div>
|
| 632 |
))}
|
| 633 |
</div>
|
| 634 |
</div>
|
| 635 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 636 |
</div>
|
| 637 |
|
|
|
|
| 638 |
<div className="grid gap-6 md:grid-cols-2">
|
| 639 |
+
<div className="glass-card rounded-3xl p-6 sm:p-8 bg-white/[0.01]">
|
| 640 |
<h4 className="text-[11px] font-bold uppercase tracking-widest text-amber-400 mb-6 flex items-center gap-2">
|
| 641 |
+
<span>⚠️</span> Compliance Gaps
|
| 642 |
</h4>
|
| 643 |
<ul className="space-y-4">
|
| 644 |
{activeAnalysis.compliance_gaps.map((gap, i) => (
|
| 645 |
+
<li key={i} className="flex gap-4 text-xs sm:text-sm text-slate-400 leading-relaxed">
|
| 646 |
<span className="text-amber-500 font-bold">•</span> {gap}
|
| 647 |
</li>
|
| 648 |
))}
|
| 649 |
</ul>
|
| 650 |
</div>
|
| 651 |
+
<div className="glass-card rounded-3xl p-6 sm:p-8 bg-white/[0.01]">
|
| 652 |
<h4 className="text-[11px] font-bold uppercase tracking-widest text-cyan mb-6 flex items-center gap-2">
|
| 653 |
+
<span>💎</span> Tech Requirements
|
| 654 |
</h4>
|
| 655 |
<ul className="space-y-4">
|
| 656 |
{activeAnalysis.key_requirements.map((req, i) => (
|
| 657 |
+
<li key={i} className="flex gap-4 text-xs sm:text-sm text-slate-400 leading-relaxed">
|
| 658 |
<span className="text-cyan font-bold">▹</span> {req}
|
| 659 |
</li>
|
| 660 |
))}
|
| 661 |
</ul>
|
| 662 |
</div>
|
| 663 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 664 |
</div>
|
| 665 |
|
| 666 |
+
{/* Audit Log / Agent Thoughts Sticky Column */}
|
| 667 |
+
<div className="lg:col-span-4 space-y-8">
|
| 668 |
+
<div className="glass-card rounded-3xl p-6 sm:p-8 bg-black/40 lg:sticky lg:top-32 max-h-[500px] lg:max-h-[700px] overflow-hidden flex flex-col">
|
| 669 |
+
<div className="flex items-center gap-3 mb-6 border-b border-white/5 pb-4">
|
| 670 |
+
<div className="h-2 w-2 rounded-full bg-purple-500 animate-pulse" />
|
| 671 |
+
<h4 className="text-[10px] font-bold uppercase tracking-widest text-slate-400">Agent Intel Log</h4>
|
| 672 |
</div>
|
| 673 |
+
<div className="space-y-6 overflow-y-auto pr-2 custom-scrollbar">
|
| 674 |
{activeAnalysis.audit_log?.map((log, i) => (
|
| 675 |
+
<div key={i} className="flex gap-4 group">
|
| 676 |
<div className="flex flex-col items-center">
|
| 677 |
+
<div className="h-6 w-6 rounded-lg bg-white/5 flex items-center justify-center text-xs border border-white/10 group-hover:border-purple-500/50 transition-all">🤖</div>
|
| 678 |
+
{i < (activeAnalysis.audit_log?.length ?? 0) - 1 && <div className="w-px flex-1 bg-white/5 my-2" />}
|
|
|
|
|
|
|
|
|
|
| 679 |
</div>
|
| 680 |
+
<p className="text-xs text-slate-500 leading-relaxed group-hover:text-slate-300 transition-colors">{log}</p>
|
| 681 |
</div>
|
| 682 |
))}
|
| 683 |
</div>
|
| 684 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 685 |
</div>
|
| 686 |
</div>
|
| 687 |
)}
|
| 688 |
|
| 689 |
+
{/* Compliance Anexos Section (Moved to prevent overlap with Chat) */}
|
| 690 |
+
{generatedAnnexes.length > 0 && (
|
| 691 |
+
<div id="annexes-section" className="glass-card rounded-3xl p-6 sm:p-10 bg-purple-500/[0.03] border border-purple-500/20 animate-in fade-in slide-in-from-bottom-8 duration-700">
|
| 692 |
+
<div className="flex flex-col sm:flex-row sm:items-center gap-4 mb-8">
|
| 693 |
+
<div className="w-12 h-12 rounded-2xl bg-purple-500/20 flex items-center justify-center text-2xl shadow-lg shadow-purple-500/20">📄</div>
|
| 694 |
+
<div>
|
| 695 |
+
<h4 className="text-xl sm:text-2xl font-black text-white tracking-tight">Compliance: Anexos Express</h4>
|
| 696 |
+
<p className="text-slate-500 text-xs sm:text-sm">Official annexes pre-filled with company data.</p>
|
| 697 |
+
</div>
|
| 698 |
+
</div>
|
| 699 |
+
|
| 700 |
+
<div className="grid gap-6 grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
|
| 701 |
+
{generatedAnnexes.map((annex, i) => (
|
| 702 |
+
<div key={i} className="group rounded-3xl bg-white/[0.02] border border-white/5 p-6 hover:border-purple-500/40 transition-all">
|
| 703 |
+
<div className="text-[10px] font-bold uppercase text-purple-400 mb-3 tracking-widest">Template</div>
|
| 704 |
+
<h5 className="text-white font-bold mb-4 line-clamp-1 text-sm">{annex.name}</h5>
|
| 705 |
+
<div className="bg-black/40 rounded-xl p-4 text-[9px] font-mono text-slate-500 mb-4 h-32 overflow-hidden relative">
|
| 706 |
+
<pre className="whitespace-pre-wrap">{annex.content}</pre>
|
| 707 |
+
<div className="absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-black/60 to-transparent" />
|
| 708 |
+
</div>
|
| 709 |
+
<button
|
| 710 |
+
onClick={() => {
|
| 711 |
+
const blob = new Blob([annex.content], { type: 'text/markdown' });
|
| 712 |
+
const url = window.URL.createObjectURL(blob);
|
| 713 |
+
const a = document.createElement('a');
|
| 714 |
+
a.href = url;
|
| 715 |
+
a.download = `${annex.name.replace(/ /g, '_')}.md`;
|
| 716 |
+
a.click();
|
| 717 |
+
}}
|
| 718 |
+
className="w-full py-2.5 rounded-xl bg-white/5 border border-white/10 text-[9px] font-bold text-slate-400 hover:text-white hover:bg-white/10 transition uppercase tracking-widest"
|
| 719 |
+
>
|
| 720 |
+
Download .md 📥
|
| 721 |
+
</button>
|
| 722 |
+
</div>
|
| 723 |
+
))}
|
| 724 |
+
</div>
|
| 725 |
+
</div>
|
| 726 |
+
)}
|
| 727 |
+
|
| 728 |
+
{/* Expert Consultation Chat (Bottom Section) */}
|
| 729 |
{tender && (
|
| 730 |
<div className="mt-12 animate-in fade-in slide-in-from-bottom-8 duration-700">
|
| 731 |
+
<div className="flex flex-col sm:flex-row sm:items-center gap-4 mb-6 px-2">
|
| 732 |
<div className="w-10 h-10 rounded-2xl bg-purple-500/10 flex items-center justify-center text-xl shadow-lg shadow-purple-500/10">💬</div>
|
| 733 |
<div>
|
| 734 |
+
<h3 className="text-xl sm:text-2xl font-black text-white tracking-tight">Expert Agent Consultation</h3>
|
| 735 |
+
<p className="text-slate-500 text-xs sm:text-sm">Deep-dive into specific questions with our AI agents.</p>
|
| 736 |
+
</div>
|
| 737 |
+
</div>
|
| 738 |
+
<AgentChat tender={tender} companyProfile={companyProfile} />
|
| 739 |
+
</div>
|
| 740 |
+
)}
|
| 741 |
+
</div>
|
| 742 |
+
ons with our specialized AI agents.</p>
|
| 743 |
</div>
|
| 744 |
</div>
|
| 745 |
<AgentChat tender={tender} companyProfile={companyProfile} />
|
frontend/components/MarketMonitor.tsx
CHANGED
|
@@ -118,37 +118,40 @@ export default function MarketMonitor() {
|
|
| 118 |
<table className="w-full text-left text-xs border-collapse sticky-header">
|
| 119 |
<thead className="sticky top-0 z-10">
|
| 120 |
<tr className="bg-slate-900/95 backdrop-blur-md text-slate-500 uppercase font-black tracking-tighter border-b border-white/5">
|
| 121 |
-
<th className="px-6 py-5">Order ID / Description</th>
|
| 122 |
-
<th className="px-6 py-5">Buyer</th>
|
| 123 |
-
<th className="px-6 py-5">Vendor</th>
|
| 124 |
-
<th className="px-6 py-5 text-right">Total
|
| 125 |
</tr>
|
| 126 |
</thead>
|
| 127 |
<tbody className="divide-y divide-white/5">
|
| 128 |
{paginatedOcs.map((oc) => (
|
| 129 |
<tr key={oc.code} className="hover:bg-white/[0.03] transition-colors group">
|
| 130 |
-
<td className="px-6 py-5 max-w-md">
|
| 131 |
<div className="flex items-center gap-2 mb-1">
|
| 132 |
-
<span className="text-cyan font-bold font-mono text-[
|
| 133 |
{oc.code}
|
| 134 |
</span>
|
| 135 |
</div>
|
| 136 |
-
<div className="text-white font-bold line-clamp-1 group-hover:line-clamp-none transition-all cursor-help" title={oc.name}>
|
| 137 |
{oc.name || "Orden de Compra"}
|
| 138 |
</div>
|
|
|
|
|
|
|
|
|
|
| 139 |
</td>
|
| 140 |
-
<td className="px-6 py-5">
|
| 141 |
-
<div className="text-slate-300 font-medium truncate max-w-[150px]">
|
| 142 |
{oc.buyer !== "Unknown" ? oc.buyer : <span className="opacity-30">...</span>}
|
| 143 |
</div>
|
| 144 |
</td>
|
| 145 |
-
<td className="px-6 py-5">
|
| 146 |
-
<div className="text-sky-400 font-bold truncate max-w-[150px]">
|
| 147 |
{oc.provider !== "Unknown" ? oc.provider : <span className="opacity-30">...</span>}
|
| 148 |
</div>
|
| 149 |
</td>
|
| 150 |
-
<td className="px-6 py-5 text-right">
|
| 151 |
-
<div className="text-white font-black text-sm">
|
| 152 |
{formatCurrency(oc.total_amount, oc.currency)}
|
| 153 |
</div>
|
| 154 |
</td>
|
|
|
|
| 118 |
<table className="w-full text-left text-xs border-collapse sticky-header">
|
| 119 |
<thead className="sticky top-0 z-10">
|
| 120 |
<tr className="bg-slate-900/95 backdrop-blur-md text-slate-500 uppercase font-black tracking-tighter border-b border-white/5">
|
| 121 |
+
<th className="px-4 sm:px-6 py-5">Order ID / Description</th>
|
| 122 |
+
<th className="px-6 py-5 hidden md:table-cell">Buyer</th>
|
| 123 |
+
<th className="px-6 py-5 hidden lg:table-cell">Vendor</th>
|
| 124 |
+
<th className="px-4 sm:px-6 py-5 text-right">Total</th>
|
| 125 |
</tr>
|
| 126 |
</thead>
|
| 127 |
<tbody className="divide-y divide-white/5">
|
| 128 |
{paginatedOcs.map((oc) => (
|
| 129 |
<tr key={oc.code} className="hover:bg-white/[0.03] transition-colors group">
|
| 130 |
+
<td className="px-4 sm:px-6 py-5 max-w-md">
|
| 131 |
<div className="flex items-center gap-2 mb-1">
|
| 132 |
+
<span className="text-cyan font-bold font-mono text-[9px] bg-cyan/5 px-2 py-0.5 rounded border border-cyan/10">
|
| 133 |
{oc.code}
|
| 134 |
</span>
|
| 135 |
</div>
|
| 136 |
+
<div className="text-white font-bold line-clamp-1 group-hover:line-clamp-none transition-all cursor-help text-xs sm:text-sm" title={oc.name}>
|
| 137 |
{oc.name || "Orden de Compra"}
|
| 138 |
</div>
|
| 139 |
+
<div className="md:hidden text-[10px] text-slate-500 mt-1 truncate max-w-[200px]">
|
| 140 |
+
{oc.buyer}
|
| 141 |
+
</div>
|
| 142 |
</td>
|
| 143 |
+
<td className="px-6 py-5 hidden md:table-cell">
|
| 144 |
+
<div className="text-slate-300 font-medium truncate max-w-[150px] text-[11px]">
|
| 145 |
{oc.buyer !== "Unknown" ? oc.buyer : <span className="opacity-30">...</span>}
|
| 146 |
</div>
|
| 147 |
</td>
|
| 148 |
+
<td className="px-6 py-5 hidden lg:table-cell">
|
| 149 |
+
<div className="text-sky-400 font-bold truncate max-w-[150px] text-[11px]">
|
| 150 |
{oc.provider !== "Unknown" ? oc.provider : <span className="opacity-30">...</span>}
|
| 151 |
</div>
|
| 152 |
</td>
|
| 153 |
+
<td className="px-4 sm:px-6 py-5 text-right">
|
| 154 |
+
<div className="text-white font-black text-xs sm:text-sm">
|
| 155 |
{formatCurrency(oc.total_amount, oc.currency)}
|
| 156 |
</div>
|
| 157 |
</td>
|
frontend/components/Sidebar.tsx
CHANGED
|
@@ -47,7 +47,7 @@ export default function Sidebar({ tabs, activeTab, onTabSelect, status, lang, fo
|
|
| 47 |
<aside
|
| 48 |
onMouseEnter={() => setIsHovered(true)}
|
| 49 |
onMouseLeave={() => setIsHovered(false)}
|
| 50 |
-
className={`glass-card rounded-3xl h-[calc(100vh-3rem)] sticky top-6 p-4 flex flex-col gap-8 transition-all duration-500 ease-in-out z-50 border-white/10 ${isExpanded ? 'w-72 shadow-2xl shadow-purple-500/10 bg-black/60' : 'w-[84px] shadow-none border-white/5 bg-white/[0.02]'}`}
|
| 51 |
>
|
| 52 |
<div className={`flex items-center gap-3 px-2 transition-all duration-300 ${isExpanded ? 'justify-start' : 'justify-center'}`}>
|
| 53 |
<div className="w-10 h-10 premium-gradient rounded-xl flex-shrink-0 flex items-center justify-center shadow-lg shadow-purple-500/20">
|
|
|
|
| 47 |
<aside
|
| 48 |
onMouseEnter={() => setIsHovered(true)}
|
| 49 |
onMouseLeave={() => setIsHovered(false)}
|
| 50 |
+
className={`glass-card rounded-2xl md:rounded-3xl h-full md:h-[calc(100vh-3rem)] md:sticky md:top-6 p-3 md:p-4 flex flex-col gap-4 md:gap-8 transition-all duration-500 ease-in-out z-50 border-white/10 ${isExpanded ? 'w-full md:w-72 shadow-2xl shadow-purple-500/10 bg-black/60' : 'w-[84px] shadow-none border-white/5 bg-white/[0.02]'}`}
|
| 51 |
>
|
| 52 |
<div className={`flex items-center gap-3 px-2 transition-all duration-300 ${isExpanded ? 'justify-start' : 'justify-center'}`}>
|
| 53 |
<div className="w-10 h-10 premium-gradient rounded-xl flex-shrink-0 flex items-center justify-center shadow-lg shadow-purple-500/20">
|
frontend/components/TenderSearch.tsx
CHANGED
|
@@ -126,7 +126,7 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
|
|
| 126 |
</div>
|
| 127 |
|
| 128 |
{!forceShowFollowed && (
|
| 129 |
-
|
| 130 |
<div className="flex flex-col md:flex-row gap-4">
|
| 131 |
<div className="relative flex-1">
|
| 132 |
<input
|
|
@@ -159,43 +159,44 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
|
|
| 159 |
<table className="w-full text-left text-sm table-fixed">
|
| 160 |
<thead className="bg-white/5 text-slate-500 uppercase text-[10px] font-bold border-b border-white/5">
|
| 161 |
<tr>
|
| 162 |
-
<th className="px-
|
| 163 |
-
<th className="px-6 py-5 w-[
|
| 164 |
-
<th className="px-6 py-5">Opportunity</th>
|
| 165 |
-
<th className="px-6 py-5 w-[
|
| 166 |
-
<th className="px-6 py-5 text-center w-[
|
| 167 |
-
<th className="px-6 py-5 w-[
|
| 168 |
</tr>
|
| 169 |
</thead>
|
| 170 |
<tbody className="divide-y divide-white/5">
|
| 171 |
{filteredTenders.map((item) => (
|
| 172 |
<tr key={item.code} className="hover:bg-white/[0.04] transition-colors group">
|
| 173 |
-
<td className="px-
|
| 174 |
<button
|
| 175 |
onPointerDown={(e) => {
|
| 176 |
e.stopPropagation();
|
| 177 |
-
console.log("[Portfolio] Toggling follow for:", item.code);
|
| 178 |
toggleFollow(item);
|
| 179 |
}}
|
| 180 |
-
className={`text-
|
| 181 |
>
|
| 182 |
{followedCodes.includes(item.code) ? "★" : "☆"}
|
| 183 |
</button>
|
| 184 |
</td>
|
| 185 |
-
<td className="px-6 py-5 font-mono text-purple-400 text-[
|
| 186 |
<td className="px-6 py-5">
|
| 187 |
-
<div className="font-bold text-white
|
|
|
|
| 188 |
</td>
|
| 189 |
-
<td className="px-6 py-5 text-slate-400 text-[
|
| 190 |
-
<td className="px-6 py-5 text-center">
|
| 191 |
-
<span className={`inline-block rounded-full px-
|
| 192 |
</td>
|
| 193 |
-
<td className="px-
|
| 194 |
<button
|
| 195 |
onClick={() => setSelectedTenderForModal(item)}
|
| 196 |
-
className="px-4 py-2 rounded-xl bg-purple-500/10 border border-purple-500/20 text-[
|
| 197 |
>
|
| 198 |
-
View
|
|
|
|
| 199 |
</button>
|
| 200 |
</td>
|
| 201 |
</tr>
|
|
|
|
| 126 |
</div>
|
| 127 |
|
| 128 |
{!forceShowFollowed && (
|
| 129 |
+
<form onSubmit={handleSearch} className="relative z-10 space-y-6">
|
| 130 |
<div className="flex flex-col md:flex-row gap-4">
|
| 131 |
<div className="relative flex-1">
|
| 132 |
<input
|
|
|
|
| 159 |
<table className="w-full text-left text-sm table-fixed">
|
| 160 |
<thead className="bg-white/5 text-slate-500 uppercase text-[10px] font-bold border-b border-white/5">
|
| 161 |
<tr>
|
| 162 |
+
<th className="px-4 py-5 w-[50px] text-center"></th>
|
| 163 |
+
<th className="px-6 py-5 w-[110px] hidden sm:table-cell">ID</th>
|
| 164 |
+
<th className="px-6 py-5 min-w-[200px]">Opportunity</th>
|
| 165 |
+
<th className="px-6 py-5 w-[150px] hidden md:table-cell">Buyer</th>
|
| 166 |
+
<th className="px-6 py-5 text-center w-[100px] hidden sm:table-cell">Status</th>
|
| 167 |
+
<th className="px-6 py-5 w-[80px] text-center">Action</th>
|
| 168 |
</tr>
|
| 169 |
</thead>
|
| 170 |
<tbody className="divide-y divide-white/5">
|
| 171 |
{filteredTenders.map((item) => (
|
| 172 |
<tr key={item.code} className="hover:bg-white/[0.04] transition-colors group">
|
| 173 |
+
<td className="px-2 py-5 text-center">
|
| 174 |
<button
|
| 175 |
onPointerDown={(e) => {
|
| 176 |
e.stopPropagation();
|
|
|
|
| 177 |
toggleFollow(item);
|
| 178 |
}}
|
| 179 |
+
className={`text-lg transition-all hover:scale-125 active:scale-90 p-2 cursor-pointer relative z-50 ${followedCodes.includes(item.code) ? 'text-amber-400 drop-shadow-[0_0_10px_rgba(251,191,36,0.6)]' : 'text-white/30 hover:text-white/60'}`}
|
| 180 |
>
|
| 181 |
{followedCodes.includes(item.code) ? "★" : "☆"}
|
| 182 |
</button>
|
| 183 |
</td>
|
| 184 |
+
<td className="px-6 py-5 font-mono text-purple-400 text-[9px] hidden sm:table-cell">{item.code}</td>
|
| 185 |
<td className="px-6 py-5">
|
| 186 |
+
<div className="font-bold text-white text-xs sm:text-sm line-clamp-2" title={item.name}>{item.name}</div>
|
| 187 |
+
<div className="text-[10px] text-slate-500 mt-1 md:hidden truncate max-w-[150px]">{item.buyer}</div>
|
| 188 |
</td>
|
| 189 |
+
<td className="px-6 py-5 text-slate-400 text-[10px] truncate hidden md:table-cell">{item.buyer}</td>
|
| 190 |
+
<td className="px-6 py-5 text-center hidden sm:table-cell">
|
| 191 |
+
<span className={`inline-block rounded-full px-2 py-1 text-[8px] font-black uppercase ${item.status.toLowerCase().includes('publicada') ? 'bg-green-500/10 text-green-400' : 'bg-slate-800 text-slate-500'}`}>{item.status}</span>
|
| 192 |
</td>
|
| 193 |
+
<td className="px-4 py-5 text-center">
|
| 194 |
<button
|
| 195 |
onClick={() => setSelectedTenderForModal(item)}
|
| 196 |
+
className="p-2 sm:px-4 sm:py-2 rounded-xl bg-purple-500/10 border border-purple-500/20 text-[9px] font-black text-purple-400 hover:bg-purple-500 hover:text-white transition-all uppercase tracking-widest"
|
| 197 |
>
|
| 198 |
+
<span className="hidden sm:inline">View</span>
|
| 199 |
+
<span className="sm:hidden">→</span>
|
| 200 |
</button>
|
| 201 |
</td>
|
| 202 |
</tr>
|
frontend/globals.css
CHANGED
|
@@ -178,3 +178,18 @@ select {
|
|
| 178 |
border-color: #e2e8f0 !important;
|
| 179 |
}
|
| 180 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
border-color: #e2e8f0 !important;
|
| 179 |
}
|
| 180 |
}
|
| 181 |
+
|
| 182 |
+
@media (max-width: 640px) {
|
| 183 |
+
.glass-card {
|
| 184 |
+
padding: 1.25rem !important;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
h2 {
|
| 188 |
+
font-size: 1.5rem !important;
|
| 189 |
+
line-height: 2rem !important;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
.premium-gradient {
|
| 193 |
+
padding: 1rem !important;
|
| 194 |
+
}
|
| 195 |
+
}
|