mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
216 lines
8.4 KiB
TypeScript
216 lines
8.4 KiB
TypeScript
import Badge from '@/components/ui/Badge.tsx';
|
|
import Button from '@/components/ui/Button.tsx';
|
|
import Input from '@/components/ui/Input.tsx';
|
|
import VerifiedBadge from '@/components/ui/VerifiedBadge.tsx';
|
|
import { getSectorDisplay } from '@/constants.tsx';
|
|
import { useOrganizationTable } from '@/hooks/features/useOrganizationTable.ts';
|
|
import { useTranslation } from '@/hooks/useI18n.tsx';
|
|
import {
|
|
getTranslatedSectorName
|
|
} from '@/lib/sector-mapper.ts';
|
|
import { getOrganizationSubtypeLabel } from '@/schemas/organizationSubtype.ts';
|
|
import { Organization } from '@/types.ts';
|
|
import { useVerifyOrganization, useRejectVerification } from '@/hooks/api/useAdminAPI.ts';
|
|
import React, { useCallback } from 'react';
|
|
|
|
interface OrganizationTableProps {
|
|
onUpdateOrganization: (org: Organization) => void;
|
|
}
|
|
|
|
const VerifyButton = React.memo(
|
|
({ org, onVerify }: { org: Organization; onVerify: (org: Organization) => void }) => {
|
|
const { t } = useTranslation();
|
|
const handleClick = useCallback(() => {
|
|
onVerify(org);
|
|
}, [onVerify, org]);
|
|
|
|
return (
|
|
<Button variant="ghost" size="sm" onClick={handleClick}>
|
|
{org.Verified ? t('adminPage.orgTable.unverify') : t('adminPage.orgTable.verify')}
|
|
</Button>
|
|
);
|
|
}
|
|
);
|
|
VerifyButton.displayName = 'VerifyButton';
|
|
|
|
const OrganizationTable = ({ onUpdateOrganization }: OrganizationTableProps) => {
|
|
const { t } = useTranslation();
|
|
const { filter, setFilter, searchTerm, setSearchTerm, filteredOrgs } = useOrganizationTable();
|
|
|
|
const { mutate: verifyOrganization } = useVerifyOrganization();
|
|
const { mutate: rejectVerification } = useRejectVerification();
|
|
|
|
const handleVerify = useCallback(
|
|
(org: Organization) => {
|
|
if (org.Verified) {
|
|
// If already verified, we could reject or just update locally
|
|
// For now, just update locally
|
|
onUpdateOrganization({ ...org, Verified: false });
|
|
} else {
|
|
verifyOrganization(
|
|
{ id: org.ID, notes: 'Verified via admin panel' },
|
|
{
|
|
onSuccess: () => {
|
|
onUpdateOrganization({ ...org, Verified: true });
|
|
},
|
|
onError: (error) => {
|
|
console.error('Failed to verify organization:', error);
|
|
},
|
|
}
|
|
);
|
|
}
|
|
},
|
|
[onUpdateOrganization, verifyOrganization]
|
|
);
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex flex-col sm:flex-row gap-4">
|
|
<Input
|
|
placeholder={t('adminPage.orgTable.searchPlaceholder')}
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
className="max-w-xs"
|
|
/>
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant={filter === 'all' ? 'primary' : 'outline'}
|
|
onClick={() => setFilter('all')}
|
|
>
|
|
{t('adminPage.orgTable.filters.all')}
|
|
</Button>
|
|
<Button
|
|
variant={filter === 'verified' ? 'primary' : 'outline'}
|
|
onClick={() => setFilter('verified')}
|
|
>
|
|
{t('adminPage.orgTable.filters.verified')}
|
|
</Button>
|
|
<Button
|
|
variant={filter === 'unverified' ? 'primary' : 'outline'}
|
|
onClick={() => setFilter('unverified')}
|
|
>
|
|
{t('adminPage.orgTable.filters.unverified')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className="border rounded-lg overflow-hidden">
|
|
<table className="min-w-full divide-y divide-border">
|
|
<thead className="bg-muted/50">
|
|
<tr>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider"
|
|
>
|
|
{t('adminPage.orgTable.logo')}
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider"
|
|
>
|
|
{t('adminPage.orgTable.name')}
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider"
|
|
>
|
|
{t('adminPage.orgTable.sector')}
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider"
|
|
>
|
|
{t('adminPage.orgTable.type')}
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider"
|
|
>
|
|
{t('adminPage.orgTable.needsOffers')}
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider"
|
|
>
|
|
{t('adminPage.orgTable.status')}
|
|
</th>
|
|
<th scope="col" className="relative px-6 py-3">
|
|
<span className="sr-only">{t('adminPage.orgTable.action')}</span>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="bg-background divide-y divide-border">
|
|
{filteredOrgs.map((org) => {
|
|
// Get sector display information
|
|
const orgSector = org.Sector || '';
|
|
const sectorDisplay = getSectorDisplay(orgSector);
|
|
return (
|
|
<tr key={org.ID}>
|
|
<td className="px-6 py-4">
|
|
<div className="h-10 w-10 rounded-md flex items-center justify-center shrink-0 bg-muted overflow-hidden border">
|
|
{org.LogoURL ? (
|
|
<img
|
|
src={org.LogoURL}
|
|
alt={`${org.Name} logo`}
|
|
className="w-full h-full object-cover"
|
|
loading="lazy"
|
|
decoding="async"
|
|
onError={(e) => {
|
|
(e.target as HTMLImageElement).style.display = 'none';
|
|
}}
|
|
/>
|
|
) : (
|
|
<div
|
|
className={`w-full h-full flex items-center justify-center bg-sector-${sectorDisplay.colorKey}`}
|
|
>
|
|
{React.cloneElement(sectorDisplay.icon, {
|
|
className: 'h-5 w-5 text-primary-foreground',
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">{org.Name}</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-muted-foreground">
|
|
{getTranslatedSectorName(orgSector, t)}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-muted-foreground">
|
|
{org.Subtype ? (
|
|
<Badge variant="secondary" className="text-xs">
|
|
{getOrganizationSubtypeLabel(org.Subtype)}
|
|
</Badge>
|
|
) : (
|
|
<span className="text-muted-foreground">—</span>
|
|
)}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-muted-foreground">
|
|
<div title="Resource flow data will be available when backend API supports counting organization resources">
|
|
{t('adminPage.orgTable.notAvailable')}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm">
|
|
{org.Verified ? (
|
|
<VerifiedBadge />
|
|
) : (
|
|
<Badge
|
|
variant="outline"
|
|
className="border-destructive/30 bg-destructive/10 text-destructive"
|
|
>
|
|
{t('adminPage.orgTable.unverified')}
|
|
</Badge>
|
|
)}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
<VerifyButton org={org} onVerify={handleVerify} />
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default React.memo(OrganizationTable);
|