phosphat-report-app/app/routes/employees.tsx
2025-08-26 10:12:51 +03:00

740 lines
33 KiB
TypeScript

import type { ActionFunctionArgs, LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { Form, useActionData, useLoaderData, useNavigation } from "@remix-run/react";
import { requireAuthLevel } from "~/utils/auth.server";
import DashboardLayout from "~/components/DashboardLayout";
import FormModal from "~/components/FormModal";
import Toast from "~/components/Toast";
import { useState, useEffect } from "react";
import bcrypt from "bcryptjs";
import { prisma } from "~/utils/db.server";
export const meta: MetaFunction = () => [{ title: "Employee Management - Alhaffer Reporting System" }];
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireAuthLevel(request, 2);
// If user is level 2 (Admin), they can only see employees with level <= 2
// If user is level 3 (Super Admin), they can see all employees
const whereClause = user.authLevel === 2
? { authLevel: { lte: 2 } } // Level 2 users can only see level 1 and 2
: {}; // Level 3 users can see all levels
const employees = await prisma.employee.findMany({
where: whereClause,
orderBy: [{ authLevel: 'asc' }, { name: 'asc' }],
include: {
_count: {
select: { reports: true }
}
}
});
return json({ user, employees });
};
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireAuthLevel(request, 2);
const formData = await request.formData();
const intent = formData.get("intent");
const id = formData.get("id");
const name = formData.get("name");
const username = formData.get("username");
const email = formData.get("email");
const password = formData.get("password");
const authLevel = formData.get("authLevel");
const status = formData.get("status");
if (intent === "create") {
if (typeof name !== "string" || name.length === 0) {
return json({ errors: { name: "Name is required" } }, { status: 400 });
}
if (typeof username !== "string" || username.length === 0) {
return json({ errors: { username: "Username is required" } }, { status: 400 });
}
if (typeof email !== "string" || email.length === 0) {
return json({ errors: { email: "Email is required" } }, { status: 400 });
}
// Basic email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return json({ errors: { email: "Please enter a valid email address" } }, { status: 400 });
}
if (typeof password !== "string" || password.length < 6) {
return json({ errors: { password: "Password must be at least 6 characters" } }, { status: 400 });
}
if (typeof authLevel !== "string" || !["1", "2", "3"].includes(authLevel)) {
return json({ errors: { authLevel: "Valid auth level is required (1, 2, or 3)" } }, { status: 400 });
}
// Level 2 users cannot create Level 3 employees
if (user.authLevel === 2 && parseInt(authLevel) === 3) {
return json({ errors: { authLevel: "You don't have permission to create Super Admin users" } }, { status: 403 });
}
try {
const hashedPassword = bcrypt.hashSync(password, 10);
await prisma.employee.create({
data: {
name,
username,
email,
password: hashedPassword,
authLevel: parseInt(authLevel),
status: 'active'
}
});
return json({ success: "Employee created successfully!" });
} catch (error) {
return json({ errors: { form: "Username or email already exists" } }, { status: 400 });
}
}
if (intent === "update") {
if (typeof name !== "string" || name.length === 0) {
return json({ errors: { name: "Name is required" } }, { status: 400 });
}
if (typeof username !== "string" || username.length === 0) {
return json({ errors: { username: "Username is required" } }, { status: 400 });
}
if (typeof email !== "string" || email.length === 0) {
return json({ errors: { email: "Email is required" } }, { status: 400 });
}
// Basic email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return json({ errors: { email: "Please enter a valid email address" } }, { status: 400 });
}
if (typeof authLevel !== "string" || !["1", "2", "3"].includes(authLevel)) {
return json({ errors: { authLevel: "Valid auth level is required (1, 2, or 3)" } }, { status: 400 });
}
if (typeof id !== "string") {
return json({ errors: { form: "Invalid employee ID" } }, { status: 400 });
}
// Check if the employee being updated exists and if current user can edit them
const existingEmployee = await prisma.employee.findUnique({
where: { id: parseInt(id) },
select: { authLevel: true }
});
if (!existingEmployee) {
return json({ errors: { form: "Employee not found" } }, { status: 404 });
}
// Level 2 users cannot edit Level 3 employees
if (user.authLevel === 2 && existingEmployee.authLevel === 3) {
return json({ errors: { form: "You don't have permission to edit Super Admin users" } }, { status: 403 });
}
// Level 2 users cannot promote someone to Level 3
if (user.authLevel === 2 && parseInt(authLevel) === 3) {
return json({ errors: { authLevel: "You don't have permission to create Super Admin users" } }, { status: 403 });
}
try {
const updateData: any = {
name,
username,
email,
authLevel: parseInt(authLevel)
};
// Add status if provided (but prevent users from changing their own status)
if (typeof status === "string" && ["active", "inactive"].includes(status)) {
if (parseInt(id) !== user.id) {
updateData.status = status;
}
}
// Only update password if provided
if (typeof password === "string" && password.length >= 6) {
updateData.password = bcrypt.hashSync(password, 10);
} else if (typeof password === "string" && password.length > 0 && password.length < 6) {
return json({ errors: { password: "Password must be at least 6 characters" } }, { status: 400 });
}
await prisma.employee.update({
where: { id: parseInt(id) },
data: updateData
});
return json({ success: "Employee updated successfully!" });
} catch (error) {
return json({ errors: { form: "Username or email already exists" } }, { status: 400 });
}
}
if (intent === "toggleStatus") {
if (typeof id !== "string") {
return json({ errors: { form: "Invalid employee ID" } }, { status: 400 });
}
const employeeId = parseInt(id);
// Prevent users from changing their own status
if (employeeId === user.id) {
return json({ errors: { form: "You cannot change your own status" } }, { status: 403 });
}
// Check if the employee exists and if current user can edit them
const existingEmployee = await prisma.employee.findUnique({
where: { id: employeeId },
select: { authLevel: true, status: true }
});
if (!existingEmployee) {
return json({ errors: { form: "Employee not found" } }, { status: 404 });
}
// Level 2 users cannot edit Level 3 employees
if (user.authLevel === 2 && existingEmployee.authLevel === 3) {
return json({ errors: { form: "You don't have permission to edit Super Admin users" } }, { status: 403 });
}
try {
const newStatus = existingEmployee.status === 'active' ? 'inactive' : 'active';
await prisma.employee.update({
where: { id: employeeId },
data: { status: newStatus }
});
return json({ success: `Employee status changed to ${newStatus}!` });
} catch (error) {
return json({ errors: { form: "Failed to update employee status" } }, { status: 400 });
}
}
if (intent === "delete") {
if (typeof id !== "string") {
return json({ errors: { form: "Invalid employee ID" } }, { status: 400 });
}
// Check if the employee being deleted exists and if current user can delete them
const existingEmployee = await prisma.employee.findUnique({
where: { id: parseInt(id) },
select: { authLevel: true }
});
if (!existingEmployee) {
return json({ errors: { form: "Employee not found" } }, { status: 404 });
}
// Level 2 users cannot delete Level 3 employees
if (user.authLevel === 2 && existingEmployee.authLevel === 3) {
return json({ errors: { form: "You don't have permission to delete Super Admin users" } }, { status: 403 });
}
try {
await prisma.employee.delete({
where: { id: parseInt(id) }
});
return json({ success: "Employee deleted successfully!" });
} catch (error) {
return json({ errors: { form: "Cannot delete employee with existing reports" } }, { status: 400 });
}
}
return json({ errors: { form: "Invalid action" } }, { status: 400 });
};
export default function Employees() {
const { user, employees } = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();
const navigation = useNavigation();
const [editingEmployee, setEditingEmployee] = useState<{ id: number; name: string; username: string; email: string; authLevel: number; status: string } | null>(null);
const [showModal, setShowModal] = useState(false);
const [toast, setToast] = useState<{ message: string; type: "success" | "error" } | null>(null);
const isSubmitting = navigation.state === "submitting";
const isEditing = editingEmployee !== null;
// Handle success/error messages
useEffect(() => {
if (actionData?.success) {
setToast({ message: actionData.success, type: "success" });
setShowModal(false);
setEditingEmployee(null);
} else if (actionData?.errors?.form) {
setToast({ message: actionData.errors.form, type: "error" });
}
}, [actionData]);
const handleEdit = (employee: { id: number; name: string; username: string; email: string; authLevel: number; status: string }) => {
setEditingEmployee(employee);
setShowModal(true);
};
const handleAdd = () => {
setEditingEmployee(null);
setShowModal(true);
};
const handleCloseModal = () => {
setShowModal(false);
setEditingEmployee(null);
};
const getAuthLevelBadge = (authLevel: number) => {
const colors = {
1: "bg-yellow-100 text-yellow-800",
2: "bg-green-100 text-green-800",
3: "bg-blue-100 text-blue-800"
};
return colors[authLevel as keyof typeof colors] || "bg-gray-100 text-gray-800";
};
const getAuthLevelText = (authLevel: number) => {
const levels = {
1: "User",
2: "Admin",
3: "Super Admin"
};
return levels[authLevel as keyof typeof levels] || "Unknown";
};
const getAuthLevelIcon = (authLevel: number) => {
switch (authLevel) {
case 1:
return (
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
);
case 2:
return (
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
);
case 3:
return (
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
);
default:
return (
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
);
}
};
const getStatusBadge = (status: string) => {
return status === 'active'
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800";
};
const getStatusIcon = (status: string) => {
return status === 'active' ? (
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
) : (
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
);
};
return (
<DashboardLayout user={user}>
<div className="space-y-6">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center space-y-4 sm:space-y-0">
<div>
<h1 className="text-xl sm:text-2xl font-bold text-gray-900">Employee Management</h1>
<p className="mt-1 text-sm text-gray-600">Manage system users and their access levels</p>
</div>
<button
onClick={handleAdd}
className="inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200"
>
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Add New Employee
</button>
</div>
{/* Employees Table - Desktop */}
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
<div className="hidden lg:block">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Employee
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Username
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Email
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Access Level
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Status
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Reports Count
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{employees.map((employee) => (
<tr key={employee.id} className="hover:bg-gray-50 transition-colors duration-150">
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className={`h-10 w-10 rounded-full flex items-center justify-center ${getAuthLevelBadge(employee.authLevel)}`}>
{getAuthLevelIcon(employee.authLevel)}
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">{employee.name}</div>
{/* <div className="text-sm text-gray-500">ID #{employee.id}</div> */}
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900 font-mono">{employee.username}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">{employee.email}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getAuthLevelBadge(employee.authLevel)}`}>
Level {employee.authLevel} - {getAuthLevelText(employee.authLevel)}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatusBadge(employee.status)}`}>
{getStatusIcon(employee.status)}
<span className="ml-1 capitalize">{employee.status}</span>
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{employee._count.reports} reports
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<div className="flex justify-end space-x-2">
<button
onClick={() => handleEdit(employee)}
className="text-indigo-600 hover:text-indigo-900 transition-colors duration-150"
>
Edit
</button>
{employee.id !== user.id && (
<>
<Form method="post" className="inline">
<input type="hidden" name="intent" value="toggleStatus" />
<input type="hidden" name="id" value={employee.id} />
<button
type="submit"
className={`transition-colors duration-150 ${
employee.status === 'active'
? 'text-orange-600 hover:text-orange-900'
: 'text-green-600 hover:text-green-900'
}`}
>
{employee.status === 'active' ? 'Deactivate' : 'Activate'}
</button>
</Form>
<Form method="post" className="inline">
<input type="hidden" name="intent" value="delete" />
<input type="hidden" name="id" value={employee.id} />
<button
type="submit"
onClick={(e) => {
if (!confirm("Are you sure you want to delete this employee?")) {
e.preventDefault();
}
}}
className="text-red-600 hover:text-red-900 transition-colors duration-150"
>
Delete
</button>
</Form>
</>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Employees Cards - Mobile */}
<div className="lg:hidden">
<div className="space-y-4 p-4">
{employees.map((employee) => (
<div key={employee.id} className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
<div className="flex items-start justify-between mb-3">
<div className="flex items-center">
<div className={`h-10 w-10 rounded-full flex items-center justify-center ${getAuthLevelBadge(employee.authLevel)}`}>
{getAuthLevelIcon(employee.authLevel)}
</div>
<div className="ml-3">
<div className="text-sm font-medium text-gray-900">{employee.name}</div>
<div className="text-xs text-gray-500 font-mono">{employee.username}</div>
</div>
</div>
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatusBadge(employee.status)}`}>
{getStatusIcon(employee.status)}
<span className="ml-1 capitalize">{employee.status}</span>
</span>
</div>
<div className="space-y-2 mb-4">
<div className="flex justify-between">
<span className="text-xs font-medium text-gray-500">Email:</span>
<span className="text-xs text-gray-900 truncate ml-2">{employee.email}</span>
</div>
<div className="flex justify-between">
<span className="text-xs font-medium text-gray-500">Access Level:</span>
<span className="text-xs text-gray-900">Level {employee.authLevel} - {getAuthLevelText(employee.authLevel)}</span>
</div>
<div className="flex justify-between">
<span className="text-xs font-medium text-gray-500">Reports:</span>
<span className="text-xs text-gray-900">{employee._count.reports} reports</span>
</div>
</div>
<div className="flex flex-col space-y-2">
<button
onClick={() => handleEdit(employee)}
className="w-full text-center px-3 py-2 text-sm text-indigo-600 bg-indigo-50 rounded-md hover:bg-indigo-100 transition-colors duration-150"
>
Edit Employee
</button>
{employee.id !== user.id && (
<div className="flex space-x-2">
<Form method="post" className="flex-1">
<input type="hidden" name="intent" value="toggleStatus" />
<input type="hidden" name="id" value={employee.id} />
<button
type="submit"
className={`w-full text-center px-3 py-2 text-sm rounded-md transition-colors duration-150 ${
employee.status === 'active'
? 'text-orange-600 bg-orange-50 hover:bg-orange-100'
: 'text-green-600 bg-green-50 hover:bg-green-100'
}`}
>
{employee.status === 'active' ? 'Deactivate' : 'Activate'}
</button>
</Form>
<Form method="post" className="flex-1">
<input type="hidden" name="intent" value="delete" />
<input type="hidden" name="id" value={employee.id} />
<button
type="submit"
onClick={(e) => {
if (!confirm("Are you sure you want to delete this employee?")) {
e.preventDefault();
}
}}
className="w-full text-center px-3 py-2 text-sm text-red-600 bg-red-50 rounded-md hover:bg-red-100 transition-colors duration-150"
>
Delete
</button>
</Form>
</div>
)}
</div>
</div>
))}
</div>
</div>
{employees.length === 0 && (
<div className="text-center py-12">
<svg className="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z" />
</svg>
<h3 className="mt-2 text-sm font-medium text-gray-900">No employees</h3>
<p className="mt-1 text-sm text-gray-500">Get started by adding your first employee.</p>
<div className="mt-6">
<button
onClick={handleAdd}
className="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700"
>
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Add Employee
</button>
</div>
</div>
)}
</div>
{/* Form Modal */}
<FormModal
isOpen={showModal}
onClose={handleCloseModal}
title={isEditing ? "Edit Employee" : "Add New Employee"}
isSubmitting={isSubmitting}
submitText={isEditing ? "Update Employee" : "Create Employee"}
>
<Form method="post" id="modal-form" className="space-y-4">
<input type="hidden" name="intent" value={isEditing ? "update" : "create"} />
{isEditing && <input type="hidden" name="id" value={editingEmployee?.id} />}
<div className="space-y-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-2">
Full Name
</label>
<input
type="text"
name="name"
id="name"
required
defaultValue={editingEmployee?.name || ""}
className="block w-full border-gray-300 h-9 p-2 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
placeholder="Enter full name"
/>
{actionData?.errors?.name && (
<p className="mt-1 text-sm text-red-600">{actionData.errors.name}</p>
)}
</div>
<div>
<label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-2">
Username
</label>
<input
type="text"
name="username"
id="username"
required
defaultValue={editingEmployee?.username || ""}
className="block w-full border-gray-300 h-9 p-2 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
placeholder="Enter username"
/>
{actionData?.errors?.username && (
<p className="mt-1 text-sm text-red-600">{actionData.errors.username}</p>
)}
</div>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-2">
Email Address
</label>
<input
type="email"
name="email"
id="email"
required
defaultValue={editingEmployee?.email || ""}
className="block w-full border-gray-300 h-9 p-2 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
placeholder="Enter email address"
/>
{actionData?.errors?.email && (
<p className="mt-1 text-sm text-red-600">{actionData.errors.email}</p>
)}
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-2">
Password {isEditing && <span className="text-gray-500">(leave blank to keep current)</span>}
</label>
<input
type="password"
name="password"
id="password"
required={!isEditing}
className="block w-full border-gray-300 h-9 p-2 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
placeholder={isEditing ? "Enter new password" : "Enter password"}
/>
{actionData?.errors?.password && (
<p className="mt-1 text-sm text-red-600">{actionData.errors.password}</p>
)}
</div>
<div>
<label htmlFor="authLevel" className="block text-sm font-medium text-gray-700 mb-2">
Access Level
</label>
<select
name="authLevel"
id="authLevel"
required
defaultValue={editingEmployee?.authLevel || ""}
className="block w-full border-gray-300 h-9 p-2 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
>
<option value="">Select access level</option>
<option value="1">Level 1 - User (Basic Access)</option>
<option value="2">Level 2 - Admin (Management Access)</option>
{user.authLevel === 3 && (
<option value="3">Level 3 - Super Admin (Full Access)</option>
)}
</select>
{actionData?.errors?.authLevel && (
<p className="mt-1 text-sm text-red-600">{actionData.errors.authLevel}</p>
)}
</div>
</div>
{isEditing && editingEmployee?.id !== user.id && (
<div>
<label htmlFor="status" className="block text-sm font-medium text-gray-700 mb-2">
Status
</label>
<select
name="status"
id="status"
defaultValue={editingEmployee?.status || "active"}
className="block w-full border-gray-300 h-9 p-2 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</div>
)}
</div>
{isEditing && (
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-3">
<div className="flex">
<svg className="h-5 w-5 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
<div className="ml-3">
<p className="text-sm text-yellow-700">
Leave password field empty to keep the current password unchanged.
</p>
</div>
</div>
</div>
)}
</Form>
</FormModal>
{/* Toast Notifications */}
{toast && (
<Toast
message={toast.message}
type={toast.type}
onClose={() => setToast(null)}
/>
)}
</div>
</DashboardLayout>
);
}