import { useEffect, useState } from "react"; import { Input } from "@/shared/common/ui/input"; import { Search, ChevronLeft, ChevronRight, Download, RefreshCw, Mail, X, } from "lucide-react"; import { useToast } from "@/shared/common/ui/use-toast"; import axiosInstance from "@/shared/services/axiosInstance"; import { toast } from "sonner"; import { Button } from "@/shared/common/ui/button"; import { MultiSelect } from "@/shared/common/ui/multi-select"; import { withHeaders } from "@/record-management/services/api/withHeaders"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/shared/common/ui/select"; import { SingleSelect } from "@/shared/common/ui/single-select"; import { OrganizationList } from "./lists/OrganizationList"; import { UnitList } from "./lists/UnitList"; import { DepartmentList } from "./lists/DepartmentList"; import { TeamMembers } from "./TeamMembers"; import { AddDepartmentDialog } from "./dialogs/AddDepartmentDialog"; import { AddUserDialog } from "./dialogs/AddUserDialog"; import { ViewUsersDialog } from "./dialogs/ViewUsersDialog"; import { AddSubDepartmentDialog } from "./dialogs/AddSubDepartmentDialog"; import { AddAssignUserDialog } from "./dialogs/AddAssignUserDialog"; import { useOrganizationsData } from "./hooks/useOrganizationsData"; import { useUnitsData } from "./hooks/useUnitsData"; import { useDepartmentsData } from "./hooks/useDepartmentsData"; import { useEmployeesData } from "./hooks/useEmployeesData"; import { useSelectionHandlers } from "./handlers/useSelectionHandlers"; import { useActionHandlers } from "./handlers/useActionHandlers"; import { Breadcrumb } from "./components/Breadcrumb"; import { AddUserUnderDepartmentDialog } from "./dialogs/AddUserUnderDepartmentDialog"; import { useAuth } from "@/shared/context/AuthContext"; import { EditDepartmentDialog } from "./dialogs/EditDepartmentDialog"; import { EditUnitDialog } from "./dialogs/EditUnitDialog"; import { DeleteUnitDialog } from "./dialogs/DeleteUnitDialog"; import { PermanentDeleteUnitDialog } from "./dialogs/PermanentDeleteUnitDialog"; import { AddFromSubCityDialog } from "./dialogs/AddFromSubCityDialog"; import { UnitSelectionModal } from "./components/UnitSelectionModal"; import { AddUnitDialog } from "./dialogs/AddUnitDialog"; import { Plus } from "lucide-react"; import { DeleteDepartmentDialog } from "./dialogs/DeleteDepartmentDialog"; import { DeleteTeamMemberDialog } from "./dialogs/DeleteTeamMemberDialog"; import { TeamMemberDto } from "../dto/teamMember/teamMember"; import { useRef } from "react"; import { UserCircle } from "lucide-react"; import { resendVerificationCode } from "@/shared/services/authService"; import { useLocalizedName } from "@/shared/common/localizedName"; import { MoveDepartmentDialog } from "./dialogs/MoveDepartmentDialog"; import i18n from "@/i18n"; import { t } from "i18next"; import { useExportUserData } from "@/shared/hooks/useExportUserData"; import { DeactivateTeamMemberDialog } from "./dialogs/DeactivateTeamMemberDialog"; import { OrganizationEmployeeSearch } from "./components/OrganizationEmployeeSearch"; import { useGetOrganizationConfig } from "@/super-admin/hooks/useConfig"; import { useUnit } from "@/user-management/hooks/useUnit"; import { useArchiveActions } from "@/user-management/hooks/useArchived"; import { useErrorHandler } from "@/shared/hooks/useErrorHandler"; import { ManageUnitAdminDialog } from "./dialogs/ManageUnitAdminDialog"; const UserManagementTree = () => { const { toast: uiToast } = useToast(); const { softDeletePosition } = useArchiveActions(); const handleArchiveDepartment = (departmentId: string) => { softDeletePosition(departmentId); }; const [searchQuery, setSearchQuery] = useState(""); const [unitSearchQuery, setUnitSearchQuery] = useState(""); const [departmentSearchQuery, setDepartmentSearchQuery] = useState(""); const [employeeSearchQuery, setEmployeeSearchQuery] = useState(""); const { user } = useAuth(); const scrollContainerRef = useRef(null); const localizedName = useLocalizedName(); const lang = i18n.language; // Resend invitation states const [showResendModal, setShowResendModal] = useState(false); const [showConfirmAllModal, setShowConfirmAllModal] = useState(false); const [pendingUsers, setPendingUsers] = useState([]); const [selectedUserIds, setSelectedUserIds] = useState([]); const [isSendingInvitations, setIsSendingInvitations] = useState(false); const [isLoadingPending, setIsLoadingPending] = useState(false); const isOrganizationAdmin = user?.roles?.some((role) => role.key === "organization_admin") ?? false; // Export functionality const { exportUserData, getUnits, isExporting, isLoadingUnits, units: exportUnits, } = useExportUserData(); const [selectedExportUnitId, setSelectedExportUnitId] = useState(""); const [showUnitSelection, setShowUnitSelection] = useState(false); const [exportFormat, setExportFormat] = useState<"xlsx" | "csv">("xlsx"); const [showEmployeeSearch, setShowEmployeeSearch] = useState(false); const [showSendToUnitModal, setShowSendToUnitModal] = useState(false); const [selectedSendUnitId, setSelectedSendUnitId] = useState(""); const { handleError } = useErrorHandler(t); // Hooks for data loading const organizationId = user?.employee && user.employee.length > 0 ? user.employee[0].organizationId : undefined; const { organizations, isLoading: orgsLoading } = useOrganizationsData({ currentOrgId: organizationId, }); const { selectedOrgId, selectedUnitId, selectedDepartmentId, selectedDepartmentName, breadcrumb, handleSelectOrganization, handleSelectUnit, handleSelectDepartment, } = useSelectionHandlers(organizations, localizedName); const [take] = useState(100); const [skip, setSkip] = useState(0); const { units, isLoading: unitsLoading, setUnits, fetchedUnits, } = useUnitsData(selectedOrgId, { take, skip }); const { data: orgConfigResp } = useGetOrganizationConfig(selectedOrgId || ""); const unitCapacity: number | null = (() => { const items = orgConfigResp?.data?.items ?? []; if (!Array.isArray(items) || items.length === 0) return null; const match = items.find((it: any) => it?.organizationId === selectedOrgId) ?? items[0]; const value = Number(match?.maximumNumberOfUnits); return Number.isFinite(value) ? value : null; })(); // Pull child units (units related under one of this org's units) so they // show up alongside the top-level units in the column. Track the expanded // parent separately so clicking on a child (or its action menu) doesn't // collapse the sibling list. const { getChildren } = useUnit(); const [expandedParentId, setExpandedParentId] = useState(""); useEffect(() => { if (!selectedUnitId) return; const isTopLevel = units.some((u) => u.id === selectedUnitId); if (isTopLevel) setExpandedParentId(selectedUnitId); }, [selectedUnitId, units]); useEffect(() => { setExpandedParentId(""); }, [selectedOrgId]); const { data: childUnitsResp } = getChildren(expandedParentId || ""); const childUnits = (() => { const data = childUnitsResp?.data; if (!data) return [] as Array<{ id: string; name: any }>; const items = Array.isArray(data) ? data : (data.items ?? []); return Array.isArray(items) ? items.map((u: any) => ({ id: u.id, name: u.name })) : []; })(); const childUnitIds = new Set(childUnits.map((u) => u.id)); console.log(units, childUnitIds); const mergedUnits = (() => { const seen = new Set(); const out: Array<{ id: string; name: any; isRelated: boolean }> = []; [...units, ...childUnits].forEach((u) => { if (u?.id && !seen.has(u.id)) { seen.add(u.id); out.push({ ...u, isRelated: childUnitIds.has(u.id) }); } }); return out; })(); const [addUnitDialogOpen, setAddUnitDialogOpen] = useState(false); const canCreateUnit = unitCapacity !== null && units.length < unitCapacity && !!selectedOrgId; const { departments, isLoading: deptsLoading, setDepartments, positions, } = useDepartmentsData(selectedUnitId); const { employees, isLoading: employeesLoading, setEmployees, refetch: refetchEmployees, } = useEmployeesData(selectedDepartmentId); // All dialog states & handlers bundled const actionHandlers = useActionHandlers({ selectedOrgId, selectedUnitId, toast: uiToast, }); // Handle inviting or resending invitation to employees const handleInviteEmployee = async (employee: TeamMemberDto) => { try { // Show immediate feedback that action is being processed if (employee.status === "pending") { toast.loading("Resending verification code..."); } else { toast.loading("Sending invitation..."); } await resendVerificationCode({ email: employee.user.email, phoneNumber: employee.user.phoneNumber, }); // Show success notification with more specific message for resend const userName = typeof employee.user.name === "string" ? localizedName(employee.user.name) : localizedName(employee.user.name) || "User"; if (employee.user.status === "pending") { toast.success("Verification Code Resent", { description: `A new verification code has been sent to ${userName}`, duration: 4000, }); } else { toast.success("Invitation Sent", { description: `Invitation sent to ${userName}`, duration: 4000, }); } // Refresh the employee list to update statuses if (refetchEmployees) { await refetchEmployees(); } } catch (error: unknown) { // Show error notification const errorMessage = error instanceof Error ? error.message : "Unknown error"; toast.error("Error", { description: `Failed to ${ employee.status === "pending" ? "resend verification code" : "send invitation" }: ${errorMessage}`, duration: 4000, }); } setTimeout(() => { toast.dismiss(); }, 4000); }; const fetchPendingUsers = async () => { try { setIsLoadingPending(true); // First fetch to get the total count const countResponse = await axiosInstance.get( "/employees/my-unit/pending", { params: { take: 10 }, headers: withHeaders(), }, ); const totalCount = countResponse.data?.count ?? 0; // Fetch all pending users using the total count const response = await axiosInstance.get("/employees/my-unit/pending", { params: { take: totalCount || 3000 }, headers: withHeaders(), }); const data = response.data; const usersList = Array.isArray(data) ? data : Array.isArray(data?.items) ? data.items : []; setPendingUsers(usersList); } catch (err: any) { handleError(err); } finally { setIsLoadingPending(false); } }; useEffect(() => { if (showResendModal) { fetchPendingUsers(); } }, [showResendModal]); const handleResendToAll = async () => { try { setIsSendingInvitations(true); toast.loading("Resending verification to all pending users..."); await axiosInstance.post( "/employees/my-unit/send-verification-to-all-pending", {}, { headers: withHeaders() }, ); toast.dismiss(); toast.success("Successfully resent verification to all pending users."); if (refetchEmployees) { await refetchEmployees(); } } catch (err: any) { toast.dismiss(); handleError(err); } finally { setIsSendingInvitations(false); setShowConfirmAllModal(false); } }; const handleSendSelectedInvitations = async () => { if (selectedUserIds.length === 0) return; try { setIsSendingInvitations(true); toast.loading( `Sending verification to ${selectedUserIds.length} user(s)...`, ); await axiosInstance.post( "/employees/my-unit/generate-verification-codes", { employeeIds: selectedUserIds }, { headers: withHeaders() }, ); toast.dismiss(); toast.success("Successfully sent verification codes."); setShowResendModal(false); setSelectedUserIds([]); if (refetchEmployees) { await refetchEmployees(); } } catch (err: any) { toast.dismiss(); handleError(err); } finally { setIsSendingInvitations(false); } }; const handleSendToSelectedUnits = async () => { if (!selectedSendUnitId) return; try { toast.loading("Sending verification to unit..."); await axiosInstance.post( `/employees/unit/${selectedSendUnitId}/send-verification-to-all-pending`, {}, { headers: withHeaders() }, ); toast.dismiss(); toast.success("Successfully sent verification to selected unit."); setShowSendToUnitModal(false); setSelectedSendUnitId(""); if (refetchEmployees) { await refetchEmployees(); } } catch (err: any) { toast.dismiss(); handleError(err); } }; const scrollLeft = () => { if (scrollContainerRef.current) { scrollContainerRef.current.scrollBy({ left: -200, behavior: "smooth" }); } }; const scrollRight = () => { if (scrollContainerRef.current) { scrollContainerRef.current.scrollBy({ left: 200, behavior: "smooth" }); } }; // Export handlers const handleExportUserData = async () => { if (!organizationId) { toast.error(t("organizationIdRequired")); return; } try { const unitsList = await getUnits(organizationId); if (unitsList && unitsList.length > 0) { setShowUnitSelection(true); } else { toast.error(t("noUnitsFound")); } } catch (error) { console.error("Failed to load units:", error); toast.error(t("exportError")); } }; const handleUnitSelect = async (unitId: string) => { setSelectedExportUnitId(unitId); setShowUnitSelection(false); // Export data for selected unit const result = await exportUserData(unitId, exportFormat); // Reset selectedExportUnitId after export (successful or failed) setSelectedExportUnitId(""); }; return (

{t("organization.userManagement")}

{/* Format selection */} {/* Export button */}
setSearchQuery(e.target.value)} />
0} />
{selectedOrgId && (
{isOrganizationAdmin && ( )}
)}
{/* Mobile view - horizontal scrollable cards */}

{t("organization.organizations")}

{ setUnits([]); setDepartments([]); setEmployees([]); handleSelectOrganization(id); }} canCreateUnitForOrg={(id) => id === selectedOrgId && canCreateUnit } onCreateUnit={() => setAddUnitDialogOpen(true)} />

{t("contentManagement.Units")}
{unitCapacity !== null && ( {mergedUnits.length}/{unitCapacity} )} {canCreateUnit && ( )}

setUnitSearchQuery(e.target.value)} />
{ setDepartments([]); setEmployees([]); handleSelectUnit(id, mergedUnits); }} units={mergedUnits} isLoading={unitsLoading} searchQuery={unitSearchQuery} onAddDepartment={actionHandlers.unit.onAddDepartment} onManageUnitAdmin={actionHandlers.unit.onManageUnitAdmin} onEditUnit={actionHandlers.unit.onEditUnit} onArchiveUnit={actionHandlers.unit.onArchiveUnit} onDeleteUnit={actionHandlers.unit.onDeleteUnit} onAddFromSubCity={actionHandlers.unit.onAddFromSubCity} />

{t("userIncoming.Departments")}

setDepartmentSearchQuery(e.target.value)} />
{ if (id !== selectedDepartmentId) { setEmployees([]); handleSelectDepartment(id, departments); } }} departments={departments} positions={positions} isLoading={deptsLoading} searchQuery={departmentSearchQuery} onAddUser={actionHandlers.department.onAddUserUnderDepartment} onViewUsers={actionHandlers.department.onViewUsers} onAddSubDepartment={ actionHandlers.department.onAddSubDepartment } onEditDepartment={actionHandlers.department.onEditDepartment} onDeleteDepartment={ actionHandlers.department.onDeleteDepartment } onArchiveDepartment={handleArchiveDepartment} onDelegate={actionHandlers.department.onDelegate} onAssignUser={actionHandlers.department.onAssignUser} onMoveDepartment={actionHandlers.department.onMoveDepartment} />

{t("contentManagement.teamMember")}

setEmployeeSearchQuery(e.target.value)} />
{/* Desktop view - grid layout */}

{t("organization.organizations")}

{ setUnits([]); setDepartments([]); setEmployees([]); handleSelectOrganization(id); }} canCreateUnitForOrg={(id) => id === selectedOrgId && canCreateUnit } onCreateUnit={() => setAddUnitDialogOpen(true)} />

{t("contentManagement.Units")} {unitCapacity !== null && ( {mergedUnits.length}/{unitCapacity} )}

setUnitSearchQuery(e.target.value)} />
{ setDepartments([]); setEmployees([]); handleSelectUnit(id, mergedUnits); }} units={mergedUnits} isLoading={unitsLoading} searchQuery={unitSearchQuery} onAddDepartment={actionHandlers.unit.onAddDepartment} onManageUnitAdmin={actionHandlers.unit.onManageUnitAdmin} onEditUnit={actionHandlers.unit.onEditUnit} onArchiveUnit={actionHandlers.unit.onArchiveUnit} onDeleteUnit={actionHandlers.unit.onDeleteUnit} onAddFromSubCity={actionHandlers.unit.onAddFromSubCity} />

{t("userIncoming.Departments")}

setDepartmentSearchQuery(e.target.value)} />
{ if (id !== selectedDepartmentId) { setEmployees([]); handleSelectDepartment(id, departments); } }} departments={departments} positions={positions} isLoading={deptsLoading} searchQuery={departmentSearchQuery} onAddUser={actionHandlers.department.onAddUserUnderDepartment} onViewUsers={actionHandlers.department.onViewUsers} onAddSubDepartment={actionHandlers.department.onAddSubDepartment} onEditDepartment={actionHandlers.department.onEditDepartment} onDeleteDepartment={actionHandlers.department.onDeleteDepartment} onArchiveDepartment={handleArchiveDepartment} onDelegate={actionHandlers.department.onDelegate} onAssignUser={actionHandlers.department.onAssignUser} onMoveDepartment={actionHandlers.department.onMoveDepartment} />

{t("contentManagement.teamMember")}

setEmployeeSearchQuery(e.target.value)} />
{/* Dialogs */} {actionHandlers.dialogs.manageUnitAdmin.isOpen && ( )} {actionHandlers.dialogs.userUnderDepartment.isOpen && ( )} {actionHandlers.dialogs.editUnit.isOpen && ( )} {actionHandlers.dialogs.deleteUnit.isOpen && ( )} {actionHandlers.dialogs.permanentDeleteUnit.isOpen && ( )} {actionHandlers.dialogs.addFromSubCity.isOpen && ( )} {addUnitDialogOpen && ( setAddUnitDialogOpen(false)} organizationId={selectedOrgId} /> )} {actionHandlers.dialogs.department.isOpen && ( )} {actionHandlers.dialogs.editDepartment.isOpen && ( )} {actionHandlers.dialogs.deleteDepartment.isOpen && ( )} {actionHandlers.dialogs.subDepartment.isOpen && ( )} {actionHandlers.dialogs.moveDepartment.isOpen && ( )} {actionHandlers.dialogs.user.isOpen && ( )} {actionHandlers.dialogs.viewUsers.isOpen && ( )} {actionHandlers.dialogs.assignUser.isOpen && ( )} {actionHandlers.dialogs.deleteTeamMember.isOpen && ( )} {actionHandlers.dialogs.deactivateTeamMember.isOpen && ( )} {/* Unit Selection Modal */} {showUnitSelection && (
setShowUnitSelection(false)} />
)} {/* Confirm Resend to All Modal */} {showConfirmAllModal && (

{t( "userManagement.confirmResendTitle", "Resend Verification Code", )}

{t( "userManagement.confirmResendDescription", "Are you sure you want to resend verification to all pending users?", )}

)} {/* Resend Invitation (MultiSelect) Modal */} {showResendModal && (

{t("userManagement.resendInvitation", "Resend Invitation")}

{t( "userManagement.selectUsersDescription", "Select the users you want to resend invitations to:", )}

{/* Scrollable container for the dropdown selection and items */}
{isLoadingPending ? (
) : pendingUsers.length === 0 ? (
{t( "userManagement.noPendingUsers", "No pending users found in your unit.", )}
) : (
{ const empName = typeof emp.name === "string" ? emp.name : localizedName(emp.name) || (emp.user?.name ? localizedName(emp.user.name) : "Unnamed"); return { label: empName, value: emp.id, }; })} value={selectedUserIds} onValueChange={setSelectedUserIds} placeholder={t( "userManagement.selectUsersPlaceholder", "Select employees...", )} maxCount={5} />
)}
)} {/* Organization Employee Search Modal */} setShowEmployeeSearch(false)} /> {showSendToUnitModal && (

{t("userManagement.sendToSelectedUnit", "Send to Selected Unit")}

Select the unit you want to send verification to:

{ const unitName = typeof unit.name === "string" ? unit.name : localizedName(unit.name) || (typeof unit.name === "object" && unit.name?.en) || unit.id || "Unnamed Unit"; return { label: unitName, value: unit.id, }; })} value={selectedSendUnitId} onValueChange={setSelectedSendUnitId} placeholder={t( "search.selectUnitPlaceholder", "Select unit...", )} />
)}
); }; export default UserManagementTree;