Introduction
The AI Tender Opportunity Scanner can now:
- Ingest tenders
- Analyze opportunities with AI
- Extract industries, technologies, skills, and requirements
- Calculate personalized match scores
- Generate recommendations
- Display ranked opportunities in the React dashboard
Users can now see which tenders are relevant.
However, before deciding whether to pursue an opportunity, users need more detail.
A recommendation card answers:
Why should I look at this tender?
The Tender Detail Page answers:
Should I actually bid on this opportunity?
This page becomes the primary decision-making interface of the platform.
Users will be able to:
- Read the AI-generated summary
- Review detailed match explanations
- Understand score breakdowns
- Explore technologies and skills
- Analyze requirements
- Identify capability gaps
- Review procurement deadlines
- Open the original tender source
- Save or dismiss opportunities
This transforms recommendations into actionable business decisions.
Development Progress
Project Scope and Roadmap ████████████████████ 100%MVP Definition ████████████████████ 100%Technology Stack ████████████████████ 100%System Architecture ████████████████████ 100%Database Design ████████████████████ 100%Backend Project Structure ████████████████████ 100%SQLAlchemy Models ████████████████████ 100%Alembic Migration ████████████████████ 100%Repository Layer ████████████████████ 100%CRUD Service Layer ████████████████████ 100%Tender Ingestion Pipeline ████████████████████ 100%AI Analysis Pipeline ████████████████████ 100%AI Matching Engine ████████████████████ 100%React Dashboard ████████████████████ 100%Tender Detail Page ████████████████████ 100%Saved Opportunities ░░░░░░░░░░░░░░░░░░░░ 0%
What We Are Building
The detail page will look like:
-------------------------------------------------Azure Cloud Migration Services-------------------------------------------------92% MatchPublic SectorNetherlandsContract Value: €250,000Deadline: 21 Days-------------------------------------------------AI Summary-------------------------------------------------This tender seeks a supplier to migratemunicipal infrastructure to Microsoft Azure.-------------------------------------------------Why This Matches-------------------------------------------------✓ Azure Expertise✓ Cloud Migration✓ Public Sector Experience-------------------------------------------------Score Breakdown-------------------------------------------------Industry 100%Technology 80%Skills 90%Commercial Fit 100%Location 100%-------------------------------------------------Technologies-------------------------------------------------AzureDockerKubernetes-------------------------------------------------Required Skills-------------------------------------------------Cloud MigrationSolution ArchitectureSecurity Assessment-------------------------------------------------Missing Capabilities-------------------------------------------------Security Assessment-------------------------------------------------[Open Source Tender][Save][Dismiss]-------------------------------------------------
Why Create a Detail Page?
The recommendation dashboard answers:
What should I investigate?
The detail page answers:
Should I invest time and money into this opportunity?
This distinction is important.
Users often spend:
HoursDaysWeeks
preparing tender responses.
The detail page helps determine whether the opportunity deserves that investment.
Detail Page Architecture
React Detail Page | vTender Detail API | +---- Tender +---- AI Analysis +---- Match Result | vPostgreSQL
The frontend should never reconstruct match logic.
The backend delivers a complete view model.
Creating the Detail Page Route
Create:
src/pages/TenderDetailPage.jsx
Configure React Router:
<Route path="/tenders/:id" element={ <TenderDetailPage /> }/>
Users can now navigate to:
/tenders/123
Why Use Route Parameters?
Each tender has a unique identifier.
Example:
Tender ID: 123
React Router allows:
const { id } = useParams();
This makes the page reusable.
Creating the Tender Service
Create:
src/services/tenderService.js
Add:
import axios from "axios";const API_URL = "http://localhost:8000";
export const getTenderDetail = async ( tenderId, profileId ) => { const response = await axios.get( `${API_URL}/tenders/${tenderId}`, { params: { profile_id: profileId } } ); return response.data;};
Why Create a Dedicated Endpoint?
Bad:
Frontend calls:5 APIs
Examples:
TenderAnalysisMatchSummarySkills
Good:
One API
Benefits:
- Faster page load
- Simpler frontend
- Reduced network traffic
Backend Detail Endpoint
Create:
GET/tenders/{id}
Return:
{ "id": 123, "title": "Azure Cloud Migration", "summary": "...", "overall_score": 92, "score_breakdown": {}, "technologies": [], "skills": [], "requirements": [], "missing_capabilities": [], "match_reasons": [], "source_url": "..."}
This becomes the page contract.
Creating a Custom Hook
Create:
src/hooks/useTenderDetail.js
import { useEffect, useState}from "react";
import { getTenderDetail}from "../services/tenderService";
Hook:
export const useTenderDetail =( tenderId, profileId) => { const [ tender, setTender ] = useState(null); const [ loading, setLoading ] = useState(true); useEffect(() => { const load = async () => { try { const result = await getTenderDetail( tenderId, profileId ); setTender( result ); } finally { setLoading( false ); } }; load(); }, [ tenderId, profileId ]); return { tender, loading };};
Building the Page Skeleton
Inside:
TenderDetailPage.jsx
Load:
const { id } = useParams();const { tender, loading} = useTenderDetail( id, 1 );
Display:
if (loading) return ( <p> Loading... </p> );if (!tender) return ( <p> Tender not found. </p> );
Creating the Layout
Recommended layout:
HeaderSummaryMatch AnalysisScore BreakdownTechnologiesSkillsRequirementsMissing CapabilitiesActions
This follows the decision-making process.
Creating the Header Component
Create:
src/components/TenderDetail/TenderHeader.jsx
Props:
function TenderHeader({ tender})
Display:
<h1> {tender.title}</h1>
And:
<p> {tender.country}</p>
<p> {tender.contract_value}</p>
<p> {tender.deadline}</p>
Displaying the Match Score
Reuse:
MatchScore.jsx
Example:
<MatchScore score={ tender.overall_score }/>
This keeps UI consistent.
Why Reuse Components?
Benefits:
ConsistencyLess codeFaster developmentEasier maintenance
Avoid creating multiple score displays.
Creating the AI Summary Section
Create:
TenderSummary.jsx
Display:
<section><h2> AI Summary</h2><p> {tender.summary}</p></section>
Why AI Summaries Matter
Many procurement documents contain:
10+20+50+pages
Users need a quick understanding before diving deeper.
The summary provides that.
Creating Match Explanation Section
Create:
MatchReasons.jsx
Reuse from dashboard.
Display:
Why This Matches
Example:
✓ Azure expertise✓ Cloud migration✓ Public sector experience
Why Show Match Reasons?
Users often ask:
Why is this 92%?
Providing reasons builds trust.
Creating Score Breakdown Section
Create:
ScoreBreakdown.jsx
Display:
Object.entries( tender.score_breakdown)
Example:
Industry 100%Technology 80%Skills 90%Commercial Fit 100%Location 100%
Why Show Score Components?
Overall scores can hide weaknesses.
Example:
Overall Score:92%
But:
Technology:50%
This insight matters.
Creating Technology Tags
Create:
TechnologyList.jsx
Render:
tender.technologies
Example:
AzureDockerKubernetes
Tag style:
.tag { padding: 8px; border-radius: 20px;}
Why Display Technologies?
Many consultancies specialize in:
AzureAWSSalesforceSAPKubernetes
Technology alignment is often the strongest signal.
Creating Skills Section
Create:
SkillsList.jsx
Render:
tender.skills
Example:
Cloud MigrationSolution ArchitectureSecurity Assessment
Why Display Skills?
Technology alone does not describe project complexity.
Skills show:
What expertise is required
Creating Requirements Section
Create:
RequirementsList.jsx
Render:
tender.requirements
Example:
• Migrate workloads• Create Azure landing zone• Security documentation
Why Show Requirements?
Requirements reveal:
Project scopeProject riskResource needs
Users often make bid decisions here.
Creating Missing Capabilities Section
Create:
MissingCapabilities.jsx
Display:
tender .missing_capabilities
Example:
Missing:• Security Assessment• Kubernetes
Why Show Capability Gaps?
A missing capability does not automatically disqualify a tender.
It may indicate:
Need for a partnerNeed for a subcontractorNeed for additional training
This information supports planning.
Creating Action Buttons
Create:
TenderActions.jsx
Buttons:
<button> Save</button><button> Dismiss</button><button> Open Source</button>
Save Action
API:
POST/saved-tenders
Payload:
{ "tender_id": 123}
Dismiss Action
API:
POST/dismissed-tenders
Payload:
{ "tender_id": 123}
Open Source Tender
Display:
<a href={ tender.source_url } target="_blank"> Open Original Tender</a>
This links back to the procurement portal.
Why Keep Source Links?
Users eventually need:
Official documentsSubmission instructionsProcurement platform
The scanner assists discovery but does not replace procurement systems.
Adding Deadline Warnings
Example:
const urgent = tender.days_remaining < 7;
Display:
⚠ Deadline Approaching
when applicable.
Why Highlight Urgency?
A perfect match:
95%
may still be impossible if:
Deadline = Tomorrow
Urgency influences decisions.
Creating Loading States
While loading:
if (loading)
Display:
Loading tender analysis...
Later:
Skeleton UI
can improve perceived performance.
Creating Error States
Display:
Tender not found.
or:
Unable to load tender.
This improves usability.
Creating a Two-Column Layout
Desktop:
------------------------------------------------Left Column Right Column------------------------------------------------Summary ScoreRequirements BreakdownSkills Match ReasonsTechnologies Missing Skills
Mobile:
Single Column
Responsive Layout CSS
.detail-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 30px;}
Mobile:
@media(max-width: 768px) { .detail-grid { grid-template-columns: 1fr; }}
Why Use a Two-Column Layout?
Users naturally separate:
Tender Information
from:
Match Analysis
This improves readability.
Creating the API Response Schema
Backend:
class TenderDetailResponse( BaseModel): id: int title: str summary: str overall_score: float score_breakdown: dict technologies: list[str] skills: list[str] requirements: list[str] missing_capabilities: list[str] match_reasons: list[str] source_url: str
This becomes the API contract.
Building the Backend Aggregation Service
Create:
TenderDetailService
Responsibilities:
Load TenderLoad AnalysisLoad MatchCombine ResultsReturn View Model
This simplifies frontend development.
Why Use View Models?
Bad:
Frontend assembles5 database entities
Good:
Backend returnsone detail object
Cleaner and faster.
Detail Page Workflow
User Clicks Tender | vReact Route | vTender Detail API | vAggregated View Model | vRender Detail Page
Testing the Detail Page
Verify:
Title loadsSummary loadsScore loadsTechnologies displaySkills displayRequirements displayActions work
Testing with Mock Data
Example:
const tender = { title: "Azure Migration", overall_score: 92, technologies: [ "Azure", "Docker" ], skills: [ "Cloud Migration" ]};
Build UI before backend integration.
Current Architecture
The platform now supports:
Tender Sources | vIngestion | vAI Analysis | vMatching Engine | vDashboard | vTender Detail Page
Users can now investigate opportunities in depth.
Current Deliverables
At this stage we have designed:
✔ Detail page route✔ Tender service✔ Detail API✔ AI summary section✔ Match explanation section✔ Score breakdown✔ Technologies display✔ Skills display✔ Requirements display✔ Missing capability analysis✔ Save actions✔ Dismiss actions✔ Source tender links✔ Deadline warnings✔ Responsive layout✔ View model architecture
The AI Tender Opportunity Scanner now supports deep opportunity evaluation.
Recommended Git Commit
git add .git commit -m "Implement tender detail page"
What We Will Build Next
Users can now:
Find opportunitiesUnderstand opportunitiesEvaluate opportunities
The next step is managing opportunities.
We will build:
Saved Opportunities Workspace
Users will be able to:
- Save tenders
- Create shortlists
- Track opportunities
- Organize bid pipelines
- Revisit important tenders
The next article in the development series is:
“Building the Saved Opportunities Workspace for Tender Pipeline Management.”
Conclusion
The Tender Detail Page transforms recommendations into actionable business intelligence.
Rather than showing a simple match score, the page provides the context needed to make informed bidding decisions. Users can review AI-generated summaries, evaluate requirements, understand capability gaps, and determine whether a tender deserves further investment.
With recommendation discovery and detailed opportunity analysis now complete, the next stage is helping users organize and manage their tender pipeline through a dedicated Saved Opportunities Workspace.