pmtool / src /pages /employee-edit.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
21.3 kB
import React, { useState, useEffect } from "react";
import { useNavigate, useParams, useLocation } from "react-router-dom";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { createRoot } from "react-dom/client";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Dropdown, DropdownOption } from "@/components/ui/dropdown";
import { toast } from "@/lib/custom-toast";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { ChevronLeft, Loader2, ArrowLeft, LockIcon, InfoIcon } from "lucide-react";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { employeeService, roleService } from "@/lib/api";
import { Employee, EmployeeRegistrationRequest, Role } from "@/types";
import Header from "@/components/layout/header";
import Sidebar from "@/components/layout/sidebar";
import { useIsMobile } from "@/hooks/use-mobile";
import { Skeleton } from "@/components/ui/skeleton";
import { useAuth } from "@/lib/auth-context";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
// Function to show toast with longer duration
const showLongToast = (message: string, type: 'success' | 'error' = 'success') => {
if (type === 'success') {
toast.success(message, { duration: 10000 });
} else {
toast.error(message, { duration: 10000 });
}
};
const EmployeeEdit = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const location = useLocation();
const queryClient = useQueryClient();
const isMobile = useIsMobile();
const [isSidebarOpen, setIsSidebarOpen] = useState(!isMobile);
const { userData } = useAuth();
// Close sidebar on mobile when route changes
useEffect(() => {
if (isMobile) {
setIsSidebarOpen(false);
}
}, [location, isMobile]);
// Update sidebar state when screen size changes
useEffect(() => {
setIsSidebarOpen(!isMobile);
}, [isMobile]);
// Check if user has HR or Admin permissions
const hasHrAdminPermission = ['HR', 'Admin', 'Manager', 'Super Admin', 'HR Manager', 'Sr. HR Manager'].includes(userData?.roleName || '');
console.log('userData:', userData);
console.log('roleName:', userData?.roleName);
console.log('hasHrAdminPermission:', hasHrAdminPermission);
// Check if editing own profile
const [isOwnProfile, setIsOwnProfile] = useState(false);
// Employee form state
const [formData, setFormData] = useState<EmployeeRegistrationRequest>({
id: 0,
empCode: "",
firstName: "",
middleName: "",
lastName: "",
email: "",
mobile: "",
type: "",
academics: "",
financialData: "",
description: "",
passwordHash: "",
roleId: 1
});
// Store the original restricted field values
const [originalValues, setOriginalValues] = useState({
email: "",
empCode: "",
type: "",
roleId: 0
});
// Fetch employee details
const { data: employee, isLoading, error } = useQuery({
queryKey: ["employee", id],
queryFn: () => employeeService.getById(Number(id)),
enabled: !!id,
staleTime: 300000 // 5 minutes
});
// Fetch roles
const { data: roles, isLoading: isLoadingRoles } = useQuery({
queryKey: ["roles"],
queryFn: roleService.getAll,
});
// Set form data when employee data is loaded
useEffect(() => {
if (employee) {
setFormData({
...employee,
// Use an empty string for password since we don't want to pre-fill it
passwordHash: ""
});
// Store original values for restricted fields
setOriginalValues({
email: employee.email,
empCode: employee.empCode,
type: employee.type,
roleId: employee.roleId
});
}
}, [employee]);
// Check if this is the user's own profile
useEffect(() => {
if (employee && userData) {
setIsOwnProfile(employee.userId === userData.userId);
}
}, [employee, userData]);
// Handle form input changes
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
// For restricted fields, only update if user has permission
if (['email', 'empCode', 'type'].includes(name) && !hasHrAdminPermission) {
return;
}
setFormData(prev => ({
...prev,
[name]: value
}));
};
// Handle select input changes
const handleSelectChange = (name: string, value: string) => {
setFormData(prev => ({
...prev,
[name]: value
}));
};
// Handle role selection
const handleRoleChange = (value: string) => {
// Only update role if user has permission
if (!hasHrAdminPermission) {
return;
}
setFormData(prev => ({
...prev,
roleId: parseInt(value)
}));
};
// Update mutation
const updateMutation = useMutation({
mutationFn: (data: EmployeeRegistrationRequest) => {
// If user doesn't have HR/Admin permission, restore original values for restricted fields
if (!hasHrAdminPermission) {
return employeeService.update({
...data,
email: originalValues.email,
empCode: originalValues.empCode,
type: originalValues.type,
roleId: originalValues.roleId
});
}
return employeeService.update(data);
},
onSuccess: (data) => {
// Use response message if available
const successMessage = data?.message || `Employee '${formData.firstName} ${formData.lastName}' has been successfully updated`;
toast.success(successMessage);
queryClient.invalidateQueries({ queryKey: ["employees"] });
queryClient.invalidateQueries({ queryKey: ["employee", id] });
// Navigate based on user role and profile ownership
if (hasHrAdminPermission || !isOwnProfile) {
navigate("/employees");
} else {
// Regular users editing their own profile should go back to tasks
navigate("/tasks");
}
},
onError: (error: any) => {
const errorMessage = error?.response?.data?.message || "Failed to update employee";
toast.error(errorMessage);
}
});
// Toggle sidebar
const toggleSidebar = () => {
setIsSidebarOpen(!isSidebarOpen);
};
// Handle form submission
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
updateMutation.mutate(formData);
};
// Handle navigation back
const handleBack = () => {
if (id) {
navigate(`/employee/${id}`);
} else {
if (hasHrAdminPermission) {
navigate("/employees");
} else {
navigate("/tasks");
}
}
};
if (isLoading || isLoadingRoles) {
return (
<div className="flex min-h-screen items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
</div>
);
}
if (!employee) {
return (
<div className="flex min-h-screen items-center justify-center">
<p className="text-destructive">Employee not found</p>
</div>
);
}
return (
<div className="flex min-h-screen flex-col">
<Header toggleSidebar={toggleSidebar} isSidebarOpen={isSidebarOpen} />
<Sidebar isOpen={isSidebarOpen} setIsOpen={setIsSidebarOpen} />
<main className={`flex-1 p-4 pt-20 md:p-6 md:pt-24 ${isSidebarOpen ? "md:ml-64" : ""} transition-all duration-300 overflow-hidden`}>
<ScrollArea className="h-[calc(100vh-6rem)]">
<div className="mx-auto max-w-3xl pb-8">
<div className="mb-6">
<div className="flex items-center mb-2">
<Button
variant="ghost"
onClick={handleBack}
className="mr-2"
size="sm"
>
<ChevronLeft className="h-4 w-4 mr-1" />
Back to {isOwnProfile && !hasHrAdminPermission ? "Profile" : "Employee"}
</Button>
<h2 className="text-2xl md:text-3xl font-bold tracking-tight">Edit Employee</h2>
</div>
<p className="text-muted-foreground">
Update employee information
</p>
{!hasHrAdminPermission && (
<div className="mt-2 p-3 bg-amber-50 border border-amber-200 rounded-md text-amber-800 text-sm flex items-start gap-2">
<InfoIcon className="h-5 w-5 shrink-0 mt-0.5" />
<div>
<p className="font-medium">Restricted Access</p>
<p>Some fields can only be edited by HR or Admin users. These fields are marked with a lock icon.</p>
</div>
</div>
)}
</div>
<form onSubmit={handleSubmit}>
<Card>
<CardHeader className="pb-4">
<CardTitle>Personal Information</CardTitle>
<CardDescription>
Edit the employee's personal details
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
<div className="space-y-2">
<Label htmlFor="empCode" className="flex items-center gap-1">
Employee Code*
{!hasHrAdminPermission && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<LockIcon className="h-3.5 w-3.5 text-amber-500" />
</TooltipTrigger>
<TooltipContent>
<p>Only HR and Admin can edit this field</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</Label>
<Input
id="empCode"
name="empCode"
value={formData.empCode}
onChange={handleChange}
placeholder="EMP-001"
required
disabled={!hasHrAdminPermission}
className={!hasHrAdminPermission ? "bg-muted cursor-not-allowed" : ""}
/>
</div>
<div className="space-y-2">
<Label htmlFor="firstName">First Name*</Label>
<Input
id="firstName"
name="firstName"
value={formData.firstName}
onChange={handleChange}
placeholder="John"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="middleName">Middle Name</Label>
<Input
id="middleName"
name="middleName"
value={formData.middleName}
onChange={handleChange}
placeholder="David"
/>
</div>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
<div className="space-y-2">
<Label htmlFor="lastName">Last Name*</Label>
<Input
id="lastName"
name="lastName"
value={formData.lastName}
onChange={handleChange}
placeholder="Doe"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="email" className="flex items-center gap-1">
Email*
{!hasHrAdminPermission && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<LockIcon className="h-3.5 w-3.5 text-amber-500" />
</TooltipTrigger>
<TooltipContent>
<p>Only HR and Admin can edit this field</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</Label>
<Input
id="email"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
placeholder="john.doe@example.com"
required
disabled={!hasHrAdminPermission}
className={!hasHrAdminPermission ? "bg-muted cursor-not-allowed" : ""}
/>
</div>
<div className="space-y-2">
<Label htmlFor="mobile">Mobile Number*</Label>
<Input
id="mobile"
name="mobile"
value={formData.mobile}
onChange={handleChange}
placeholder="9876543210"
required
/>
</div>
</div>
<Separator className="my-2" />
<h3 className="text-lg font-medium">Professional Details</h3>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="type" className="flex items-center gap-1">
Employee Type*
{!hasHrAdminPermission && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<LockIcon className="h-3.5 w-3.5 text-amber-500" />
</TooltipTrigger>
<TooltipContent>
<p>Only HR and Admin can edit this field</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</Label>
<Input
id="type"
name="type"
value={formData.type}
onChange={handleChange}
placeholder="Software Engineer"
required
disabled={!hasHrAdminPermission}
className={!hasHrAdminPermission ? "bg-muted cursor-not-allowed" : ""}
/>
</div>
<div className="space-y-2">
<Label htmlFor="academics">Academics*</Label>
<Input
id="academics"
name="academics"
value={formData.academics}
onChange={handleChange}
placeholder="BSc Computer Science"
required
/>
</div>
</div>
{/* <div className="space-y-2">
<Label htmlFor="financialData">Financial Data</Label>
<Textarea
id="financialData"
name="financialData"
value={formData.financialData}
onChange={handleChange}
placeholder="Financial information (optional)"
rows={3}
className="resize-none"
/>
</div> */}
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
name="description"
value={formData.description}
onChange={handleChange}
placeholder="Additional information about the employee (optional)"
rows={3}
className="resize-none"
/>
</div>
<Separator className="my-2" />
<h3 className="text-lg font-medium">Account Details</h3>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="passwordHash">
Password{!id ? "*" : " (Leave blank to keep current password)"}
</Label>
<Input
id="passwordHash"
name="passwordHash"
type="password"
value={formData.passwordHash}
onChange={handleChange}
placeholder={id ? "Leave blank to keep current password" : "Enter password"}
required={!id}
/>
</div>
<div className="space-y-2">
<Label htmlFor="roleId" className="flex items-center gap-1">
Role <span className="text-destructive">*</span>
{!hasHrAdminPermission && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<LockIcon className="h-3.5 w-3.5 text-amber-500" />
</TooltipTrigger>
<TooltipContent>
<p>Only HR and Admin can edit this field</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</Label>
<Dropdown
options={roles?.map((role: Role) => ({
label: role.roleName,
value: role.roleId.toString()
})) || []}
value={formData.roleId.toString()}
onValueChange={handleRoleChange}
placeholder="Select role"
disabled={!hasHrAdminPermission}
className={!hasHrAdminPermission ? "opacity-70 cursor-not-allowed" : ""}
/>
</div>
</div>
</CardContent>
<CardFooter className="flex flex-col sm:flex-row justify-between gap-3 pt-6 border-t">
<Button
variant="outline"
onClick={handleBack}
className="w-full sm:w-auto"
>
<ChevronLeft className="mr-2 h-4 w-4" />
Cancel
</Button>
<Button
type="submit"
disabled={updateMutation.isPending}
className="w-full sm:w-auto"
>
{updateMutation.isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Updating...
</>
) : (
<>
Save Changes
</>
)}
</Button>
</CardFooter>
</Card>
</form>
</div>
</ScrollArea>
</main>
</div>
);
};
export default EmployeeEdit;