import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { adminApi } from '../../api/admin';
import { AppLayout } from '../../components/layout/AppLayout';
import { PageLoader } from '../../components/ui/LoadingSpinner';
import {
FolderOpen, ArrowLeft, Plus, X, Building2, Trash2, Search,
Edit3, Check,
} from 'lucide-react';
import type { Portfolio } from '../../types';
export function PortfolioDetailPage() {
const { portfolioId } = useParams<{ portfolioId: string }>();
const queryClient = useQueryClient();
const [search, setSearch] = useState('');
const [showAddModal, setShowAddModal] = useState(false);
const [editMode, setEditMode] = useState(false);
const [editName, setEditName] = useState('');
const [editDescription, setEditDescription] = useState('');
const { data, isLoading } = useQuery({
queryKey: ['portfolio', portfolioId],
queryFn: () => adminApi.getPortfolio(portfolioId),
});
const portfolio = data?.portfolio;
const updateMutation = useMutation({
mutationFn: (d: { name?: string; description?: string }) =>
adminApi.updatePortfolio(portfolioId, d),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['portfolio', portfolioId] });
queryClient.invalidateQueries({ queryKey: ['portfolios'] });
setEditMode(false);
},
});
const removeMutation = useMutation({
mutationFn: (companyIds: string[]) =>
adminApi.removePortfolioCompanies(portfolioId, companyIds),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['portfolio', portfolioId] });
},
});
const handleSaveEdit = () => {
if (!editName.trim()) return;
updateMutation.mutate({
name: editName.trim(),
description: editDescription.trim(),
});
};
if (isLoading) {
return (
<AppLayout title="Portfolio">
<PageLoader />
</AppLayout>
);
}
if (!portfolio) {
return (
<AppLayout title="Portfolio">
<div className="flex items-center gap-3">
<a href="/admin/portfolios" className="text-brand-600 hover:text-brand-700">
<ArrowLeft className="h-5 w-5" />
</a>
<p className="text-surface-500">Portfolio not found</p>
</div>
</AppLayout>
);
}
const companies = portfolio.companies ?? [];
const filtered = companies.filter((c) =>
c.name.toLowerCase().includes(search.toLowerCase()) ||
c.industry.toLowerCase().includes(search.toLowerCase())
);
return (
<AppLayout title="Portfolio">
{/* Back link */}
<a
href="/admin/portfolios"
className="mb-4 inline-flex items-center gap-1 text-sm text-brand-600 hover:text-brand-700"
>
<ArrowLeft className="h-4 w-4" />
Back to Portfolios
</a>
{/* Header */}
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div>
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-brand-50 text-brand-600">
<FolderOpen className="h-5 w-5" />
</div>
{editMode ? (
<div className="flex-1">
<input
type="text"
value={editName}
onChange={(e) => setEditName(e.target.value)}
className="w-full rounded-lg border border-surface-300 px-3 py-2 text-sm font-bold text-surface-900 outline-none focus:border-brand-500"
autoFocus
/>
<textarea
value={editDescription}
onChange={(e) => setEditDescription(e.target.value)}
rows={2}
className="mt-2 w-full rounded-lg border border-surface-300 px-3 py-2 text-xs text-surface-500 outline-none focus:border-brand-500 resize-none"
/>
</div>
) : (
<div>
<h1 className="text-xl sm:text-2xl font-bold text-surface-900">{portfolio.name}</h1>
{portfolio.description && (
<p className="mt-1 text-sm text-surface-500">{portfolio.description}</p>
)}
</div>
)}
</div>
<p className="mt-2 text-xs text-surface-400">
Created by {portfolio.created_by_name || portfolio.created_by_email}
</p>
</div>
<div className="flex gap-2">
{editMode ? (
<>
<button
onClick={handleSaveEdit}
disabled={!editName.trim() || updateMutation.isPending}
className="flex items-center gap-2 rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-700 disabled:opacity-50 min-h-[44px]"
>
<Check className="h-4 w-4" />
Save
</button>
<button
onClick={() => { setEditMode(false); setEditName(portfolio.name); setEditDescription(portfolio.description); }}
className="flex items-center gap-2 rounded-lg border border-surface-300 px-4 py-2.5 text-sm font-medium text-surface-700 hover:bg-surface-50 min-h-[44px]"
>
<X className="h-4 w-4" />
Cancel
</button>
</>
) : (
<>
<button
onClick={() => { setEditMode(true); setEditName(portfolio.name); setEditDescription(portfolio.description); }}
className="flex items-center gap-2 rounded-lg border border-surface-300 px-4 py-2.5 text-sm font-medium text-surface-700 hover:bg-surface-50 min-h-[44px]"
>
<Edit3 className="h-4 w-4" />
Edit
</button>
<button
onClick={() => setShowAddModal(true)}
className="flex items-center gap-2 rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-700 min-h-[44px]"
>
<Plus className="h-4 w-4" />
Add Companies
</button>
</>
)}
</div>
</div>
{/* Companies */}
<div className="mb-4 flex items-center gap-3">
<h2 className="text-sm font-semibold text-surface-700">
Companies ({companies.length})
</h2>
<div className="relative flex-1 max-w-sm">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-surface-500" />
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search companies..."
className="w-full rounded-lg border border-surface-300 py-2 pl-9 pr-4 text-sm outline-none focus:border-brand-500 min-h-[44px]"
/>
</div>
</div>
{filtered.length === 0 ? (
<div className="flex flex-col items-center justify-center rounded-xl border border-surface-200 bg-white py-12">
<Building2 className="h-10 w-10 text-surface-300" />
<p className="mt-3 text-sm font-medium text-surface-700">
{search ? 'No companies match your search' : 'No companies in this portfolio'}
</p>
<button
onClick={() => setShowAddModal(true)}
className="mt-3 text-sm text-brand-600 hover:text-brand-700"
>
+ Add companies
</button>
</div>
) : (
<div className="rounded-xl border border-surface-200 bg-white overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-surface-200">
<th className="pb-3 pl-5 text-left font-medium text-surface-500">Company</th>
<th className="pb-3 text-left font-medium text-surface-500">Industry</th>
<th className="pb-3 text-right font-medium text-surface-500">Revenue</th>
<th className="pb-3 pr-5 w-10"></th>
</tr>
</thead>
<tbody className="divide-y divide-surface-100">
{filtered.map((company) => (
<tr key={company.id} className="hover:bg-surface-50">
<td className="py-3 pl-5">
<div className="flex items-center gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-brand-100 text-brand-600">
<Building2 className="h-4 w-4" />
</div>
<span className="font-medium text-surface-900">{company.name}</span>
</div>
</td>
<td className="py-3 text-surface-600">{company.industry || '—'}</td>
<td className="py-3 text-right text-surface-600">
{company.annual_revenue ? `$${company.annual_revenue.toLocaleString()}` : '—'}
</td>
<td className="py-3 pr-5 text-right">
<button
onClick={() => removeMutation.mutate([company.id])}
className="rounded-lg p-1.5 text-surface-400 hover:bg-red-50 hover:text-red-600"
title="Remove from portfolio"
>
<Trash2 className="h-4 w-4" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{/* Add Companies Modal */}
{showAddModal && (
<AddCompaniesModal
portfolio={portfolio}
onClose={() => setShowAddModal(false)}
/>
)}
</AppLayout>
);
}
function AddCompaniesModal({ portfolio, onClose }: {
portfolio: Portfolio;
onClose: () => void;
}) {
const queryClient = useQueryClient();
const [search, setSearch] = useState('');
const { data: orgData, isLoading: loadingOrgs } = useQuery({
queryKey: ['admin-organizations'],
queryFn: () => adminApi.getOrganizations(),
});
const addMutation = useMutation({
mutationFn: (companyIds: string[]) =>
adminApi.addPortfolioCompanies(portfolio.id, companyIds),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['portfolio', portfolio.id] });
queryClient.invalidateQueries({ queryKey: ['portfolios'] });
onClose();
},
});
const allCompanies = orgData?.items ?? [];
const existingIds = new Set(portfolio.companies?.map(c => c.id) ?? []);
const available = allCompanies
.filter((org) => !existingIds.has(String(org.id)))
.filter((org) =>
org.name.toLowerCase().includes(search.toLowerCase())
);
const [selected, setSelected] = useState<Set<string>>(new Set());
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
<div className="w-full max-w-lg max-h-[80vh] overflow-y-auto rounded-xl bg-white p-6 shadow-xl">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-bold text-surface-900">Add Companies to Portfolio</h2>
<button
onClick={onClose}
className="rounded-lg p-1 text-surface-400 hover:bg-surface-100 hover:text-surface-600"
>
<X className="h-5 w-5" />
</button>
</div>
<div className="relative mb-4">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-surface-500" />
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search companies..."
className="w-full rounded-lg border border-surface-300 py-2.5 pl-9 pr-4 text-sm outline-none focus:border-brand-500"
/>
</div>
{loadingOrgs ? (
<PageLoader />
) : available.length === 0 ? (
<p className="text-sm text-surface-500">
{search ? 'No companies found' : 'All companies are already in this portfolio'}
</p>
) : (
<div className="space-y-1">
{available.map((org) => (
<label
key={org.id}
className={`flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2.5 text-sm transition-colors ${
selected.has(String(org.id))
? 'bg-brand-50 text-brand-700'
: 'hover:bg-surface-50 text-surface-700'
}`}
>
<input
type="checkbox"
checked={selected.has(String(org.id))}
onChange={(e) => {
const next = new Set(selected);
if (e.target.checked) next.add(String(org.id));
else next.delete(String(org.id));
setSelected(next);
}}
className="h-4 w-4 rounded border-surface-300 text-brand-600 focus:ring-brand-500"
/>
<div className="min-w-0 flex-1">
<span className="font-medium">{org.name}</span>
<span className="ml-2 text-xs text-surface-400">{org.industry}</span>
</div>
</label>
))}
</div>
)}
<div className="mt-4 flex justify-end gap-3">
<button
onClick={onClose}
className="rounded-lg border border-surface-300 px-4 py-2.5 text-sm font-medium text-surface-700 hover:bg-surface-50 min-h-[44px]"
>
Cancel
</button>
<button
onClick={() => addMutation.mutate(Array.from(selected))}
disabled={selected.size === 0 || addMutation.isPending}
className="rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-700 disabled:opacity-50 min-h-[44px]"
>
{addMutation.isPending ? 'Adding...' : `Add ${selected.size} Company${selected.size !== 1 ? 'ies' : ''}`}
</button>
</div>
</div>
</div>
);
}