Implemented data export

This commit is contained in:
2026-09-26 13:45:42 +02:00
parent 15a8d8f449
commit 2949d6218b
12 changed files with 10939 additions and 359 deletions
@@ -29,6 +29,7 @@ import {
updateOrganisation,
getAllPortfolios,
getPortfolios,
getOrganisationPortfolios,
getPortfolio,
createPortfolio,
updatePortfolio,
@@ -39,8 +40,8 @@ import {
updateProgramme,
getInstruments,
getInstructions,
createInstruction,
updateInstruction,
createInstrument,
updateInstrument,
getRules,
createRule,
updateRule,
@@ -51,8 +52,10 @@ import {
import { Modal } from '../components/ui/Modal';
import { HelpTooltip } from '../components/ui/HelpTooltip';
import { useToast } from '../components/ui/Toast';
import { ExportButton } from '../components/ui/ExportButton';
import { ExportColumn } from '../utils/exportUtils';
import { useAuth } from '../auth/AuthProvider';
import { InstructionsSection } from '../components/governance/InstructionsSection';
import { FundingInstrumentsSection, InstructionsSection } from '../components/governance/InstructionsSection';
import { RulesSection } from '../components/governance/RulesSection';
import {
Organisation,
@@ -133,26 +136,27 @@ export const GovernancePage: React.FC = () => {
const [searchParams, setSearchParams] = useSearchParams();
const tabParam = searchParams.get('tab');
const getValidTab = (val: string | null): 'organisations' | 'portfolios' | 'programmes' | 'instructions' | 'rules' => {
if (val === 'portfolios' || val === 'programmes' || val === 'organisations' || val === 'instructions' || val === 'rules') {
return val;
const getValidTab = (val: string | null): 'organisations' | 'portfolios' | 'programmes' | 'instruments' | 'instructions' | 'rules' => {
if (val === 'portfolios' || val === 'programmes' || val === 'organisations' || val === 'instruments' || val === 'instructions' || val === 'rules') {
return val === 'instructions' ? 'instruments' : val;
}
return 'organisations';
};
const [activeTab, setActiveTab] = useState<'organisations' | 'portfolios' | 'programmes' | 'instructions' | 'rules'>(() => getValidTab(tabParam));
const [activeTab, setActiveTab] = useState<'organisations' | 'portfolios' | 'programmes' | 'instruments' | 'instructions' | 'rules'>(() => getValidTab(tabParam));
useEffect(() => {
if (tabParam && (tabParam === 'organisations' || tabParam === 'portfolios' || tabParam === 'programmes' || tabParam === 'instructions' || tabParam === 'rules')) {
setActiveTab(tabParam);
if (tabParam && (tabParam === 'organisations' || tabParam === 'portfolios' || tabParam === 'programmes' || tabParam === 'instruments' || tabParam === 'instructions' || tabParam === 'rules')) {
setActiveTab(tabParam === 'instructions' ? 'instruments' : tabParam);
}
}, [tabParam]);
const handleTabChange = (tab: 'organisations' | 'portfolios' | 'programmes' | 'instructions' | 'rules') => {
setActiveTab(tab);
const handleTabChange = (tab: 'organisations' | 'portfolios' | 'programmes' | 'instruments' | 'instructions' | 'rules') => {
const canonicalTab = tab === 'instructions' ? 'instruments' : tab;
setActiveTab(canonicalTab);
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
next.set('tab', tab);
next.set('tab', canonicalTab);
return next;
});
};
@@ -219,17 +223,32 @@ export const GovernancePage: React.FC = () => {
staleTime: 5 * 60 * 1000,
});
const { data: organisations = [], isLoading: loadingOrgs } = useQuery({
const {
data: organisations = [],
isLoading: loadingOrgs,
isFetching: isFetchingOrgs,
refetch: refetchOrgs,
} = useQuery({
queryKey: ['organisations'],
queryFn: () => getOrganisations(),
});
const { data: portfolios = [], isLoading: loadingPortfolios } = useQuery({
const {
data: portfolios = [],
isLoading: loadingPortfolios,
isFetching: isFetchingPortfolios,
refetch: refetchPortfolios,
} = useQuery({
queryKey: ['portfolios'],
queryFn: () => getAllPortfolios(),
});
const { data: programmes = [], isLoading: loadingProgrammes } = useQuery({
const {
data: programmes = [],
isLoading: loadingProgrammes,
isFetching: isFetchingProgrammes,
refetch: refetchProgrammes,
} = useQuery({
queryKey: ['programmes'],
queryFn: () => getAllProgrammes(),
});
@@ -278,7 +297,7 @@ export const GovernancePage: React.FC = () => {
const { data: orgPortfolios = [], isLoading: loadingOrgPortfolios } = useQuery({
queryKey: ['organisation-portfolios', selectedOrgForDetail?.id],
queryFn: () => (selectedOrgForDetail ? getPortfolios(selectedOrgForDetail.id) : Promise.resolve([])),
queryFn: () => (selectedOrgForDetail ? getOrganisationPortfolios(selectedOrgForDetail.id) : Promise.resolve([])),
enabled: !!selectedOrgForDetail && isOrgDetailModalOpen,
});
@@ -575,9 +594,81 @@ export const GovernancePage: React.FC = () => {
(o) =>
o.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
o.registrationNo?.toLowerCase().includes(searchTerm.toLowerCase()) ||
o.email?.toLowerCase().includes(searchTerm.toLowerCase())
o.email?.toLowerCase().includes(searchTerm.toLowerCase()) ||
String(o.id).includes(searchTerm)
);
const filteredPortfolios = portfolios.filter((p) => {
if (!searchTerm.trim()) return true;
const q = searchTerm.toLowerCase();
const org = organisations.find((o) => String(o.id) === String(p.organisationId));
const orgName = org?.name?.toLowerCase() || '';
const name = (p.name || '').toLowerCase();
const desc = (p.description || '').toLowerCase();
const id = String(p.id);
const owner = getUserEmail(p.ownedBy).toLowerCase();
return name.includes(q) || desc.includes(q) || id.includes(q) || orgName.includes(q) || owner.includes(q);
});
const filteredProgrammes = programmes.filter((pr) => {
if (!searchTerm.trim()) return true;
const q = searchTerm.toLowerCase();
const port = portfolios.find((p) => String(p.id) === String(pr.portfolioId));
const portName = port?.name?.toLowerCase() || '';
const name = (pr.name || '').toLowerCase();
const id = String(pr.id);
const owner = getUserEmail(pr.ownedBy).toLowerCase();
return name.includes(q) || id.includes(q) || portName.includes(q) || owner.includes(q);
});
// Export Columns Definitions
const orgExportColumns: ExportColumn<Organisation>[] = [
{ header: 'Organisation ID', key: 'id' },
{ header: 'Legal Entity Name', key: 'name' },
{ header: 'Registration / CIPC No', key: 'registrationNo', format: (val) => val || '—' },
{ header: 'Entity Classification', key: 'type', format: (val) => getOrganisationTypeLabel(val) },
{ header: 'Official Email', key: 'email', format: (val) => val || '—' },
{ header: 'Status', key: 'status', format: (val) => getOrganisationStatusBadge({ status: val }).label },
{ header: 'Registration Date', key: 'createdAt', format: (val) => formatDate(val) },
];
const portfolioExportColumns: ExportColumn<Portfolio>[] = [
{ header: 'Portfolio ID', key: 'id' },
{ header: 'Portfolio Name', key: 'name' },
{
header: 'Organisation',
key: 'organisationId',
format: (val) => {
const org = organisations.find((o) => String(o.id) === String(val));
return org ? org.name : `Org #${val}`;
},
},
{
header: 'Programmes Count',
key: 'id',
format: (id) => programmes.filter((pr) => String(pr.portfolioId) === String(id)).length,
},
{ header: 'Owner Email', key: 'ownedBy', format: (val) => getUserEmail(val) },
{ header: 'Creation Date', key: 'createdAt', format: (val) => formatDate(val) },
{ header: 'Description', key: 'description', format: (val) => val || '—' },
];
const programmeExportColumns: ExportColumn<Programme>[] = [
{ header: 'Programme ID', key: 'id' },
{ header: 'Programme Name', key: 'name' },
{
header: 'Parent Portfolio',
key: 'portfolioId',
format: (val) => {
const port = portfolios.find((p) => String(p.id) === String(val));
return port ? port.name : `Portfolio #${val}`;
},
},
{ header: 'Governance Status', key: 'status', format: (val) => getApprovalStatusBadge(val).label },
{ header: 'Creation Date', key: 'createdAt', format: (val) => formatDate(val) },
{ header: 'Owner Email', key: 'ownedBy', format: (val) => getUserEmail(val) },
];
return (
<div className="space-y-6 max-w-7xl mx-auto">
{/* Header */}
@@ -597,7 +688,7 @@ export const GovernancePage: React.FC = () => {
/>
</div>
<p className="text-xs text-slate-400 mt-1">
Maintain Master Organisation Registry, Funder Portfolios, Strategic Programmes, Funding Instructions, and Programme Rules.
Maintain Master Organisation Registry, Funder Portfolios, Strategic Programmes, Funding Instruments, and Programme Rules.
</p>
</div>
@@ -630,13 +721,13 @@ export const GovernancePage: React.FC = () => {
Create Programme
</button>
)}
{activeTab === 'instructions' && (
{(activeTab === 'instruments' || activeTab === 'instructions') && (
<button
onClick={() => setIsInstructionCreateTriggered(true)}
className="inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-md shadow-blue-500/20 transition cursor-pointer"
>
<Plus className="w-4 h-4" />
Add Instruction
Add Funding Instrument
</button>
)}
{activeTab === 'rules' && (
@@ -705,18 +796,18 @@ export const GovernancePage: React.FC = () => {
/>
</button>
<button
onClick={() => handleTabChange('instructions')}
onClick={() => handleTabChange('instruments')}
className={`py-3.5 px-4 text-xs font-semibold border-b-2 transition-colors flex items-center gap-2 cursor-pointer whitespace-nowrap ${
activeTab === 'instructions'
activeTab === 'instruments' || activeTab === 'instructions'
? 'border-blue-500 text-blue-400'
: 'border-transparent text-slate-400 hover:text-slate-200'
}`}
>
<FileText className="w-4 h-4" />
<span>Funding Instructions</span>
<Coins className="w-4 h-4" />
<span>Funding Instruments</span>
<HelpTooltip
title="Funding Instructions"
content="Operational directives and funding instrument guidelines attached to specific strategic programmes."
title="Funding Instruments"
content="Binding funding instruments and financing directives attached to specific strategic funding programmes."
size="sm"
placement="top"
/>
@@ -740,31 +831,41 @@ export const GovernancePage: React.FC = () => {
</button>
</div>
{/* Search Bar & Actions */}
{/* Search Bar & Actions for Organisations */}
{activeTab === 'organisations' && (
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
<div className="relative max-w-md flex-1">
<Search className="w-4 h-4 text-slate-500 absolute left-3 top-3" />
<input
type="text"
placeholder="Search by organisation name, reg no, or email..."
placeholder="Search by organisation name, reg no, email, or ID..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-9 pr-4 py-2.5 text-xs bg-[#090d16] border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-slate-200 placeholder:text-slate-500 transition shadow-xs"
/>
</div>
<button
type="button"
onClick={() => {
queryClient.invalidateQueries({ queryKey: ['organisations'] });
success('Refreshed', 'Organisation directory refreshed from database.');
}}
className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 text-xs font-semibold text-slate-300 bg-[#0d121f] hover:bg-slate-800 border border-slate-700/80 rounded-xl transition cursor-pointer"
title="Force refresh organisation directory from database"
>
<RefreshCw className="w-3.5 h-3.5 text-blue-400" />
Refresh Directory
</button>
<div className="flex items-center gap-2 self-end sm:self-center">
<button
type="button"
onClick={() => {
refetchOrgs();
success('Refreshed', 'Organisation directory refreshed from API.');
}}
disabled={isFetchingOrgs}
className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 text-xs font-semibold text-slate-300 bg-[#0d121f] hover:bg-slate-800 border border-slate-700/80 rounded-xl transition cursor-pointer shadow-xs disabled:opacity-50"
title="Force refresh organisation directory from API"
>
<RefreshCw className={`w-3.5 h-3.5 text-blue-400 ${isFetchingOrgs ? 'animate-spin' : ''}`} />
<span>Refresh Directory</span>
</button>
<ExportButton
title="Organisation Master Registry"
subtitle="Official directory of verified recipient and funder entities"
filename={`pfm-organisations-${new Date().toISOString().split('T')[0]}`}
columns={orgExportColumns}
data={filteredOrgs}
/>
</div>
</div>
)}
@@ -855,13 +956,57 @@ export const GovernancePage: React.FC = () => {
</div>
)}
{/* Search Bar & Actions for Portfolios */}
{activeTab === 'portfolios' && (
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
<div className="relative max-w-md flex-1">
<Search className="w-4 h-4 text-slate-500 absolute left-3 top-3" />
<input
type="text"
placeholder="Search portfolios by name, ID, organisation, or owner..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-9 pr-4 py-2.5 text-xs bg-[#090d16] border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-slate-200 placeholder:text-slate-500 transition shadow-xs"
/>
</div>
<div className="flex items-center gap-2 self-end sm:self-center">
<button
type="button"
onClick={() => {
refetchPortfolios();
success('Refreshed', 'Funder portfolios refreshed from API.');
}}
disabled={isFetchingPortfolios}
className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 text-xs font-semibold text-slate-300 bg-[#0d121f] hover:bg-slate-800 border border-slate-700/80 rounded-xl transition cursor-pointer shadow-xs disabled:opacity-50"
title="Force refresh portfolios from API"
>
<RefreshCw className={`w-3.5 h-3.5 text-blue-400 ${isFetchingPortfolios ? 'animate-spin' : ''}`} />
<span>Refresh Portfolios</span>
</button>
<ExportButton
title="Funder Portfolios Registry"
subtitle="High-level funding groupings and thematic capital allocations"
filename={`pfm-portfolios-${new Date().toISOString().split('T')[0]}`}
columns={portfolioExportColumns}
data={filteredPortfolios}
/>
</div>
</div>
)}
{/* Tab 2: Portfolios List */}
{activeTab === 'portfolios' && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
{loadingPortfolios ? (
<p className="text-xs text-slate-500 col-span-3">Loading portfolios...</p>
<p className="text-xs text-slate-500 col-span-3 text-center py-8">Loading portfolios...</p>
) : filteredPortfolios.length === 0 ? (
<div className="col-span-3 py-12 text-center bg-[#0f1523]/80 rounded-2xl border border-slate-800/80 p-8">
<FolderKanban className="w-8 h-8 text-slate-600 mx-auto mb-2" />
<p className="text-sm font-semibold text-slate-300">No portfolios found matching query</p>
<p className="text-xs text-slate-500 mt-1">Try refining your search keywords or create a new portfolio.</p>
</div>
) : (
portfolios.map((p) => {
filteredPortfolios.map((p) => {
const org = organisations.find((o) => String(o.id) === String(p.organisationId));
const progCount = programmes.filter((pr) => String(pr.portfolioId) === String(p.id)).length;
const isHighlighted = String(highlightedPortfolioId) === String(p.id);
@@ -942,6 +1087,44 @@ export const GovernancePage: React.FC = () => {
</div>
)}
{/* Search Bar & Actions for Programmes */}
{activeTab === 'programmes' && (
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
<div className="relative max-w-md flex-1">
<Search className="w-4 h-4 text-slate-500 absolute left-3 top-3" />
<input
type="text"
placeholder="Search programmes by name, ID, parent portfolio, or owner..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-9 pr-4 py-2.5 text-xs bg-[#090d16] border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-slate-200 placeholder:text-slate-500 transition shadow-xs"
/>
</div>
<div className="flex items-center gap-2 self-end sm:self-center">
<button
type="button"
onClick={() => {
refetchProgrammes();
success('Refreshed', 'Programmes hierarchy refreshed from API.');
}}
disabled={isFetchingProgrammes}
className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 text-xs font-semibold text-slate-300 bg-[#0d121f] hover:bg-slate-800 border border-slate-700/80 rounded-xl transition cursor-pointer shadow-xs disabled:opacity-50"
title="Force refresh programmes from API"
>
<RefreshCw className={`w-3.5 h-3.5 text-blue-400 ${isFetchingProgrammes ? 'animate-spin' : ''}`} />
<span>Refresh Programmes</span>
</button>
<ExportButton
title="Strategic Programmes Hierarchy"
subtitle="Operational funding vehicles linked to funder portfolios"
filename={`pfm-programmes-${new Date().toISOString().split('T')[0]}`}
columns={programmeExportColumns}
data={filteredProgrammes}
/>
</div>
</div>
)}
{/* Tab 3: Programmes Hierarchy */}
{activeTab === 'programmes' && (
<div className="bg-[#0f1523]/80 rounded-2xl border border-slate-800/80 shadow-lg backdrop-blur-sm overflow-hidden">
@@ -964,8 +1147,14 @@ export const GovernancePage: React.FC = () => {
Loading programmes...
</td>
</tr>
) : filteredProgrammes.length === 0 ? (
<tr>
<td colSpan={6} className="py-8 text-center text-slate-500">
No programmes found matching query.
</td>
</tr>
) : (
programmes.map((pr) => {
filteredProgrammes.map((pr) => {
const port = portfolios.find((p) => String(p.id) === String(pr.portfolioId));
const statusBadge = getApprovalStatusBadge(pr.status);
return (
@@ -1007,10 +1196,10 @@ export const GovernancePage: React.FC = () => {
setIsInstrumentsModalOpen(true);
}}
className="inline-flex items-center gap-1 text-xs font-semibold text-blue-400 hover:text-white bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 px-2.5 py-1 rounded-lg transition shadow-xs cursor-pointer"
title="View Funding Instructions"
title="View Funding Instruments"
>
<FileText className="w-3 h-3" />
Instructions
<Coins className="w-3 h-3" />
Instruments
</button>
<button
type="button"
@@ -1044,9 +1233,9 @@ export const GovernancePage: React.FC = () => {
</div>
)}
{/* Tab 4: Funding Instructions */}
{activeTab === 'instructions' && (
<InstructionsSection
{/* Tab 4: Funding Instruments */}
{(activeTab === 'instruments' || activeTab === 'instructions') && (
<FundingInstrumentsSection
programmes={programmes}
currentUserId={currentUserId}
preselectedProgrammeId={preselectedProgForInstruction}
@@ -1476,26 +1665,26 @@ export const GovernancePage: React.FC = () => {
</form>
</Modal>
{/* Modal: View Funding Instructions */}
{/* Modal: View Funding Instruments */}
<Modal
isOpen={isInstrumentsModalOpen}
onClose={() => {
setIsInstrumentsModalOpen(false);
setSelectedProgrammeForInstruments(null);
}}
title={`Funding Instructions: ${selectedProgrammeForInstruments?.name || ''}`}
subtitle={`Authoritative funding instructions linked to Programme #${selectedProgrammeForInstruments?.id || ''}`}
title={`Funding Instruments: ${selectedProgrammeForInstruments?.name || ''}`}
subtitle={`Authoritative funding instruments linked to Programme #${selectedProgrammeForInstruments?.id || ''}`}
>
<div className="space-y-4">
{loadingInstruments ? (
<div className="py-8 text-center text-xs text-slate-400 flex items-center justify-center gap-2">
<RefreshCw className="w-4 h-4 animate-spin text-blue-400" />
<span>Loading funding instructions...</span>
<span>Loading funding instruments...</span>
</div>
) : programmeInstruments.length === 0 ? (
<div className="py-8 text-center text-xs text-slate-400 space-y-3">
<div className="text-slate-500">
No funding instructions registered under this programme yet.
No funding instruments registered under this programme yet.
</div>
<button
type="button"
@@ -1505,12 +1694,12 @@ export const GovernancePage: React.FC = () => {
setSelectedProgrammeForInstruments(null);
setPreselectedProgForInstruction(pId || null);
setIsInstructionCreateTriggered(true);
handleTabChange('instructions');
handleTabChange('instruments');
}}
className="inline-flex items-center gap-1.5 px-3.5 py-2 text-xs font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded-xl transition cursor-pointer shadow-md shadow-blue-500/20"
>
<Plus className="w-3.5 h-3.5" />
Add Instruction to this Programme
Add Instrument to this Programme
</button>
</div>
) : (
@@ -1540,10 +1729,10 @@ export const GovernancePage: React.FC = () => {
setIsInstrumentsModalOpen(false);
setSelectedProgrammeForInstruments(null);
setPreselectedProgForInstruction(pId || null);
handleTabChange('instructions');
handleTabChange('instruments');
}}
className="inline-flex items-center gap-1 text-[11px] font-semibold text-slate-300 hover:text-white bg-slate-800 hover:bg-slate-700 border border-slate-700 px-2 py-0.5 rounded cursor-pointer"
title="Manage in Instructions Tab"
title="Manage in Funding Instruments Tab"
>
<Edit2 className="w-3 h-3" />
Manage
@@ -1571,12 +1760,12 @@ export const GovernancePage: React.FC = () => {
setSelectedProgrammeForInstruments(null);
setPreselectedProgForInstruction(pId || null);
setIsInstructionCreateTriggered(true);
handleTabChange('instructions');
handleTabChange('instruments');
}}
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold text-blue-400 hover:text-white bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 rounded-xl transition cursor-pointer"
>
<Plus className="w-3.5 h-3.5" />
Add Instruction
Add Funding Instrument
</button>
<button
type="button"
@@ -1765,10 +1954,10 @@ export const GovernancePage: React.FC = () => {
setIsInstrumentsModalOpen(true);
}}
className="inline-flex items-center gap-1 text-[11px] font-semibold text-blue-400 hover:text-white bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 px-2 py-1 rounded-lg cursor-pointer"
title="View Funding Instructions"
title="View Funding Instruments"
>
<FileText className="w-3 h-3" />
Instructions
<Coins className="w-3 h-3" />
Instruments
</button>
<button
type="button"
@@ -2067,12 +2256,12 @@ export const GovernancePage: React.FC = () => {
</button>
</div>
{/* Total Instructions */}
{/* Total Funding Instruments */}
<div className="p-3 bg-gradient-to-br from-blue-500/10 to-blue-500/5 rounded-xl border border-blue-500/20 flex flex-col justify-between">
<div className="flex items-center justify-between mb-1">
<span className="text-[11px] font-semibold text-blue-300 flex items-center gap-1.5">
<FileText className="w-3.5 h-3.5 text-blue-400" />
Total Instructions
<Coins className="w-3.5 h-3.5 text-blue-400" />
Total Funding Instruments
</span>
<span className="text-lg font-bold font-mono text-blue-400">
{loadingDetailInstructions ? (
@@ -2089,11 +2278,11 @@ export const GovernancePage: React.FC = () => {
setIsProgrammeDetailModalOpen(false);
setSelectedProgrammeForDetail(null);
setPreselectedProgForInstruction(pId || null);
handleTabChange('instructions');
handleTabChange('instruments');
}}
className="text-[11px] font-semibold text-blue-400 hover:text-blue-300 inline-flex items-center gap-1 mt-1 transition cursor-pointer self-start"
>
View in Instructions Tab &rarr;
View in Funding Instruments Tab &rarr;
</button>
</div>
</div>