mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 01:55:41 +00:00
user management ui
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
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-blue-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-blue-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;
|
||||
Reference in New Issue
Block a user