import React, { useState, useMemo } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { FileText, Plus, Search, Filter, RefreshCw, Edit2, Eye, Layers, Calendar, User as UserIcon, Tag, CheckCircle2, AlertCircle, Coins, } from 'lucide-react'; import { Modal } from '../ui/Modal'; import { HelpTooltip } from '../ui/HelpTooltip'; import { useToast } from '../ui/Toast'; import { ExportButton } from '../ui/ExportButton'; import { ExportColumn } from '../../utils/exportUtils'; import { useTableSort, SortableTh } from '../../utils/tableSort'; import { Instrument, Instruction, Programme, ActivityStatus, } from '../../types'; import { getAllInstruments, createInstrument, updateInstrument, } from '../../services/api'; import { getActivityStatusBadge, formatDate, getUserEmail, ACTIVITY_STATUS_OPTIONS, } from '../../lib/formatters'; const instrumentSchema = z.object({ programmeId: z.coerce.number().min(1, 'Please select an associated programme'), name: z.string().min(3, 'Funding instrument title must be at least 3 characters'), code: z.string().optional(), description: z.string().min(5, 'Description is required (minimum 5 characters)'), status: z.coerce.number(), }); type InstrumentFormValues = z.infer; interface FundingInstrumentsSectionProps { programmes: Programme[]; currentUserId: string; preselectedProgrammeId?: string | number | null; onClearPreselectedProgramme?: () => void; isCreateModalOpenExternal?: boolean; onCloseCreateModalExternal?: () => void; } export const FundingInstrumentsSection: React.FC = ({ programmes, currentUserId, preselectedProgrammeId, onClearPreselectedProgramme, isCreateModalOpenExternal, onCloseCreateModalExternal, }) => { const queryClient = useQueryClient(); const { success, error: toastError } = useToast(); const [searchTerm, setSearchTerm] = useState(''); const [selectedProgrammeFilter, setSelectedProgrammeFilter] = useState( preselectedProgrammeId ? String(preselectedProgrammeId) : 'all' ); const [statusFilter, setStatusFilter] = useState('all'); // Modals const [isModalOpen, setIsModalOpen] = useState(false); const [editingInstrument, setEditingInstrument] = useState(null); const [inspectingInstrument, setInspectingInstrument] = useState(null); // Form setup const instrumentForm = useForm({ resolver: zodResolver(instrumentSchema), defaultValues: { programmeId: programmes[0]?.id ? Number(programmes[0].id) : 0, name: '', code: '', description: '', status: ActivityStatus.Active, }, }); // Sync external create modal trigger if provided React.useEffect(() => { if (isCreateModalOpenExternal) { setEditingInstrument(null); instrumentForm.reset({ programmeId: preselectedProgrammeId ? Number(preselectedProgrammeId) : (programmes[0]?.id ? Number(programmes[0].id) : 0), name: '', code: '', description: '', status: ActivityStatus.Active, }); setIsModalOpen(true); } }, [isCreateModalOpenExternal, preselectedProgrammeId, programmes, instrumentForm]); // Query: Get all funding instruments primarily from API const { data: instruments = [], isLoading, isFetching, refetch, } = useQuery({ queryKey: ['all-instruments', programmes], queryFn: () => getAllInstruments(programmes), }); // Programme lookup helper const programmeMap = useMemo(() => { const map = new Map(); programmes.forEach((p) => map.set(String(p.id), p)); return map; }, [programmes]); // Mutation: Create or Update Funding Instrument const mutation = useMutation({ mutationFn: async (data: InstrumentFormValues) => { if (editingInstrument) { await updateInstrument({ instructionId: editingInstrument.id, instrumentId: editingInstrument.id, id: editingInstrument.id, programmeId: data.programmeId, code: data.code, name: data.name, description: data.description, status: data.status !== undefined ? Number(data.status) : ActivityStatus.Active, userId: currentUserId, }); } else { await createInstrument({ programmeId: data.programmeId, code: data.code, name: data.name, description: data.description, status: data.status !== undefined ? Number(data.status) : ActivityStatus.Active, userId: currentUserId, }); } }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['all-instruments'] }); queryClient.invalidateQueries({ queryKey: ['all-instructions'] }); queryClient.invalidateQueries({ queryKey: ['instruments'] }); success( editingInstrument ? 'Funding Instrument Updated' : 'Funding Instrument Created', editingInstrument ? 'Funding instrument record updated successfully via the API.' : 'New funding instrument created and linked to strategic programme successfully.' ); setIsModalOpen(false); setEditingInstrument(null); if (onCloseCreateModalExternal) onCloseCreateModalExternal(); }, onError: (err: unknown) => { toastError('Save Failed', err); }, }); // Handlers const openCreateModal = (programmeId?: number | string) => { setEditingInstrument(null); instrumentForm.reset({ programmeId: programmeId ? Number(programmeId) : selectedProgrammeFilter !== 'all' ? Number(selectedProgrammeFilter) : programmes[0]?.id ? Number(programmes[0].id) : 0, name: '', code: '', description: '', status: ActivityStatus.Active, }); setIsModalOpen(true); }; const openEditModal = (inst: Instrument) => { setEditingInstrument(inst); instrumentForm.reset({ programmeId: Number(inst.programmeId), name: inst.name || '', code: inst.code || '', description: inst.description || '', status: Number(inst.status), }); setIsModalOpen(true); }; const handleCloseModal = () => { setIsModalOpen(false); setEditingInstrument(null); if (onCloseCreateModalExternal) onCloseCreateModalExternal(); }; // Filter funding instruments const filteredInstruments = useMemo(() => { return instruments.filter((inst) => { // Programme filter if (selectedProgrammeFilter !== 'all' && String(inst.programmeId) !== String(selectedProgrammeFilter)) { return false; } // Status filter if (statusFilter !== 'all' && String(inst.status) !== String(statusFilter)) { return false; } // Search term if (searchTerm.trim()) { const query = searchTerm.toLowerCase(); const pName = programmeMap.get(String(inst.programmeId))?.name?.toLowerCase() || ''; const matchTitle = (inst.name || '').toLowerCase().includes(query); const matchCode = (inst.code || '').toLowerCase().includes(query); const matchDesc = (inst.description || '').toLowerCase().includes(query); const matchProg = pName.includes(query); if (!matchTitle && !matchCode && !matchDesc && !matchProg) { return false; } } return true; }); }, [instruments, selectedProgrammeFilter, statusFilter, searchTerm, programmeMap]); // Sorting hook const { sortedData: sortedInstruments, sortColumn: instSortCol, sortDirection: instSortDir, handleSort: handleInstSort, } = useTableSort(filteredInstruments, null, null, { code: (inst: Instrument) => inst.code || `INST-${inst.id}`, name: (inst: Instrument) => inst.name, programmeId: (inst: Instrument) => programmeMap.get(String(inst.programmeId))?.name || `Programme #${inst.programmeId}`, status: (inst: Instrument) => getActivityStatusBadge(inst.status).label, createdAt: (inst: Instrument) => inst.createdAt, }); // Export columns definition const instrumentExportColumns: ExportColumn[] = useMemo( () => [ { header: 'Instrument ID', key: 'id' }, { header: 'Instrument Code', key: 'code', format: (val) => val || '—' }, { header: 'Title / Name', key: 'name', format: (val) => val || 'Untitled' }, { header: 'Strategic Programme', key: 'programmeId', format: (val) => programmeMap.get(String(val))?.name || `Programme #${val}`, }, { header: 'Status', key: 'status', format: (val) => (Number(val) === ActivityStatus.Active ? 'Active' : 'Inactive'), }, { header: 'Description', key: 'description', format: (val) => val || '—' }, { header: 'Created Date', key: 'createdAt', format: (val) => (val ? formatDate(val) : '—') }, ], [programmeMap] ); return (
{/* Top Filter and Actions Bar */}
{/* Search Input */}
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" />
{/* Programme Filter Dropdown */}
{/* Status Filter Dropdown */}
{/* Action Buttons: Refresh, Export & Add */}
{/* Funding Instruments Table & Cards */}

Funding Instruments & Financing Directives ({filteredInstruments.length})

{filteredInstruments.length} of {instruments.length} recorded
{isLoading ? (
Loading funding instruments directory...
) : filteredInstruments.length === 0 ? (
{searchTerm || selectedProgrammeFilter !== 'all' || statusFilter !== 'all' ? 'No funding instruments match the active filters.' : 'No funding instruments registered yet. Link funding instruments to programmes using the button below.'}
) : (
Code / Ref Instrument Title Associated Programme Status Created Info {sortedInstruments.map((inst) => { const statusBadge = getActivityStatusBadge(inst.status); const parentProg = programmeMap.get(String(inst.programmeId)); return ( ); })}
Actions
{inst.code || `INST-${inst.id}`}
{inst.name}
{inst.description && (
{inst.description}
)}
{parentProg ? ( {parentProg.name} ) : ( Programme #{inst.programmeId} )} {statusBadge.label}
{formatDate(inst.createdAt)}
{inst.createdBy && (
{getUserEmail(inst.createdBy)}
)}
)}
{/* Modal: Create / Edit Funding Instrument */}
mutation.mutate(data))} className="space-y-4" > {/* Associated Programme Dropdown */}
{instrumentForm.formState.errors.programmeId && (

{instrumentForm.formState.errors.programmeId.message}

)}
{/* Title */}
{instrumentForm.formState.errors.name && (

{instrumentForm.formState.errors.name.message}

)}
{/* Code */}
{/* Description */}