'use client'; import { useEffect, useState } from 'react'; import { Check, Link2, Trash2, X } from 'lucide-react'; import { useAuth } from '@/lib/hooks/useAuth'; import { useOrganizationInviteLinkCopy } from '@/lib/hooks/useOrganizationInviteLinkCopy'; import { organizationApi, type CounterpartItemDto, type CounterpartSearchResultDto, type OrganizationInvitationHistoryItemDto, } from '@/lib/api/organization'; import { InvitationHistoryDialog } from '@/components/ui/organizations/InvitationHistoryDialog'; import { Button } from '@/components/ui/common/Button'; import { Badge, organizationLinkStatusVariant } from '@/components/ui/common/Badge'; import { Input } from '@/components/ui/common/Input'; import { SearchBar } from '@/components/ui/common/SearchBar'; import { Table } from '@/components/ui/common/Table'; import type { ApiError } from '@/types/api'; function formatOrganizationStatusLabel(status: string): string { if (!status) return status; const lower = status.toLowerCase(); return lower.charAt(0).toUpperCase() + lower.slice(1); } function formatLinkStatusLabel(status: CounterpartItemDto['status']): string { if (status === 'PENDING') return 'Link request pending'; if (status === 'ACTIVE') return 'Linked'; if (status === 'REJECTED') return 'Link request rejected'; return formatOrganizationStatusLabel(status); } function formatApiMessage(err: unknown): string { if (!err || typeof err !== 'object') return 'Something went wrong'; const m = (err as ApiError).message; if (Array.isArray(m)) return m.join(', '); if (typeof m === 'string') return m; return 'Something went wrong'; } function formatTableDate(value: string): string { const d = new Date(value); if (Number.isNaN(d.getTime())) return '—'; return d.toLocaleDateString(); } type TableMode = 'existing' | 'search'; export default function OrganizationsPage() { const { currentOrganization } = useAuth(); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const [query, setQuery] = useState(''); const [mode, setMode] = useState('existing'); const [searching, setSearching] = useState(false); const [searchResults, setSearchResults] = useState([]); const [requestLinkRowId, setRequestLinkRowId] = useState(null); const [deleteLinkRowId, setDeleteLinkRowId] = useState(null); const [items, setItems] = useState([]); const [manualOrganizationName, setManualOrganizationName] = useState(''); const [manualOwnerEmail, setManualOwnerEmail] = useState(''); const [inviteLoading, setInviteLoading] = useState(false); const [showInviteForm, setShowInviteForm] = useState(false); const [historyOpen, setHistoryOpen] = useState(false); const [historyLoading, setHistoryLoading] = useState(false); const [historyItems, setHistoryItems] = useState([]); const [historyCopyError, setHistoryCopyError] = useState(''); const [historyCopySuccess, setHistoryCopySuccess] = useState(''); const { copiedId, copyingInvitationId, storeInviteLink, copyInvitationLink, pruneAcceptedLinks, } = useOrganizationInviteLinkCopy(currentOrganization?.id); const counterpartLabel = currentOrganization?.type === 'LAB' ? 'Clinic' : 'Lab'; const tabLabel = currentOrganization?.type === 'LAB' ? 'Clinics' : 'Labs'; const existingRows = items; async function loadList() { setLoading(true); setError(''); try { const res = await organizationApi.list(); setItems(res.data.items); } catch (e) { setError(formatApiMessage(e)); } finally { setLoading(false); } } useEffect(() => { void loadList(); }, []); useEffect(() => { if (!success) return; const t = setTimeout(() => setSuccess(''), 4000); return () => clearTimeout(t); }, [success]); async function runSearch() { const q = query.trim(); if (!q) { setMode('existing'); setSearchResults([]); setShowInviteForm(false); return; } setSearching(true); setError(''); setMode('search'); setShowInviteForm(false); try { const res = await organizationApi.search(q); setSearchResults(res.data); } catch (e) { setError(formatApiMessage(e)); setSearchResults([]); } finally { setSearching(false); } } async function submitRequestLink(targetOrganizationId: string) { setRequestLinkRowId(targetOrganizationId); setError(''); try { await organizationApi.createLink(targetOrganizationId); setSuccess(`${counterpartLabel} link request sent`); setSearchResults([]); setQuery(''); setMode('existing'); await loadList(); } catch (e) { setError(formatApiMessage(e)); } finally { setRequestLinkRowId(null); } } async function sendInvite() { setInviteLoading(true); setError(''); try { const res = await organizationApi.invite({ organizationName: manualOrganizationName.trim(), ownerEmail: manualOwnerEmail.trim(), }); storeInviteLink(res.data.invitationId, manualOwnerEmail, res.data.invitationUrl); setSuccess(`Invitation link created for ${manualOwnerEmail.trim()}`); setManualOrganizationName(''); setManualOwnerEmail(''); setShowInviteForm(false); setMode('existing'); setQuery(''); setSearchResults([]); await loadList(); } catch (e) { setError(formatApiMessage(e)); } finally { setInviteLoading(false); } } async function loadInvitationHistory() { const res = await organizationApi.listInvitations(); setHistoryItems(res.data.items); pruneAcceptedLinks(res.data.items); return res.data.items; } async function openInvitationHistory() { setHistoryOpen(true); setHistoryLoading(true); setHistoryCopyError(''); setHistoryCopySuccess(''); setError(''); try { await loadInvitationHistory(); } catch (e) { setError(formatApiMessage(e)); } finally { setHistoryLoading(false); } } async function handleHistoryCopy(invitation: OrganizationInvitationHistoryItemDto) { setHistoryCopyError(''); setHistoryCopySuccess(''); try { await copyInvitationLink(invitation, { onRegenerated: async () => { await loadInvitationHistory(); }, }); setHistoryCopySuccess('Invitation link copied to clipboard.'); setTimeout(() => setHistoryCopySuccess(''), 3000); } catch (e) { setHistoryCopyError(formatApiMessage(e)); } } async function respondToPendingLink(linkId: string, action: 'ACCEPT' | 'REJECT') { setRequestLinkRowId(linkId); setError(''); try { await organizationApi.respondLink(linkId, action); setSuccess(action === 'ACCEPT' ? 'Link request accepted' : 'Link request rejected'); await loadList(); } catch (e) { setError(formatApiMessage(e)); } finally { setRequestLinkRowId(null); } } async function deleteLinkedOrganization(linkId: string) { setDeleteLinkRowId(linkId); setError(''); try { await organizationApi.deleteLink(linkId); setSuccess('Linked organization removed'); await loadList(); } catch (e) { setError(formatApiMessage(e)); } finally { setDeleteLinkRowId(null); } } function clearSearchView() { setMode('existing'); setQuery(''); setSearchResults([]); setShowInviteForm(false); } if (!currentOrganization) { return

Loading organization...

; } return (

{tabLabel}

Search organizations and send link requests or invitation links in one place.

{error && (
{error}
)} {success && (
{success}
)} void runSearch()} placeholder={`Search ${counterpartLabel.toLowerCase()} by name, email, or phone...`} actions={ <> {mode === 'search' && ( )} } /> } body={ <> {loading ? ( ) : mode === 'existing' ? ( existingRows.length === 0 ? ( ) : ( existingRows.map((row) => { const canRespond = row.status === 'PENDING' && row.requestedByOrganizationId !== null && row.requestedByOrganizationId !== currentOrganization.id; return ( ); }) ) ) : searchResults.length > 0 ? ( searchResults.map((r) => ( )) ) : ( )} } /> { setHistoryOpen(false); setHistoryCopyError(''); setHistoryCopySuccess(''); }} loading={historyLoading} items={historyItems} copiedId={copiedId} copyingInvitationId={copyingInvitationId} onCopy={(invitation) => void handleHistoryCopy(invitation)} copyError={historyCopyError} copySuccess={historyCopySuccess} /> ); }
Organization Owner email Date Status Action
Loading...
No organizations linked or pending yet. Use search to find and connect.
{row.organizationName} {row.ownerEmail} {formatTableDate(row.createdAt)} {formatLinkStatusLabel(row.status)}
{canRespond && ( <> )} {row.status === 'ACTIVE' && ( )}
{r.name} {r.owner.email} Today Found

No organization found in directory search.

{showInviteForm && (
setManualOrganizationName(e.target.value)} /> setManualOwnerEmail(e.target.value)} />
)}