(Feat): Initial Commit
This commit is contained in:
786
src/pages/UsersPage.tsx
Normal file
786
src/pages/UsersPage.tsx
Normal file
@@ -0,0 +1,786 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { RefreshCw, Plus, Trash2, Edit, Save, X, Search, AlertTriangle, UserX } from 'lucide-react';
|
||||
import { Card, CardHeader, CardContent } from '../components/ui/Card';
|
||||
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '../components/ui/Table';
|
||||
import { Button } from '../components/ui/Button';
|
||||
import { Input, Select } from '../components/ui/Input';
|
||||
import { useEmployees } from '../hooks/useEmployees';
|
||||
import { useDepartments } from '../hooks/useDepartments';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { api } from '../services/api';
|
||||
|
||||
export const UsersPage: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<'list' | 'add' | 'edit' | 'delete'>('list');
|
||||
const [filterRole, setFilterRole] = useState('');
|
||||
const [filterDept, setFilterDept] = useState('');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const { employees, loading, error, refresh, createEmployee, deleteEmployee, updateEmployee } = useEmployees();
|
||||
const { departments } = useDepartments();
|
||||
const { user: currentUser } = useAuth();
|
||||
|
||||
// Form state
|
||||
const [formData, setFormData] = useState({
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
name: '',
|
||||
email: '',
|
||||
role: 'Employee',
|
||||
departmentId: '',
|
||||
contractorId: '',
|
||||
isActive: true,
|
||||
});
|
||||
const [formError, setFormError] = useState('');
|
||||
const [formLoading, setFormLoading] = useState(false);
|
||||
const [contractors, setContractors] = useState<any[]>([]);
|
||||
const [editingUserId, setEditingUserId] = useState<number | null>(null);
|
||||
|
||||
// Load contractors when role is Employee
|
||||
React.useEffect(() => {
|
||||
if (formData.role === 'Employee') {
|
||||
api.getUsers({ role: 'Contractor' }).then(setContractors).catch(console.error);
|
||||
}
|
||||
}, [formData.role]);
|
||||
|
||||
// Check if current user can manage users
|
||||
const canManageUsers = currentUser?.role === 'SuperAdmin' || currentUser?.role === 'Supervisor';
|
||||
const isSupervisor = currentUser?.role === 'Supervisor';
|
||||
|
||||
// Filter departments for supervisors (only show their department)
|
||||
const filteredDepartments = isSupervisor
|
||||
? departments.filter(d => d.id === currentUser?.department_id)
|
||||
: departments;
|
||||
|
||||
const roleOptions = [
|
||||
{ value: '', label: 'All Roles' },
|
||||
{ value: 'SuperAdmin', label: 'Super Admin' },
|
||||
{ value: 'Supervisor', label: 'Supervisor' },
|
||||
{ value: 'Contractor', label: 'Contractor' },
|
||||
{ value: 'Employee', label: 'Employee' },
|
||||
];
|
||||
|
||||
const deptOptions = isSupervisor
|
||||
? [{ value: String(currentUser?.department_id), label: filteredDepartments[0]?.name || 'My Department' }]
|
||||
: [
|
||||
{ value: '', label: 'All Departments' },
|
||||
...departments.map(d => ({ value: String(d.id), label: d.name })),
|
||||
];
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
setFormError('');
|
||||
};
|
||||
|
||||
const handleCreateUser = async () => {
|
||||
// Validation
|
||||
if (!formData.username || !formData.password || !formData.name || !formData.email) {
|
||||
setFormError('Please fill in all required fields');
|
||||
return;
|
||||
}
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
setFormError('Passwords do not match');
|
||||
return;
|
||||
}
|
||||
if (formData.password.length < 6) {
|
||||
setFormError('Password must be at least 6 characters');
|
||||
return;
|
||||
}
|
||||
|
||||
setFormLoading(true);
|
||||
setFormError('');
|
||||
|
||||
try {
|
||||
await createEmployee({
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
name: formData.name,
|
||||
email: formData.email,
|
||||
role: formData.role,
|
||||
departmentId: formData.departmentId ? parseInt(formData.departmentId) : null,
|
||||
contractorId: formData.contractorId ? parseInt(formData.contractorId) : null,
|
||||
});
|
||||
|
||||
// Reset form and switch to list
|
||||
setFormData({
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
name: '',
|
||||
email: '',
|
||||
role: 'Employee',
|
||||
departmentId: '',
|
||||
contractorId: '',
|
||||
});
|
||||
setActiveTab('list');
|
||||
refresh();
|
||||
} catch (err: any) {
|
||||
setFormError(err.message || 'Failed to create user');
|
||||
} finally {
|
||||
setFormLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteUser = async (id: number, username: string) => {
|
||||
if (!confirm(`Are you sure you want to delete user "${username}"?`)) return;
|
||||
|
||||
try {
|
||||
await deleteEmployee(id);
|
||||
refresh();
|
||||
} catch (err: any) {
|
||||
alert(err.message || 'Failed to delete user');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditUser = (user: any) => {
|
||||
setFormData({
|
||||
username: user.username,
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
departmentId: user.department_id ? String(user.department_id) : '',
|
||||
contractorId: user.contractor_id ? String(user.contractor_id) : '',
|
||||
isActive: user.is_active,
|
||||
});
|
||||
setEditingUserId(user.id);
|
||||
setActiveTab('edit');
|
||||
setFormError('');
|
||||
};
|
||||
|
||||
const handleUpdateUser = async () => {
|
||||
if (!formData.name || !formData.email) {
|
||||
setFormError('Please fill in all required fields');
|
||||
return;
|
||||
}
|
||||
|
||||
setFormLoading(true);
|
||||
setFormError('');
|
||||
|
||||
try {
|
||||
await updateEmployee(editingUserId!, {
|
||||
name: formData.name,
|
||||
email: formData.email,
|
||||
role: formData.role,
|
||||
departmentId: formData.departmentId ? parseInt(formData.departmentId) : null,
|
||||
contractorId: formData.contractorId ? parseInt(formData.contractorId) : null,
|
||||
isActive: formData.isActive,
|
||||
});
|
||||
|
||||
resetForm();
|
||||
setActiveTab('list');
|
||||
refresh();
|
||||
} catch (err: any) {
|
||||
setFormError(err.message || 'Failed to update user');
|
||||
} finally {
|
||||
setFormLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setFormData({
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
name: '',
|
||||
email: '',
|
||||
role: 'Employee',
|
||||
departmentId: '',
|
||||
contractorId: '',
|
||||
isActive: true,
|
||||
});
|
||||
setEditingUserId(null);
|
||||
setFormError('');
|
||||
};
|
||||
|
||||
// Auto-set filter for supervisors
|
||||
React.useEffect(() => {
|
||||
if (isSupervisor && currentUser?.department_id) {
|
||||
setFilterDept(String(currentUser.department_id));
|
||||
}
|
||||
}, [isSupervisor, currentUser?.department_id]);
|
||||
|
||||
// Filter employees
|
||||
const filteredEmployees = employees.filter(emp => {
|
||||
// Search filter
|
||||
if (searchQuery) {
|
||||
const query = searchQuery.toLowerCase();
|
||||
const matchesSearch =
|
||||
emp.name?.toLowerCase().includes(query) ||
|
||||
emp.username?.toLowerCase().includes(query) ||
|
||||
emp.email?.toLowerCase().includes(query) ||
|
||||
emp.role?.toLowerCase().includes(query);
|
||||
if (!matchesSearch) return false;
|
||||
}
|
||||
if (filterRole && emp.role !== filterRole) return false;
|
||||
// For supervisors, always filter by their department
|
||||
if (isSupervisor && currentUser?.department_id) {
|
||||
if (emp.department_id !== currentUser.department_id) return false;
|
||||
} else if (filterDept && emp.department_id !== parseInt(filterDept)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<Card>
|
||||
<div className="border-b border-gray-200">
|
||||
<div className="flex space-x-8 px-6">
|
||||
<button
|
||||
onClick={() => { setActiveTab('list'); resetForm(); }}
|
||||
className={`py-4 px-2 border-b-2 font-medium text-sm ${
|
||||
activeTab === 'list'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
User List
|
||||
</button>
|
||||
{canManageUsers && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => { setActiveTab('add'); resetForm(); }}
|
||||
className={`py-4 px-2 border-b-2 font-medium text-sm ${
|
||||
activeTab === 'add'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
Add User
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('edit')}
|
||||
className={`py-4 px-2 border-b-2 font-medium text-sm ${
|
||||
activeTab === 'edit'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
Edit User
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('delete')}
|
||||
className={`py-4 px-2 border-b-2 font-medium text-sm ${
|
||||
activeTab === 'delete'
|
||||
? 'border-red-500 text-red-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
Delete Users
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardContent>
|
||||
{activeTab === 'list' && (
|
||||
<div>
|
||||
<div className="flex gap-4 mb-6">
|
||||
<div className="relative min-w-[300px] flex-1">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={18} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search users by name, username, email..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
options={deptOptions}
|
||||
className="w-48 flex-shrink-0"
|
||||
value={filterDept}
|
||||
onChange={(e) => setFilterDept(e.target.value)}
|
||||
disabled={isSupervisor}
|
||||
/>
|
||||
<Select
|
||||
options={roleOptions}
|
||||
className="w-48 flex-shrink-0"
|
||||
value={filterRole}
|
||||
onChange={(e) => setFilterRole(e.target.value)}
|
||||
/>
|
||||
<Button variant="ghost" onClick={refresh}>
|
||||
<RefreshCw size={16} className="mr-2" />
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 text-sm text-gray-600">
|
||||
Total Users: {filteredEmployees.length}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="text-center py-8 text-red-600">
|
||||
Error: {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<div className="text-center py-8">Loading...</div>
|
||||
) : filteredEmployees.length > 0 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>USERNAME</TableHead>
|
||||
<TableHead>FULL NAME</TableHead>
|
||||
<TableHead>EMAIL</TableHead>
|
||||
<TableHead>ROLE</TableHead>
|
||||
<TableHead>DEPARTMENT</TableHead>
|
||||
<TableHead>STATUS</TableHead>
|
||||
<TableHead>ACTIONS</TableHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredEmployees.map((user) => (
|
||||
<TableRow key={user.id}>
|
||||
<TableCell>{user.id}</TableCell>
|
||||
<TableCell className="text-blue-600">{user.username}</TableCell>
|
||||
<TableCell>{user.name}</TableCell>
|
||||
<TableCell>{user.email}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
||||
user.role === 'SuperAdmin' ? 'bg-purple-100 text-purple-700' :
|
||||
user.role === 'Supervisor' ? 'bg-blue-100 text-blue-700' :
|
||||
user.role === 'Contractor' ? 'bg-orange-100 text-orange-700' :
|
||||
'bg-gray-100 text-gray-700'
|
||||
}`}>
|
||||
{user.role}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>{user.department_name || '-'}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
||||
user.is_active ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'
|
||||
}`}>
|
||||
{user.is_active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{canManageUsers && (
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleEditUser(user)}
|
||||
className="text-blue-600 hover:text-blue-800"
|
||||
title="Edit"
|
||||
>
|
||||
<Edit size={14} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteUser(user.id, user.username)}
|
||||
className="text-red-600 hover:text-red-800"
|
||||
title="Delete"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : !loading && (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
No users found
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'add' && (
|
||||
<div className="max-w-3xl">
|
||||
{formError && (
|
||||
<div className="mb-4 p-3 bg-red-100 text-red-700 rounded-md">
|
||||
{formError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h3 className="text-lg font-semibold text-gray-800 mb-6">User Information</h3>
|
||||
<div className="grid grid-cols-2 gap-6 mb-8">
|
||||
<Input
|
||||
label="Username"
|
||||
name="username"
|
||||
value={formData.username}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Password"
|
||||
name="password"
|
||||
type="password"
|
||||
value={formData.password}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Full Name"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Confirm Password"
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
<div className="col-span-2">
|
||||
<Input
|
||||
label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-semibold text-gray-800 mb-6">Role & Department</h3>
|
||||
<div className="grid grid-cols-2 gap-6 mb-8">
|
||||
<Select
|
||||
label="Role"
|
||||
name="role"
|
||||
value={formData.role}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
options={roleOptions.slice(1)}
|
||||
/>
|
||||
<Select
|
||||
label="Department"
|
||||
name="departmentId"
|
||||
value={formData.departmentId}
|
||||
onChange={handleInputChange}
|
||||
options={deptOptions.slice(1)}
|
||||
/>
|
||||
{formData.role === 'Employee' && (
|
||||
<Select
|
||||
label="Contractor (for Employees)"
|
||||
name="contractorId"
|
||||
value={formData.contractorId}
|
||||
onChange={handleInputChange}
|
||||
options={[
|
||||
{ value: '', label: 'Select Contractor' },
|
||||
...contractors.map(c => ({ value: String(c.id), label: c.name }))
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => { setActiveTab('list'); resetForm(); }}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={handleCreateUser}
|
||||
disabled={formLoading}
|
||||
>
|
||||
{formLoading ? (
|
||||
'Creating...'
|
||||
) : (
|
||||
<>
|
||||
<Plus size={16} className="mr-2" />
|
||||
Create User
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'edit' && (
|
||||
<div className="max-w-3xl">
|
||||
{formError && (
|
||||
<div className="mb-4 p-3 bg-red-100 text-red-700 rounded-md">
|
||||
{formError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!editingUserId ? (
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-800 mb-6">Select User to Edit</h3>
|
||||
<Select
|
||||
label="Select User"
|
||||
value=""
|
||||
onChange={(e) => {
|
||||
const user = employees.find(emp => emp.id === parseInt(e.target.value));
|
||||
if (user) handleEditUser(user);
|
||||
}}
|
||||
options={[
|
||||
{ value: '', label: 'Choose a user to edit...' },
|
||||
...employees.map(emp => ({
|
||||
value: String(emp.id),
|
||||
label: `${emp.name} (${emp.username}) - ${emp.role}`
|
||||
}))
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold text-gray-800 mb-6">Edit User: {formData.username}</h3>
|
||||
<div className="grid grid-cols-2 gap-6 mb-8">
|
||||
<Input
|
||||
label="Username"
|
||||
name="username"
|
||||
value={formData.username}
|
||||
disabled
|
||||
/>
|
||||
<Input
|
||||
label="Full Name"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
<Select
|
||||
label="Status"
|
||||
name="isActive"
|
||||
value={formData.isActive ? 'true' : 'false'}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, isActive: e.target.value === 'true' }))}
|
||||
options={[
|
||||
{ value: 'true', label: 'Active' },
|
||||
{ value: 'false', label: 'Inactive' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-semibold text-gray-800 mb-6">Role & Department</h3>
|
||||
<div className="grid grid-cols-2 gap-6 mb-8">
|
||||
<Select
|
||||
label="Role"
|
||||
name="role"
|
||||
value={formData.role}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
options={roleOptions.slice(1)}
|
||||
/>
|
||||
<Select
|
||||
label="Department"
|
||||
name="departmentId"
|
||||
value={formData.departmentId}
|
||||
onChange={handleInputChange}
|
||||
options={[
|
||||
{ value: '', label: 'No Department' },
|
||||
...departments.map(d => ({ value: String(d.id), label: d.name }))
|
||||
]}
|
||||
/>
|
||||
{formData.role === 'Employee' && (
|
||||
<Select
|
||||
label="Contractor (for Employees)"
|
||||
name="contractorId"
|
||||
value={formData.contractorId}
|
||||
onChange={handleInputChange}
|
||||
options={[
|
||||
{ value: '', label: 'Select Contractor' },
|
||||
...contractors.map(c => ({ value: String(c.id), label: c.name }))
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => resetForm()}
|
||||
>
|
||||
<X size={16} className="mr-2" />
|
||||
Clear Selection
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleUpdateUser}
|
||||
disabled={formLoading}
|
||||
>
|
||||
{formLoading ? (
|
||||
'Saving...'
|
||||
) : (
|
||||
<>
|
||||
<Save size={16} className="mr-2" />
|
||||
Save Changes
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'delete' && canManageUsers && (
|
||||
<div>
|
||||
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="text-red-500 flex-shrink-0 mt-0.5" size={20} />
|
||||
<div>
|
||||
<h4 className="font-semibold text-red-800">Warning: Permanent Action</h4>
|
||||
<p className="text-sm text-red-700 mt-1">
|
||||
Deleting a user is permanent and cannot be undone. All associated data will be removed.
|
||||
{isSupervisor && " As a Supervisor, you can only delete Employees and Contractors in your department."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 mb-6">
|
||||
<div className="relative min-w-[300px] flex-1">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={18} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search users to delete..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-red-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
{!isSupervisor && (
|
||||
<Select
|
||||
options={deptOptions}
|
||||
className="w-48 flex-shrink-0"
|
||||
value={filterDept}
|
||||
onChange={(e) => setFilterDept(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'All Deletable Roles' },
|
||||
{ value: 'Employee', label: 'Employee' },
|
||||
{ value: 'Contractor', label: 'Contractor' },
|
||||
]}
|
||||
className="w-48 flex-shrink-0"
|
||||
value={filterRole}
|
||||
onChange={(e) => setFilterRole(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{(() => {
|
||||
// Filter users that can be deleted
|
||||
const deletableUsers = employees.filter(emp => {
|
||||
// SuperAdmins and Supervisors cannot be deleted from this tab
|
||||
if (emp.role === 'SuperAdmin' || emp.role === 'Supervisor') return false;
|
||||
|
||||
// Only Employees and Contractors can be deleted
|
||||
if (emp.role !== 'Employee' && emp.role !== 'Contractor') return false;
|
||||
|
||||
// Supervisors can only delete users in their department
|
||||
if (isSupervisor && emp.department_id !== currentUser?.department_id) return false;
|
||||
|
||||
// Apply search filter
|
||||
if (searchQuery) {
|
||||
const query = searchQuery.toLowerCase();
|
||||
const matchesSearch =
|
||||
emp.name?.toLowerCase().includes(query) ||
|
||||
emp.username?.toLowerCase().includes(query) ||
|
||||
emp.email?.toLowerCase().includes(query);
|
||||
if (!matchesSearch) return false;
|
||||
}
|
||||
|
||||
// Apply role filter
|
||||
if (filterRole && emp.role !== filterRole) return false;
|
||||
|
||||
// Apply department filter (for SuperAdmin)
|
||||
if (!isSupervisor && filterDept && emp.department_id !== parseInt(filterDept)) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 text-sm text-gray-600">
|
||||
Deletable Users: {deletableUsers.length}
|
||||
</div>
|
||||
|
||||
{deletableUsers.length > 0 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>USERNAME</TableHead>
|
||||
<TableHead>FULL NAME</TableHead>
|
||||
<TableHead>EMAIL</TableHead>
|
||||
<TableHead>ROLE</TableHead>
|
||||
<TableHead>DEPARTMENT</TableHead>
|
||||
<TableHead>STATUS</TableHead>
|
||||
<TableHead>ACTION</TableHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{deletableUsers.map((user) => (
|
||||
<TableRow key={user.id} className="hover:bg-red-50">
|
||||
<TableCell>{user.id}</TableCell>
|
||||
<TableCell className="text-blue-600">{user.username}</TableCell>
|
||||
<TableCell>{user.name}</TableCell>
|
||||
<TableCell>{user.email}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
||||
user.role === 'Contractor' ? 'bg-orange-100 text-orange-700' :
|
||||
'bg-gray-100 text-gray-700'
|
||||
}`}>
|
||||
{user.role}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>{user.department_name || '-'}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
||||
user.is_active ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'
|
||||
}`}>
|
||||
{user.is_active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (confirm(`Are you sure you want to permanently delete "${user.name}" (${user.username})?\n\nThis action cannot be undone!`)) {
|
||||
deleteEmployee(user.id);
|
||||
}
|
||||
}}
|
||||
className="text-red-600 border-red-300 hover:bg-red-50 hover:border-red-400"
|
||||
>
|
||||
<UserX size={14} className="mr-1" />
|
||||
Delete
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
<UserX size={48} className="mx-auto mb-4 text-gray-300" />
|
||||
<p>No deletable users found</p>
|
||||
<p className="text-sm mt-1">
|
||||
{isSupervisor
|
||||
? "Only Employees and Contractors in your department can be deleted."
|
||||
: "Only Employees and Contractors can be deleted from this tab."}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user