import { cn } from "@/super-admin/lib/utils"; import { UserCircle, Mail, Check, Clock, AlertCircle, UserPen, RefreshCw, MoreVertical, Phone, Eye, PhoneCall, UserMinus, Briefcase, } from "lucide-react"; import { Badge } from "@/shared/common/ui/badge"; import { Button } from "@/shared/common/ui/button"; import { TeamMemberDto } from "../dto/teamMember/teamMember"; import { useEffect, useState } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/shared/common/ui/dialog"; import { Avatar, AvatarFallback } from "@/shared/common/ui/avatar"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/shared/common/ui/tooltip"; import { motion } from "framer-motion"; import { Input } from "@/shared/common/ui/input"; import { t } from "i18next"; import { toast } from "sonner"; import { updateProfile } from "../services/api/employeePositionsService"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/shared/common/ui/dropdown-menu"; import { useLocalizedName } from "@/shared/common/localizedName"; import { EmployeePositionsDialog } from "./dialogs/EmployeePositionsDialog"; interface TeamMembersProps { departmentId: string; departmentName: string; employees: TeamMemberDto[]; isLoading: boolean; searchQuery?: string; onEditEmployee?: (employee: TeamMemberDto) => void; onInviteEmployee?: (employee: TeamMemberDto) => void; onDeleteEmployee?: ( departmentId: string, departmentName: string, teamMemberId: string, teamMemberName: string, ) => void; onDeactivateTeamMember?: (userId: string, teamMemberName: string) => void; employeePositions?: Array<{ [key: string]: Array<{ id: string; employeeId: string; position: { id: string; name: { am: string; en: string; }; }; }>; }>; } type UserStatus = "accepted" | "pending" | "Not Invited"; const statusStyles: Record< UserStatus, { bg: string; text: string; icon: React.ReactNode } > = { accepted: { bg: "bg-primary-100 dark:bg-primary-900/40", text: "text-primary-800 dark:text-primary-300", icon: , }, pending: { bg: "bg-yellow-100 dark:bg-yellow-900/40", text: "text-yellow-800 dark:text-yellow-300", icon: , }, "Not Invited": { bg: "bg-gray-100 dark:bg-gray-700", text: "text-gray-800 dark:text-gray-300", icon: , }, }; export const TeamMembers = ({ departmentId, departmentName, employees = [], isLoading = false, searchQuery = "", onEditEmployee, onInviteEmployee, onDeleteEmployee, onDeactivateTeamMember, employeePositions, }: TeamMembersProps) => { const [selectedEmployee, setSelectedEmployee] = useState(null); const [showUserDetails, setShowUserDetails] = useState(false); const [showEditPage, setEditPage] = useState(false); const [showPositionsDialog, setShowPositionsDialog] = useState(false); const [userName, setUserName] = useState(""); const [email, setEmail] = useState(""); const [englishName, setEnglishName] = useState(""); const [amharicName, setAmharicName] = useState(""); const [phoneNumber, setPhoneNumber] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [emploeeId, setEmployeeId] = useState(""); const [errors, setErrors] = useState<{ [key: string]: string }>({}); const localizedName = useLocalizedName(); // Filter employees based on search query const filteredEmployees = employees.filter((employee) => { if (!searchQuery) return true; const name = localizedName(employee.user.name); const email = employee.user.email || ""; const username = employee.user.username || ""; const phoneNumber = employee.user.phoneNumber || ""; const query = searchQuery.toLowerCase(); return ( name.toLowerCase().includes(query) || email.toLowerCase().includes(query) || username.toLowerCase().includes(query) || phoneNumber.toLowerCase().includes(query) ); }); const renderStatusBadge = (status: string) => { const fallback = { bg: "bg-gray-100", text: "text-gray-700", icon: null, }; const style = (statusStyles as Record)[status] ?? fallback; return ( {style.icon} {status} ); }; const getUserStatus = (employee: TeamMemberDto): UserStatus => { if (employee.user.hasSetPassword) { return "accepted"; } return "pending"; }; const handleResendClick = (employee: TeamMemberDto) => { if (onInviteEmployee) { onInviteEmployee(employee); } }; const handleViewUserDetail = (employee: TeamMemberDto) => { setSelectedEmployee(employee); setShowUserDetails(true); }; const handleShowUserDetails = (employee: TeamMemberDto) => { setSelectedEmployee(employee); setShowUserDetails(true); }; const handleEditPage = (employee: TeamMemberDto) => { setSelectedEmployee(employee); setEditPage(true); setEmployeeId(employee?.user?.id); }; const handleShowPositions = (employee: TeamMemberDto) => { setSelectedEmployee(employee); setShowPositionsDialog(true); }; const getInitials = (name: string) => { const splittedName = name.trim().split(" "); return splittedName.length === 1 ? splittedName[0][0] : `${splittedName[0][0]}${splittedName[1][0]}`; }; useEffect(() => { if (showEditPage && selectedEmployee) { setUserName(selectedEmployee?.user.username ?? ""); setEmail(selectedEmployee?.user.email ?? ""); setEnglishName(selectedEmployee?.user.name.en ?? ""); setAmharicName(selectedEmployee?.user.name.am ?? ""); setPhoneNumber(selectedEmployee?.user.phoneNumber ?? ""); setErrors({}); setError(""); } }, [showEditPage, selectedEmployee]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const newErrors: { [key: string]: string } = {}; if (!email?.trim()) newErrors.email = t("profile.emailRequired"); if (!userName?.trim()) newErrors.userName = t("profile.usernameRequired"); if (!phoneNumber?.trim()) newErrors.phoneNumber = t("profile.phoneRequired"); if (!englishName?.trim()) newErrors.englishName = t("profile.englishNameRequired"); if (!amharicName?.trim()) newErrors.amharicName = t("profile.amharicNameRequired"); setErrors(newErrors); if (Object.keys(newErrors).length > 0) return; const phoneRegex = /^(\+2519\d{8}|09\d{8})$/; if (!phoneRegex.test(phoneNumber ?? "")) { setError(t("profile.invalidPhoneFormat")); toast.error(t("profile.invalidPhoneFormat"), { description: t("profile.validPhoneFormat"), }); return; } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email ?? "")) { setError(t("profile.invalidEmailFormat")); toast.error(t("profile.invalidEmailFormat"), { description: t("profile.validEmailFormat"), }); return; } setLoading(true); try { await updateProfile( { email, username: userName, phoneNumber, name: { am: amharicName, en: englishName, }, }, emploeeId, ); toast.success(t("profile.profileUpdateSuccess"), { description: t("profile.profileUpdated"), }); } catch (error: any) { const message = error?.message || t("profile.profileUpdateFailed"); setError(message); toast.error(t("profile.profileUpdateFailed"), { description: message, }); } finally { setLoading(false); } }; const ActionDropdown = ({ employee }: { employee: TeamMemberDto }) => { // getUserStatus only ever yields "accepted" | "pending" — a member with no // password set is "pending", so anything else has one already. const isPending = getUserStatus(employee) === "pending"; return ( e.stopPropagation()}> {/* Edit Option */} { e.stopPropagation(); handleShowUserDetails(employee); }} className="cursor-pointer dark:text-gray-200 dark:hover:bg-gray-600"> {t("viewDetail.viewDetail")} { e.stopPropagation(); handleEditPage(employee); }} className="cursor-pointer dark:text-gray-200 dark:hover:bg-gray-600"> {t("common.Edit")} {/* Show Positions Option */} { e.stopPropagation(); handleShowPositions(employee); }} className="cursor-pointer dark:text-gray-200 dark:hover:bg-gray-600"> {t("positions.viewPositions")} {/* Resend/Invite Option. Offered for accepted members too: the code this sends lets them set a new password, so for someone who already has one it is a reset — labelled as such rather than as an invite, which would misdescribe what the operator is doing. */} {onInviteEmployee && ( { e.stopPropagation(); handleResendClick(employee); }} className="cursor-pointer dark:text-gray-200 dark:hover:bg-gray-600"> {isPending ? t("Resend Invite") : t("Send Password Reset")} )} {/* Remove Option */} {onDeleteEmployee && ( { e.stopPropagation(); const memberName = typeof employee.user.name === "string" ? employee.user.name : (employee.user.name as any)?.en || (employee.user.name as any)?.am || "User"; onDeleteEmployee( departmentId, departmentName, employee.id, memberName, ); }} className="cursor-pointer text-red-600 focus:text-red-600 dark:focus:text-red-400"> {t("signatureUpload.remove")} )} {onDeactivateTeamMember && ( { e.stopPropagation(); const memberName = typeof employee.user.name === "string" ? employee.user.name : (employee.user.name as any)?.en || (employee.user.name as any)?.am || "User"; onDeactivateTeamMember(employee.id, memberName); }} className="cursor-pointer text-red-600 focus:text-red-600 dark:focus:text-red-400"> {t("common.DeactivateUser")} )} ); }; const renderEmployeeItem = (employee: TeamMemberDto) => { const status = getUserStatus(employee); return ( {/* Left side - Employee info */} handleShowUserDetails(employee)}> {getInitials(localizedName(employee.user.name)).toUpperCase()} {localizedName(employee.user.name)} {employee.user.userType || "Employee"} {employee.user.phoneNumber || "Employee"} {renderStatusBadge(status)} {/* Right side - Action dropdown */} {/* Status-specific (optional - you can remove this if you want all actions in dropdown) */} {/* Three dots dropdown menu */} ); }; const renderContent = () => { if (isLoading) { return ( Loading team members... ); } if (filteredEmployees.length > 0) { return ( {filteredEmployees.map(renderEmployeeItem)} ); } if (employees.length > 0 && filteredEmployees.length === 0) { return ( {t("search.noResults")} {t("search.tryDifferentKeywords")} ); } return ( {departmentId ? t("contentManagement.msg1") : t("contentManagement.msg2")} {departmentId ? t("contentManagement.msg3") : t("contentManagement.msg4")} ); }; return ( {renderContent()} {/* User Details Modal */} setShowUserDetails(open)}> {t("contentManagement.userDetail")} {selectedEmployee && ( {getInitials( localizedName(selectedEmployee.user.name), ).toUpperCase()} {localizedName(selectedEmployee.user.name)} {selectedEmployee.user.userType || "Employee"} {getUserStatus(selectedEmployee) && ( {renderStatusBadge(getUserStatus(selectedEmployee))} )} {t("organization.username")} {selectedEmployee.user.username || "Not provided"} {t("organization.email")} {selectedEmployee.user.email || "Not provided"} {t("organization.phoneNumber")} {selectedEmployee.user.phoneNumber} )} {/* Edit Modal */} setEditPage(open)}> {t("contentManagement.editEmployee")} {selectedEmployee && ( {getInitials( localizedName(selectedEmployee.user.name), ).toUpperCase()} {localizedName(selectedEmployee.user.name)} {selectedEmployee.user.userType || "Employee"} {getUserStatus(selectedEmployee) && ( {renderStatusBadge(getUserStatus(selectedEmployee))} )} {t("organization.username")} setUserName(e.target.value)} className="w-full dark:bg-gray-600 dark:text-gray-200 dark:border-gray-500" /> {errors.userName && ( {errors.userName} )} {t("profile.englishName")} setEnglishName(e.target.value)} className="w-full dark:bg-gray-600 dark:text-gray-200 dark:border-gray-500" /> {errors.englishName && ( {errors.englishName} )} {t("profile.amharicName")} setAmharicName(e.target.value)} className="w-full dark:bg-gray-600 dark:text-gray-200 dark:border-gray-500" /> {errors.amharicName && ( {errors.amharicName} )} {t("organization.email")} setEmail(e.target.value)} className="w-full dark:bg-gray-600 dark:text-gray-200 dark:border-gray-500" /> {errors.email && ( {errors.email} )} {t("organization.phoneNumber")} setPhoneNumber(e.target.value)} className="w-full dark:bg-gray-600 dark:text-gray-200 dark:border-gray-500" /> {errors.phoneNumber && ( {errors.phoneNumber} )} {loading ? ( {t("profile.updating")}... ) : ( t("profile.updateProfile") )} )} {/* Employee Positions Dialog */} {selectedEmployee && ( setShowPositionsDialog(false)} employeeName={localizedName(selectedEmployee.user.name)} positions={selectedEmployee.employeePositions || []} onPositionRemoved={() => { // Optionally refresh the positions if needed setShowPositionsDialog(false); }} /> )} ); };
Loading team members...
{t("search.noResults")}
{t("search.tryDifferentKeywords")}
{departmentId ? t("contentManagement.msg1") : t("contentManagement.msg2")}
{departmentId ? t("contentManagement.msg3") : t("contentManagement.msg4")}
{selectedEmployee.user.userType || "Employee"}
{t("organization.username")}
{selectedEmployee.user.username || "Not provided"}
{t("organization.email")}
{selectedEmployee.user.email || "Not provided"}
{t("organization.phoneNumber")}
{selectedEmployee.user.phoneNumber}
{errors.userName}
{t("profile.englishName")}
{errors.englishName}
{t("profile.amharicName")}
{errors.amharicName}
{errors.email}
{errors.phoneNumber}