mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 00:50:56 +00:00
222 lines
9.8 KiB
TypeScript
222 lines
9.8 KiB
TypeScript
import React, { useState } from "react";
|
|
import { usePendingUsers } from "@/user-management/userManagement/hooks/usePendingUsersHook";
|
|
import { toast } from "sonner";
|
|
import { Check, X, Users, Search, RefreshCw } from "lucide-react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { useErrorHandler } from "@/shared/hooks/useErrorHandler";
|
|
|
|
const ReviewAllPendingUsers = () => {
|
|
const { t } = useTranslation();
|
|
const { handleError } = useErrorHandler(t);
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
|
|
const { pendingUsers, loadingPendingUsers, approveMutation, rejectMutation } =
|
|
usePendingUsers({ take: 50, skip: 0 });
|
|
|
|
const handleApprove = (id: string) => {
|
|
approveMutation.mutate(id, {
|
|
onSuccess: () => toast.success(t("pendingUsers.employeeApprovedSuccess")),
|
|
onError: (error) => handleError(error),
|
|
});
|
|
};
|
|
|
|
const handleReject = (id: string) => {
|
|
rejectMutation.mutate(id, {
|
|
onSuccess: () => toast.success(t("pendingUsers.employeeRejectedSuccess")),
|
|
onError: (error) => handleError(error),
|
|
});
|
|
};
|
|
|
|
const handleSearch = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
// Integrate search with your API if needed
|
|
};
|
|
|
|
// Loading skeleton (full width)
|
|
if (loadingPendingUsers) {
|
|
return (
|
|
<div className="p-6 w-full">
|
|
{/* Header skeleton */}
|
|
<div className="flex justify-between items-center mb-6">
|
|
<div className="h-8 w-48 bg-gray-200 rounded animate-pulse"></div>
|
|
<div className="h-6 w-24 bg-gray-200 rounded animate-pulse"></div>
|
|
</div>
|
|
{/* Search bar skeleton */}
|
|
<div className="flex gap-2 mb-6">
|
|
<div className="flex-1 h-10 bg-gray-200 rounded animate-pulse"></div>
|
|
<div className="w-20 h-10 bg-gray-200 rounded animate-pulse"></div>
|
|
</div>
|
|
{/* Table skeleton */}
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
|
|
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
<thead className="bg-gray-50 dark:bg-gray-700">
|
|
<tr>
|
|
{[
|
|
t("pendingUsers.name"),
|
|
t("pendingUsers.email"),
|
|
t("pendingUsers.positions"),
|
|
t("pendingUsers.actions"),
|
|
].map((h) => (
|
|
<th
|
|
key={h}
|
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase">
|
|
{h}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
|
|
{[...Array(5)].map((_, i) => (
|
|
<tr
|
|
key={i}
|
|
className="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
|
<td className="px-6 py-4">
|
|
<div className="h-4 w-32 bg-gray-200 dark:bg-gray-600 rounded"></div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<div className="h-4 w-40 bg-gray-200 dark:bg-gray-600 rounded"></div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<div className="h-4 w-48 bg-gray-200 dark:bg-gray-600 rounded"></div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<div className="flex gap-2">
|
|
<div className="h-8 w-20 bg-gray-200 dark:bg-gray-600 rounded"></div>
|
|
<div className="h-8 w-20 bg-gray-200 dark:bg-gray-600 rounded"></div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Empty state
|
|
if (!pendingUsers?.items?.length) {
|
|
return (
|
|
<div className="p-6 w-full">
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h1 className="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
|
<Users className="h-6 w-6 text-primary-600" />
|
|
{t("pendingUsers.title")}
|
|
</h1>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-100 p-12 text-center mt-6">
|
|
<div className="flex justify-center mb-4">
|
|
<div className="bg-primary-100 p-3 rounded-full">
|
|
<Users className="h-8 w-8 text-primary-600" />
|
|
</div>
|
|
</div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
|
{t("pendingUsers.noPendingApprovals")}
|
|
</h3>
|
|
<p className="text-gray-500 mb-6">
|
|
{t("pendingUsers.allRequestsReviewed")}
|
|
</p>
|
|
<button
|
|
onClick={() => window.location.reload()}
|
|
className="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50">
|
|
<RefreshCw className="h-4 w-4 mr-2" />
|
|
{t("pendingUsers.refresh")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="p-6 w-full">
|
|
{/* Header with count */}
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
|
<h1 className="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
|
<Users className="h-6 w-6 text-primary-600" />
|
|
{t("pendingUsers.title")}
|
|
</h1>
|
|
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 text-yellow-800">
|
|
{pendingUsers.count} {t("pendingUsers.pending")}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Table - full width */}
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden mt-6">
|
|
<div className="overflow-x-auto">
|
|
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
<thead className="bg-gray-50 dark:bg-gray-700">
|
|
<tr>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
{t("pendingUsers.name")}
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
{t("pendingUsers.email")}
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
{t("pendingUsers.positions")}
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
{t("pendingUsers.actions")}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
{pendingUsers.items.map((emp: any) => (
|
|
<tr
|
|
key={emp.id}
|
|
className="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
{emp.name?.en}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
|
{emp.user?.email}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
|
{emp.employeePositions
|
|
?.map((pos: any) => pos.position?.name?.en)
|
|
.join(", ") || "—"}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-center">
|
|
<div className="flex justify-center gap-2">
|
|
<button
|
|
className="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
disabled={approveMutation.isPending}
|
|
onClick={() => handleApprove(emp.id)}>
|
|
<Check className="h-3.5 w-3.5 mr-1" />
|
|
{t("pendingUsers.approve")}
|
|
</button>
|
|
<button
|
|
className="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
disabled={rejectMutation.isPending}
|
|
onClick={() => handleReject(emp.id)}>
|
|
<X className="h-3.5 w-3.5 mr-1" />
|
|
{t("pendingUsers.reject")}
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Footer note */}
|
|
<p className="text-xs text-gray-400 mt-4 text-center">
|
|
{t("pendingUsers.showingUpTo")}
|
|
</p>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ReviewAllPendingUsers;
|