Dominion/src/components/InvestmentsPortfolioView.tsx
2026-08-07 13:06:39 +05:00

1027 lines
47 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
TrendingUp,
Briefcase,
DollarSign,
PieChart,
Edit3,
Trash2,
Plus,
RefreshCw,
Search,
CheckCircle,
AlertTriangle,
FileSpreadsheet,
Layers,
ArrowUpRight,
ArrowDownRight,
Sliders,
Building2,
X,
Check,
Zap,
Info,
ShieldCheck
} from 'lucide-react';
import { InvestmentSecurity, InvestmentCategory, InvestmentReconciliation, BranchId, User } from '../types';
import { BRANCHES_LIST } from '../types';
interface InvestmentsPortfolioViewProps {
currentUser: User | null;
activePeriod: string;
}
export const InvestmentsPortfolioView: React.FC<InvestmentsPortfolioViewProps> = ({
currentUser,
activePeriod,
}) => {
const isHO = currentUser?.role === 'admin';
const branchId = currentUser?.branchId;
const [investments, setInvestments] = useState<InvestmentSecurity[]>([]);
const [reconciliations, setReconciliations] = useState<InvestmentReconciliation[]>([]);
const [grandTotals, setGrandTotals] = useState<any>(null);
const [loading, setLoading] = useState<boolean>(true);
const [activeTab, setActiveTab] = useState<'securities' | 'reconciliation' | 'mtm_pricing'>('securities');
// Filters
const [searchQuery, setSearchQuery] = useState<string>('');
const [selectedBranchFilter, setSelectedBranchFilter] = useState<string>(isHO ? 'all' : branchId || 'all');
const [selectedCategoryFilter, setSelectedCategoryFilter] = useState<string>('all');
// Modals state
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [editingSecurity, setEditingSecurity] = useState<Partial<InvestmentSecurity> | null>(null);
// Bulk MTM Modal / Form state
const [bulkIsin, setBulkIsin] = useState<string>('');
const [bulkMtmPrice, setBulkMtmPrice] = useState<string>('');
const [bulkMtmYield, setBulkMtmYield] = useState<string>('');
const [mtmSuccessMsg, setMtmSuccessMsg] = useState<string>('');
useEffect(() => {
if (!isHO && branchId) {
setSelectedBranchFilter(branchId);
}
}, [currentUser, isHO, branchId]);
const fetchInvestments = async () => {
setLoading(true);
try {
const url = !isHO && branchId ? `/api/investments?branchId=${branchId}` : (selectedBranchFilter !== 'all' ? `/api/investments?branchId=${selectedBranchFilter}` : '/api/investments');
const res = await fetch(url);
const data = await res.json();
setInvestments(data.investments || []);
// Fetch reconciliation
const reconRes = await fetch(`/api/investments/reconciliation?period=${activePeriod}`);
const reconData = await reconRes.json();
setReconciliations(reconData.reconciliations || []);
setGrandTotals(reconData.grandTotals || null);
} catch (err) {
console.error('Failed to fetch investments:', err);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchInvestments();
}, [currentUser, activePeriod, selectedBranchFilter]);
// Handle Add/Edit Security Save
const handleSaveSecurity = async (e: React.FormEvent) => {
e.preventDefault();
if (!editingSecurity) return;
try {
const res = await fetch('/api/investments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...editingSecurity,
branchId: editingSecurity.branchId || branchId || 'bahrain',
faceValue: Number(editingSecurity.faceValue || 0),
amountInvested: Number(editingSecurity.amountInvested || 0),
couponRate: Number(editingSecurity.couponRate || 0),
originalPrice: Number(editingSecurity.originalPrice || 100),
purchaseYield: Number(editingSecurity.purchaseYield || 0),
}),
});
if (res.ok) {
setIsModalOpen(false);
setEditingSecurity(null);
fetchInvestments();
} else {
const err = await res.json();
alert(`Error: ${err.error || 'Failed to save security'}`);
}
} catch (err) {
console.error('Save security error:', err);
}
};
// Handle Delete Security
const handleDeleteSecurity = async (id: string) => {
if (!confirm('Are you sure you want to delete this security from the portfolio?')) return;
try {
const res = await fetch(`/api/investments/${id}`, { method: 'DELETE' });
if (res.ok) {
fetchInvestments();
}
} catch (err) {
console.error('Delete security error:', err);
}
};
// Handle HO Bulk MTM Price Update
const handleBulkMtmSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!bulkIsin || !bulkMtmPrice) return;
try {
const res = await fetch('/api/investments/bulk-mtm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
isin: bulkIsin.trim(),
mtmPrice: Number(bulkMtmPrice),
mtmYield: bulkMtmYield ? Number(bulkMtmYield) : undefined,
updatedBy: currentUser?.name || 'Head Office Admin',
}),
});
if (res.ok) {
const data = await res.json();
setMtmSuccessMsg(data.message || 'MTM prices updated successfully');
setTimeout(() => setMtmSuccessMsg(''), 4000);
setBulkIsin('');
setBulkMtmPrice('');
setBulkMtmYield('');
fetchInvestments();
}
} catch (err) {
console.error('Bulk MTM error:', err);
}
};
// Filtered Securities
const filteredSecurities = investments.filter((sec) => {
const matchesBranch = selectedBranchFilter === 'all' || sec.branchId === selectedBranchFilter;
const matchesCategory = selectedCategoryFilter === 'all' || sec.category === selectedCategoryFilter;
const q = searchQuery.toLowerCase();
const matchesSearch =
!q ||
sec.isin?.toLowerCase().includes(q) ||
sec.securityType?.toLowerCase().includes(q) ||
sec.country?.toLowerCase().includes(q);
return matchesBranch && matchesCategory && matchesSearch;
});
// KPI Calculations
const totalFaceValue = filteredSecurities.reduce((acc, s) => acc + (s.faceValue || 0), 0);
const totalAmountInvested = filteredSecurities.reduce((acc, s) => acc + (s.amountInvested || 0), 0);
const totalMtmPnL = filteredSecurities.reduce((acc, s) => acc + (s.mtmPnL || 0), 0);
const totalMtmValue = filteredSecurities.reduce((acc, s) => {
const mtm = s.mtmPrice !== undefined ? (s.faceValue * s.mtmPrice) / 100 : s.amountInvested;
return acc + mtm;
}, 0);
// Unique ISINs count
const uniqueIsins = new Set(investments.map((s) => s.isin)).size;
return (
<div className="space-y-6">
{/* Top Banner / Header */}
<div className="holo-card p-6 rounded-2xl bg-slate-900/90 border border-slate-700/80 shadow-2xl relative overflow-hidden">
<div className="absolute top-0 right-0 w-96 h-96 bg-cyan-500/10 rounded-full blur-3xl pointer-events-none" />
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 relative z-10">
<div>
<div className="flex items-center gap-3">
<div className="p-3 bg-gradient-to-br from-cyan-500/20 to-blue-600/20 border border-cyan-500/30 rounded-xl text-cyan-400">
<Briefcase className="w-7 h-7" />
</div>
<div>
<h1 className="text-2xl font-bold text-slate-100 flex items-center gap-2">
Investments Portfolio
<span className="text-xs px-2.5 py-0.5 rounded-full bg-cyan-500/10 border border-cyan-500/30 text-cyan-300 font-mono">
{isHO ? 'Global Consolidated' : `${currentUser?.branchId?.toUpperCase()} Branch`}
</span>
</h1>
<p className="text-sm text-slate-400 mt-0.5">
Complete portfolio management (HTM, AFS, HFT) with Head Office MTM revaluation & Balance Sheet reconciliation
</p>
</div>
</div>
</div>
<div className="flex items-center gap-3">
<button
onClick={fetchInvestments}
className="px-3.5 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-300 border border-slate-700 text-sm font-medium flex items-center gap-2 transition-all"
>
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin text-cyan-400' : ''}`} />
Refresh
</button>
<button
onClick={() => {
setEditingSecurity({
branchId: (branchId || 'bahrain') as BranchId,
country: BRANCHES_LIST.find((b) => b.id === (branchId || 'bahrain'))?.country || 'Bahrain',
category: 'AFS',
isin: '',
securityType: '',
faceValue: 0,
amountInvested: 0,
couponRate: 0,
issueDate: '',
purchaseDate: '',
maturityDate: '',
originalPrice: 100,
});
setIsModalOpen(true);
}}
className="px-4 py-2 rounded-xl bg-gradient-to-r from-cyan-500 to-blue-600 hover:from-cyan-400 hover:to-blue-500 text-white font-medium text-sm shadow-lg shadow-cyan-500/20 flex items-center gap-2 transition-all"
>
<Plus className="w-4 h-4" />
Add Security
</button>
</div>
</div>
</div>
{/* KPI Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="holo-card p-5 rounded-2xl bg-slate-900/80 border border-slate-800 hover:border-slate-700 transition-all">
<div className="flex items-center justify-between text-slate-400 text-xs font-semibold uppercase tracking-wider mb-2">
<span>Total Face Value</span>
<Layers className="w-4 h-4 text-cyan-400" />
</div>
<div className="text-2xl font-bold text-slate-100 font-mono">
USD {totalFaceValue.toFixed(2)} <span className="text-sm font-normal text-slate-400">Mn</span>
</div>
<div className="text-xs text-slate-400 mt-2 flex items-center gap-1">
<span className="text-cyan-400 font-semibold">{filteredSecurities.length}</span> active portfolio holdings
</div>
</div>
<div className="holo-card p-5 rounded-2xl bg-slate-900/80 border border-slate-800 hover:border-slate-700 transition-all">
<div className="flex items-center justify-between text-slate-400 text-xs font-semibold uppercase tracking-wider mb-2">
<span>Book Value (Invested)</span>
<DollarSign className="w-4 h-4 text-emerald-400" />
</div>
<div className="text-2xl font-bold text-emerald-400 font-mono">
USD {totalAmountInvested.toFixed(2)} <span className="text-sm font-normal text-slate-400">Mn</span>
</div>
<div className="text-xs text-slate-400 mt-2 flex items-center gap-1">
<span>Reconciles with Balance Sheet Investments</span>
</div>
</div>
<div className="holo-card p-5 rounded-2xl bg-slate-900/80 border border-slate-800 hover:border-slate-700 transition-all">
<div className="flex items-center justify-between text-slate-400 text-xs font-semibold uppercase tracking-wider mb-2">
<span>MTM Market Value</span>
<TrendingUp className="w-4 h-4 text-purple-400" />
</div>
<div className="text-2xl font-bold text-purple-300 font-mono">
USD {totalMtmValue.toFixed(2)} <span className="text-sm font-normal text-slate-400">Mn</span>
</div>
<div className="text-xs text-slate-400 mt-2 flex items-center gap-1">
<span>HO Mark-to-Market revalued</span>
</div>
</div>
<div className="holo-card p-5 rounded-2xl bg-slate-900/80 border border-slate-800 hover:border-slate-700 transition-all">
<div className="flex items-center justify-between text-slate-400 text-xs font-semibold uppercase tracking-wider mb-2">
<span>Unrealized MTM P&L</span>
{totalMtmPnL >= 0 ? (
<ArrowUpRight className="w-4 h-4 text-emerald-400" />
) : (
<ArrowDownRight className="w-4 h-4 text-rose-400" />
)}
</div>
<div
className={`text-2xl font-bold font-mono ${
totalMtmPnL >= 0 ? 'text-emerald-400' : 'text-rose-400'
}`}
>
{totalMtmPnL >= 0 ? '+' : ''}${(totalMtmPnL / 1000000).toFixed(2)}{' '}
<span className="text-sm font-normal text-slate-400">Mn</span>
</div>
<div className="text-xs text-slate-400 mt-2 font-mono">
(${totalMtmPnL.toLocaleString('en-US', { maximumFractionDigits: 0 })})
</div>
</div>
</div>
{/* Navigation Tabs */}
<div className="flex items-center justify-between border-b border-slate-800 pb-3">
<div className="flex items-center gap-2">
<button
onClick={() => setActiveTab('securities')}
className={`px-4 py-2.5 rounded-xl font-medium text-sm flex items-center gap-2 transition-all ${
activeTab === 'securities'
? 'bg-cyan-500/20 text-cyan-300 border border-cyan-500/30'
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/50'
}`}
>
<Briefcase className="w-4 h-4" />
Securities Master ({filteredSecurities.length})
</button>
<button
onClick={() => setActiveTab('reconciliation')}
className={`px-4 py-2.5 rounded-xl font-medium text-sm flex items-center gap-2 transition-all ${
activeTab === 'reconciliation'
? 'bg-cyan-500/20 text-cyan-300 border border-cyan-500/30'
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/50'
}`}
>
<CheckCircle className="w-4 h-4" />
Balance Sheet Reconciliation
{grandTotals && !grandTotals.isMatched && (
<span className="w-2 h-2 rounded-full bg-amber-400 animate-ping" />
)}
</button>
{isHO && (
<button
onClick={() => setActiveTab('mtm_pricing')}
className={`px-4 py-2.5 rounded-xl font-medium text-sm flex items-center gap-2 transition-all ${
activeTab === 'mtm_pricing'
? 'bg-cyan-500/20 text-cyan-300 border border-cyan-500/30'
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/50'
}`}
>
<Zap className="w-4 h-4 text-amber-400" />
HO Bulk MTM Pricing Tool
</button>
)}
</div>
<div className="text-xs text-slate-400 hidden sm:block">
<span className="text-slate-300 font-semibold">{uniqueIsins}</span> Unique ISINs tracked
</div>
</div>
{/* TAB 1: SECURITIES MASTER TABLE */}
{activeTab === 'securities' && (
<div className="space-y-4">
{/* Controls Bar */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-slate-900/60 p-4 rounded-xl border border-slate-800">
<div className="flex-1 relative">
<Search className="w-4 h-4 text-slate-500 absolute left-3.5 top-1/2 -translate-y-1/2" />
<input
type="text"
placeholder="Search ISIN, Security Description, or Country..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2 rounded-xl bg-slate-950/80 border border-slate-700/80 text-slate-200 text-sm focus:outline-none focus:border-cyan-500"
/>
</div>
<div className="flex flex-wrap items-center gap-3">
{isHO ? (
<div className="flex items-center gap-2">
<span className="text-xs text-slate-400">Branch:</span>
<select
value={selectedBranchFilter}
onChange={(e) => setSelectedBranchFilter(e.target.value)}
className="py-2 px-3 rounded-xl bg-slate-950 border border-slate-700 text-slate-200 text-sm focus:outline-none focus:border-cyan-500"
>
<option value="all">All Branches</option>
{BRANCHES_LIST.map((b) => (
<option key={b.id} value={b.id}>
{b.name}
</option>
))}
</select>
</div>
) : (
<div className="flex items-center gap-2 bg-slate-950 px-3 py-1.5 rounded-xl border border-cyan-500/30 text-xs text-cyan-300 font-mono">
<ShieldCheck className="w-3.5 h-3.5 text-cyan-400" />
<span>Branch Scope: {BRANCHES_LIST.find((b) => b.id === branchId)?.name || branchId?.toUpperCase()}</span>
</div>
)}
<div className="flex items-center gap-2">
<span className="text-xs text-slate-400">Category:</span>
<select
value={selectedCategoryFilter}
onChange={(e) => setSelectedCategoryFilter(e.target.value)}
className="py-2 px-3 rounded-xl bg-slate-950 border border-slate-700 text-slate-200 text-sm focus:outline-none focus:border-cyan-500"
>
<option value="all">All Categories</option>
<option value="HTM">HTM (Held-To-Maturity)</option>
<option value="AFS">AFS (Available-For-Sale)</option>
<option value="HFT">HFT (Held-For-Trading)</option>
</select>
</div>
</div>
</div>
{/* Table */}
<div className="overflow-x-auto rounded-2xl border border-slate-800 bg-slate-900/80 shadow-xl">
<table className="w-full text-left border-collapse text-xs">
<thead>
<tr className="bg-slate-950/90 text-slate-400 font-semibold border-b border-slate-800 uppercase tracking-wider">
<th className="py-3.5 px-4">Branch / Country</th>
<th className="py-3.5 px-3">Cat</th>
<th className="py-3.5 px-3">ISIN / Security</th>
<th className="py-3.5 px-3 text-right">Face Value ($M)</th>
<th className="py-3.5 px-3 text-right">Amount Invested ($M)</th>
<th className="py-3.5 px-3 text-right">Coupon %</th>
<th className="py-3.5 px-3">Maturity Date</th>
<th className="py-3.5 px-3 text-right">Orig Price %</th>
<th className="py-3.5 px-3 text-right bg-cyan-950/30 text-cyan-300">HO MTM Price %</th>
<th className="py-3.5 px-3 text-right bg-cyan-950/30 text-cyan-300">MTM Yield %</th>
<th className="py-3.5 px-3 text-right">MTM P&L ($)</th>
<th className="py-3.5 px-3 text-center">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800/60 font-mono text-slate-300">
{filteredSecurities.length === 0 ? (
<tr>
<td colSpan={12} className="py-12 text-center text-slate-500 font-sans">
No investment securities match the selected filters.
</td>
</tr>
) : (
filteredSecurities.map((sec) => {
const branchObj = BRANCHES_LIST.find((b) => b.id === sec.branchId);
return (
<tr key={sec.id} className="hover:bg-slate-800/50 transition-colors">
<td className="py-3 px-4 font-sans font-medium text-slate-200">
<div>{branchObj?.name || sec.country}</div>
<div className="text-[10px] text-slate-500">{sec.country}</div>
</td>
<td className="py-3 px-3">
<span
className={`px-2 py-0.5 rounded text-[10px] font-semibold ${
sec.category === 'HTM'
? 'bg-amber-500/10 text-amber-300 border border-amber-500/30'
: sec.category === 'AFS'
? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/30'
: 'bg-purple-500/10 text-purple-300 border border-purple-500/30'
}`}
>
{sec.category}
</span>
</td>
<td className="py-3 px-3 font-sans">
<div className="font-mono text-cyan-400 font-semibold text-xs">{sec.isin}</div>
<div className="text-slate-300 text-[11px] truncate max-w-[200px]" title={sec.securityType}>
{sec.securityType}
</div>
</td>
<td className="py-3 px-3 text-right font-bold text-slate-100">
{sec.faceValue.toFixed(2)}
</td>
<td className="py-3 px-3 text-right font-bold text-emerald-400">
{sec.amountInvested.toFixed(2)}
</td>
<td className="py-3 px-3 text-right text-slate-300">
{sec.couponRate ? `${sec.couponRate.toFixed(2)}%` : '—'}
</td>
<td className="py-3 px-3 font-sans text-slate-400">{sec.maturityDate}</td>
<td className="py-3 px-3 text-right text-slate-300">
{sec.originalPrice ? sec.originalPrice.toFixed(3) : '100.000'}
</td>
<td className="py-3 px-3 text-right font-bold text-cyan-300 bg-cyan-950/20">
{sec.mtmPrice !== undefined ? sec.mtmPrice.toFixed(3) : '—'}
</td>
<td className="py-3 px-3 text-right text-cyan-200 bg-cyan-950/20">
{sec.mtmYield !== undefined ? `${sec.mtmYield.toFixed(2)}%` : '—'}
</td>
<td
className={`py-3 px-3 text-right font-bold ${
(sec.mtmPnL || 0) >= 0 ? 'text-emerald-400' : 'text-rose-400'
}`}
>
{sec.mtmPnL !== undefined
? `${sec.mtmPnL >= 0 ? '+' : ''}$${Math.round(sec.mtmPnL).toLocaleString()}`
: '—'}
</td>
<td className="py-3 px-3 text-center font-sans">
<div className="flex items-center justify-center gap-1.5">
<button
onClick={() => {
setEditingSecurity(sec);
setIsModalOpen(true);
}}
className="p-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-cyan-400 transition-colors"
title="Edit Security"
>
<Edit3 className="w-3.5 h-3.5" />
</button>
<button
onClick={() => handleDeleteSecurity(sec.id)}
className="p-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-rose-400 transition-colors"
title="Delete Security"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
</div>
)}
{/* TAB 2: RECONCILIATION MATRIX */}
{activeTab === 'reconciliation' && (
<div className="space-y-4">
<div className="bg-slate-900/80 p-5 rounded-2xl border border-slate-800">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-bold text-slate-100 flex items-center gap-2">
<CheckCircle className="w-5 h-5 text-cyan-400" />
Portfolio vs Balance Sheet Reconciliation
</h3>
<p className="text-xs text-slate-400 mt-0.5">
Verifies that each branch's detailed Investment Portfolio sum matches its Balance Sheet 'Investments' line item for period {activePeriod}.
</p>
</div>
{grandTotals && (
<div className="flex items-center gap-3 font-mono text-xs">
<span className="px-3 py-1.5 rounded-xl bg-slate-950 border border-slate-800 text-slate-300">
Grand Portfolio Total: <strong className="text-cyan-400">${grandTotals.portfolioTotalInvested}M</strong>
</span>
<span className="px-3 py-1.5 rounded-xl bg-slate-950 border border-slate-800 text-slate-300">
Grand BS Total: <strong className="text-emerald-400">${grandTotals.balanceSheetInvestments}M</strong>
</span>
</div>
)}
</div>
<div className="overflow-x-auto rounded-xl border border-slate-800">
<table className="w-full text-left border-collapse text-xs font-sans">
<thead>
<tr className="bg-slate-950 text-slate-400 font-semibold border-b border-slate-800 uppercase tracking-wider">
<th className="py-3 px-4">Branch Name</th>
<th className="py-3 px-3 text-center">Securities Count</th>
<th className="py-3 px-3 text-right">Portfolio Invested ($M)</th>
<th className="py-3 px-3 text-right">Balance Sheet 'Investments' ($M)</th>
<th className="py-3 px-3 text-right">Variance ($M)</th>
<th className="py-3 px-4 text-center">Reconciliation Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800/60 font-mono">
{reconciliations.map((recon) => (
<tr key={recon.branchId} className="hover:bg-slate-800/40 transition-colors">
<td className="py-3 px-4 font-sans font-semibold text-slate-200">
{recon.branchName}
</td>
<td className="py-3 px-3 text-center text-slate-400">
{recon.securitiesCount}
</td>
<td className="py-3 px-3 text-right font-bold text-cyan-300">
${recon.portfolioTotalInvested.toFixed(2)}
</td>
<td className="py-3 px-3 text-right font-bold text-emerald-400">
${recon.balanceSheetInvestments.toFixed(2)}
</td>
<td
className={`py-3 px-3 text-right font-bold ${
recon.difference === 0
? 'text-slate-400'
: recon.difference > 0
? 'text-cyan-400'
: 'text-amber-400'
}`}
>
{recon.difference === 0
? '$0.00'
: `${recon.difference > 0 ? '+' : ''}$${recon.difference.toFixed(2)}`}
</td>
<td className="py-3 px-4 text-center font-sans">
{recon.isMatched ? (
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-semibold bg-emerald-500/10 border border-emerald-500/30 text-emerald-400">
<Check className="w-3.5 h-3.5" />
Reconciled
</span>
) : (
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-semibold bg-amber-500/10 border border-amber-500/30 text-amber-300">
<AlertTriangle className="w-3.5 h-3.5" />
Mismatch (${Math.abs(recon.difference).toFixed(2)}M)
</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
{/* TAB 3: HO BULK MTM PRICING TOOL */}
{activeTab === 'mtm_pricing' && isHO && (
<div className="space-y-6">
<div className="holo-card p-6 rounded-2xl bg-slate-900/90 border border-slate-800">
<h3 className="text-lg font-bold text-slate-100 flex items-center gap-2 mb-2">
<Zap className="w-5 h-5 text-amber-400" />
Head Office Mark-to-Market (MTM) Revaluation Feed
</h3>
<p className="text-xs text-slate-400 mb-6">
Input MTM prices (% of par) or Yields for specific ISIN securities. All branches holding securities with matching ISIN will be updated and revalued automatically.
</p>
{mtmSuccessMsg && (
<div className="p-3.5 mb-6 rounded-xl bg-emerald-500/10 border border-emerald-500/30 text-emerald-300 text-sm flex items-center gap-2 font-medium">
<Check className="w-4 h-4 text-emerald-400" />
{mtmSuccessMsg}
</div>
)}
<form onSubmit={handleBulkMtmSubmit} className="grid grid-cols-1 md:grid-cols-4 gap-4 items-end">
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">
Select / Enter ISIN Code
</label>
<input
type="text"
placeholder="e.g. XS2232319638"
value={bulkIsin}
onChange={(e) => setBulkIsin(e.target.value)}
required
className="w-full px-3.5 py-2.5 rounded-xl bg-slate-950 border border-slate-700 font-mono text-cyan-300 text-sm focus:outline-none focus:border-cyan-500"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">
New MTM Price (% of Par)
</label>
<input
type="number"
step="0.001"
placeholder="e.g. 100.380"
value={bulkMtmPrice}
onChange={(e) => setBulkMtmPrice(e.target.value)}
required
className="w-full px-3.5 py-2.5 rounded-xl bg-slate-950 border border-slate-700 font-mono text-emerald-400 text-sm focus:outline-none focus:border-cyan-500"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">
MTM Yield % (Optional)
</label>
<input
type="number"
step="0.01"
placeholder="e.g. 7.28"
value={bulkMtmYield}
onChange={(e) => setBulkMtmYield(e.target.value)}
className="w-full px-3.5 py-2.5 rounded-xl bg-slate-950 border border-slate-700 font-mono text-purple-300 text-sm focus:outline-none focus:border-cyan-500"
/>
</div>
<div>
<button
type="submit"
className="w-full py-2.5 px-4 rounded-xl bg-gradient-to-r from-amber-500 to-orange-600 hover:from-amber-400 hover:to-orange-500 text-white font-semibold text-sm shadow-lg shadow-amber-500/20 flex items-center justify-center gap-2 transition-all"
>
<Zap className="w-4 h-4" />
Apply MTM Revaluation
</button>
</div>
</form>
</div>
{/* Preset MTM Market Feed Quotes */}
<div className="bg-slate-900/80 p-5 rounded-2xl border border-slate-800">
<h4 className="text-sm font-bold text-slate-200 mb-3 flex items-center justify-between">
<span>Bloomberg / Market Benchmark Reference Quotes (30-Jun-26 Feed)</span>
<span className="text-xs font-normal text-slate-400">Click quote to auto-fill</span>
</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
{[
{ isin: 'XS2232319638', name: 'GOP EuroBond 2031', price: 100.38, yield: 7.28 },
{ isin: 'XS1729875598', name: 'GOP EuroBond 2027', price: 100.50, yield: 6.50 },
{ isin: 'XS3353874145', name: 'GOP EuroBond 2029', price: 99.75, yield: 7.07 },
{ isin: 'XS2419405274', name: 'PAKISTAN SUKUK 7.95%', price: 99.99, yield: 7.95 },
{ isin: 'BD927251100', name: 'Bangladesh 10Y T-Bond', price: 98.45, yield: 9.71 },
{ isin: 'BD934481203', name: 'Bangladesh 20Y T-Bond', price: 109.90, yield: 10.27 },
].map((feed) => (
<button
key={feed.isin}
onClick={() => {
setBulkIsin(feed.isin);
setBulkMtmPrice(feed.price.toString());
setBulkMtmYield(feed.yield.toString());
}}
className="p-3 rounded-xl bg-slate-950 border border-slate-800 hover:border-cyan-500/50 text-left transition-all group"
>
<div className="flex items-center justify-between mb-1">
<span className="font-mono text-xs font-bold text-cyan-400 group-hover:text-cyan-300">
{feed.isin}
</span>
<span className="text-[10px] text-slate-500">Live Quote</span>
</div>
<div className="text-xs text-slate-200 font-medium">{feed.name}</div>
<div className="mt-2 flex items-center justify-between text-xs font-mono">
<span className="text-emerald-400">Price: {feed.price}%</span>
<span className="text-purple-300">Yield: {feed.yield}%</span>
</div>
</button>
))}
</div>
</div>
</div>
)}
{/* MODAL: ADD / EDIT SECURITY */}
{isModalOpen && editingSecurity && (
<div className="fixed inset-0 z-50 bg-slate-950/80 backdrop-blur-md flex items-center justify-center p-4">
<div className="bg-slate-900 border border-slate-700 rounded-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto p-6 shadow-2xl relative">
<button
onClick={() => setIsModalOpen(false)}
className="absolute top-4 right-4 p-2 rounded-xl bg-slate-800 text-slate-400 hover:text-slate-100"
>
<X className="w-5 h-5" />
</button>
<h3 className="text-xl font-bold text-slate-100 mb-1">
{editingSecurity.id ? 'Edit Investment Security' : 'Add Investment Security'}
</h3>
<p className="text-xs text-slate-400 mb-5">
Enter complete security details for portfolio tracking & reconciliation.
</p>
<form onSubmit={handleSaveSecurity} className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
Branch / Territory
</label>
<select
value={editingSecurity.branchId || branchId || 'bahrain'}
onChange={(e) =>
setEditingSecurity({
...editingSecurity,
branchId: e.target.value as BranchId,
country: BRANCHES_LIST.find((b) => b.id === e.target.value)?.country || 'Bahrain',
})
}
disabled={!isHO}
className="w-full px-3 py-2 rounded-xl bg-slate-950 border border-slate-700 text-slate-200 text-sm"
>
{BRANCHES_LIST.map((b) => (
<option key={b.id} value={b.id}>
{b.name} ({b.country})
</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
Category
</label>
<select
value={editingSecurity.category || 'AFS'}
onChange={(e) =>
setEditingSecurity({
...editingSecurity,
category: e.target.value as InvestmentCategory,
})
}
className="w-full px-3 py-2 rounded-xl bg-slate-950 border border-slate-700 text-slate-200 text-sm"
>
<option value="HTM">HTM (Held-To-Maturity)</option>
<option value="AFS">AFS (Available-For-Sale)</option>
<option value="HFT">HFT (Held-For-Trading)</option>
</select>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
ISIN Code
</label>
<input
type="text"
required
placeholder="e.g. XS2232319638"
value={editingSecurity.isin || ''}
onChange={(e) => setEditingSecurity({ ...editingSecurity, isin: e.target.value })}
className="w-full px-3 py-2 rounded-xl bg-slate-950 border border-slate-700 font-mono text-cyan-300 text-sm"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
Security Description
</label>
<input
type="text"
required
placeholder="e.g. GOP EuroBond 2029"
value={editingSecurity.securityType || ''}
onChange={(e) => setEditingSecurity({ ...editingSecurity, securityType: e.target.value })}
className="w-full px-3 py-2 rounded-xl bg-slate-950 border border-slate-700 text-slate-200 text-sm"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
Deal Currency
</label>
<select
value={editingSecurity.currency || 'USD'}
onChange={(e) =>
setEditingSecurity({
...editingSecurity,
currency: e.target.value,
})
}
className="w-full px-3 py-2 rounded-xl bg-slate-950 border border-slate-700 text-slate-200 text-sm font-mono"
>
<option value="USD">USD - US Dollar</option>
<option value="PKR">PKR - Pakistani Rupee</option>
<option value="EUR">EUR - Euro</option>
<option value="GBP">GBP - British Pound</option>
<option value="JPY">JPY - Japanese Yen</option>
<option value="CAD">CAD - Canadian Dollar</option>
<option value="SAR">SAR - Saudi Riyal</option>
<option value="AED">AED - UAE Dirham</option>
<option value="BDT">BDT - Bangladeshi Taka</option>
<option value="CNY">CNY - Chinese Yuan</option>
<option value="HKD">HKD - Hong Kong Dollar</option>
</select>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
Face Value (Mio in Deal Currency)
</label>
<input
type="number"
step="0.01"
required
placeholder="e.g. 10.00"
value={editingSecurity.faceValue ?? ''}
onChange={(e) => setEditingSecurity({ ...editingSecurity, faceValue: Number(e.target.value) })}
className="w-full px-3 py-2 rounded-xl bg-slate-950 border border-slate-700 font-mono text-slate-100 text-sm"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
Amount Invested / Book Value (USD Mio)
</label>
<input
type="number"
step="0.01"
required
placeholder="e.g. 10.17"
value={editingSecurity.amountInvested ?? ''}
onChange={(e) =>
setEditingSecurity({ ...editingSecurity, amountInvested: Number(e.target.value) })
}
className="w-full px-3 py-2 rounded-xl bg-slate-950 border border-slate-700 font-mono text-emerald-400 text-sm"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
Coupon Rate (%)
</label>
<input
type="number"
step="0.01"
placeholder="e.g. 7.38"
value={editingSecurity.couponRate ?? ''}
onChange={(e) => setEditingSecurity({ ...editingSecurity, couponRate: Number(e.target.value) })}
className="w-full px-3 py-2 rounded-xl bg-slate-950 border border-slate-700 font-mono text-slate-200 text-sm"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
Original Price (% of Par)
</label>
<input
type="number"
step="0.001"
placeholder="e.g. 101.700"
value={editingSecurity.originalPrice ?? ''}
onChange={(e) =>
setEditingSecurity({ ...editingSecurity, originalPrice: Number(e.target.value) })
}
className="w-full px-3 py-2 rounded-xl bg-slate-950 border border-slate-700 font-mono text-slate-200 text-sm"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
Purchase / Issue Date
</label>
<input
type="text"
placeholder="e.g. 13-Jul-21"
value={editingSecurity.purchaseDate || ''}
onChange={(e) => setEditingSecurity({ ...editingSecurity, purchaseDate: e.target.value })}
className="w-full px-3 py-2 rounded-xl bg-slate-950 border border-slate-700 text-slate-200 text-sm"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
Maturity Date
</label>
<input
type="text"
placeholder="e.g. 08-Apr-31"
value={editingSecurity.maturityDate || ''}
onChange={(e) => setEditingSecurity({ ...editingSecurity, maturityDate: e.target.value })}
className="w-full px-3 py-2 rounded-xl bg-slate-950 border border-slate-700 text-slate-200 text-sm"
/>
</div>
</div>
{isHO && (
<div className="p-4 rounded-xl bg-slate-950 border border-slate-800 space-y-3 mt-2">
<div className="text-xs font-semibold text-cyan-400">Head Office Valuation (MTM)</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-[11px] text-slate-400 mb-1">MTM Price (%)</label>
<input
type="number"
step="0.001"
placeholder="e.g. 100.380"
value={editingSecurity.mtmPrice ?? ''}
onChange={(e) =>
setEditingSecurity({ ...editingSecurity, mtmPrice: Number(e.target.value) })
}
className="w-full px-3 py-1.5 rounded-lg bg-slate-900 border border-slate-700 font-mono text-cyan-300 text-xs"
/>
</div>
<div>
<label className="block text-[11px] text-slate-400 mb-1">MTM Yield (%)</label>
<input
type="number"
step="0.01"
placeholder="e.g. 7.28"
value={editingSecurity.mtmYield ?? ''}
onChange={(e) =>
setEditingSecurity({ ...editingSecurity, mtmYield: Number(e.target.value) })
}
className="w-full px-3 py-1.5 rounded-lg bg-slate-900 border border-slate-700 font-mono text-purple-300 text-xs"
/>
</div>
</div>
</div>
)}
<div className="flex items-center justify-end gap-3 pt-4 border-t border-slate-800">
<button
type="button"
onClick={() => setIsModalOpen(false)}
className="px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-300 text-sm"
>
Cancel
</button>
<button
type="submit"
className="px-5 py-2 rounded-xl bg-gradient-to-r from-cyan-500 to-blue-600 hover:from-cyan-400 hover:to-blue-500 text-white font-semibold text-sm shadow-lg shadow-cyan-500/20"
>
Save Security
</button>
</div>
</form>
</div>
</div>
)}
</div>
);
};