// ── My Portfolio view ──────────────────────────────────────────────────
// Left panel: portfolio composition (add/remove/search + dates + start).
// Right panel: empty by default; on "Start update" shows a support-contact box.
const { useState: useStateP, useEffect: useEffectP, useRef: useRefP } = React;
function PortfolioView({ tweaks, appPortfolio, setView }) {
const today = new Date().toISOString().slice(0, 10);
const now = new Date();
const hh = String(now.getHours()).padStart(2, "0");
const mm = String(now.getMinutes()).padStart(2, "0");
// portfolio: array of {entity_id, entity_name, kg_ticker} objects (loaded from API)
const [portfolio, setPortfolio] = useStateP(appPortfolio || []);
const [portfolioLoaded, setPortfolioLoaded] = useStateP(appPortfolio !== null);
const [allCandidates, setAllCandidates] = useStateP([]);
const [search, setSearch] = useStateP("");
const [showResults, setShowResults] = useStateP(false);
const [updateDate, setUpdateDate] = useStateP(today);
const [updateTime, setUpdateTime] = useStateP(`${hh}:${mm}`);
const [showSupport, setShowSupport] = useStateP(false);
const publicMode = window.DATA?.publicMode === true;
const searchRef = useRefP(null);
// Load portfolio and universe candidates on mount
useEffectP(() => {
if (!appPortfolio) {
fetch("/api/frontend/portfolio")
.then(r => r.json())
.then(data => {
setPortfolio(data.portfolio || []);
setPortfolioLoaded(true);
})
.catch(() => setPortfolioLoaded(true));
}
fetch("/api/frontend/portfolio/candidates")
.then(r => r.json())
.then(data => setAllCandidates(data.candidates || []))
.catch(() => {});
}, []);
// Close search dropdown on outside click
useEffectP(() => {
function handler(e) {
if (searchRef.current && !searchRef.current.contains(e.target)) setShowResults(false);
}
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
const portfolioIds = new Set(portfolio.map(p => p.entity_id));
const portfolioCompanies = portfolio; // full objects from API
const searchLower = search.trim().toLowerCase();
const searchResults = searchLower
? allCandidates
.filter(c =>
!portfolioIds.has(c.id) &&
(c.name.toLowerCase().includes(searchLower) || (c.ticker || "").toLowerCase().includes(searchLower))
)
.slice(0, 8)
: [];
function addCompany(id) {
setSearch("");
setShowResults(false);
if (publicMode) { setShowSupport(true); return; }
const candidate = allCandidates.find(c => c.id === id);
fetch("/api/frontend/portfolio", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ entity_id: id, entity_name: candidate?.name, kg_ticker: candidate?.ticker }),
})
.then(r => r.json())
.then(() => fetch("/api/frontend/portfolio").then(r => r.json()).then(d => setPortfolio(d.portfolio || [])))
.catch(() => {});
}
function removeCompany(id) {
if (publicMode) { setShowSupport(true); return; }
fetch(`/api/frontend/portfolio/${encodeURIComponent(id)}`, { method: "DELETE" })
.then(() => setPortfolio(prev => prev.filter(p => p.entity_id !== id)))
.catch(() => {});
}
function handleStart() {
if (publicMode) { setShowSupport(true); return; }
if (setView) setView("scan");
}
return (
{/* LEFT: portfolio composition + controls */}
My Portfolio
Build your portfolio .
Track the companies that matter to you. Briefs are generated each morning for everything in this list.
{/* Add bar */}
{ setSearch(e.target.value); setShowResults(true); }}
onFocus={() => setShowResults(true)}
autoComplete="off"
/>
searchResults[0] && addCompany(searchResults[0].id)}
>
+ Add
{showResults && search && (
{searchResults.length === 0 ? (
{portfolio.some(p =>
p.entity_name.toLowerCase().includes(searchLower) ||
(p.kg_ticker || "").toLowerCase().includes(searchLower)
)
? "Already in your portfolio."
: "No matches in the coverage universe."}
) : (
searchResults.map(c => (
addCompany(c.id)}>
{_tk(c.ticker)}
{c.name}
))
)}
)}
Holdings
{portfolioCompanies.length} {portfolioCompanies.length === 1 ? "company" : "companies"}
{/* Date + time */}
▶ Start update
{/* RIGHT: portfolio companies list → support box on Start */}
{!showSupport ? (
{portfolioCompanies.length > 0 ? (
{portfolioCompanies.map(p => (
{p.kg_ticker || "PRIVATE"}
{p.entity_name}
removeCompany(p.entity_id)} aria-label={`Remove ${p.entity_name}`}>
Remove
))}
) : (
Your portfolio is empty. Search on the left to add your first company.
)}
) : (
)}
);
}
window.PortfolioView = PortfolioView;