Files
edr-platform/apps/edr-freight-web/backoffice/src/user-management/components/content/AllPendingUsers.tsx
natib21 22cf8eabd1 fix ui
2026-07-14 07:59:17 +00:00

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;