import { useState, useEffect, type ReactElement, forwardRef, useImperativeHandle, } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Plus, ArrowUpDown, Loader2 } from "lucide-react"; import { PrimaryButton, SecondaryButton, StatusBadge, ActionDropdown, DataTable, Pagination, FilterDropdown, SearchBox, Modal, FormField, FormSelect, type Column, } from "@/components/shared"; import { userService } from "@/services/user-service"; import { roleService } from "@/services/role-service"; import type { Role } from "@/types/role"; import type { User } from "@/types/user"; import { showToast } from "@/utils/toast"; import { formatDate } from "@/utils/format-date"; import { usePermissions } from "@/hooks/usePermissions"; // Helper function to get user initials const getUserInitials = (firstName: string | null | undefined, lastName: string | null | undefined): string => { const first = (firstName || "").trim().slice(0, 1); const last = (lastName || "").trim().slice(0, 1); return `${first}${last}`.toUpperCase() || "?"; }; // Helper function to get status badge variant const getStatusVariant = ( status: string, ): "success" | "failure" | "process" => { switch (status?.toLowerCase()) { case "active": return "success"; case "deleted": return "failure"; case "suspended": return "process"; default: return "success"; } }; // Zod schemas for validation const newPlatformUserSchema = z.object({ email: z.string().email({ message: "Please enter a valid email address" }), first_name: z .string() .min(1, "First name is required") .max(255, "Maximum 255 characters allowed"), last_name: z .string() .min(1, "Last name is required") .max(255, "Maximum 255 characters allowed"), status: z.enum(["active", "suspended", "deleted"], { message: "Status is required", }), role_id: z.string().min(1, "Role is required"), }); type NewPlatformUserFormData = z.infer; const editPlatformUserSchema = z.object({ email: z.string().email({ message: "Please enter a valid email address" }), first_name: z .string() .min(1, "First name is required") .max(255, "Maximum 255 characters allowed"), last_name: z .string() .min(1, "Last name is required") .max(255, "Maximum 255 characters allowed"), status: z.enum(["active", "suspended", "deleted"], { message: "Status is required", }), role_id: z.string().min(1, "Role is required"), }); type EditPlatformUserFormData = z.infer; // Dedicated Creation Modal interface NewPlatformUserModalProps { isOpen: boolean; onClose: () => void; onSubmit: (data: NewPlatformUserFormData) => Promise; isLoading: boolean; roles: Role[]; } const NewPlatformUserModal = ({ isOpen, onClose, onSubmit, isLoading, roles, }: NewPlatformUserModalProps): ReactElement | null => { const { register, handleSubmit, setValue, watch, reset, setError, formState: { errors }, } = useForm({ resolver: zodResolver(newPlatformUserSchema), defaultValues: { status: "active", role_id: "", }, }); const statusValue = watch("status"); const roleIdValue = watch("role_id"); useEffect(() => { if (!isOpen) { reset({ email: "", first_name: "", last_name: "", status: "active", role_id: "", }); } }, [isOpen, reset]); const handleFormSubmit = async (data: NewPlatformUserFormData) => { try { await onSubmit(data); } catch (error: any) { if ( error?.response?.data?.details && Array.isArray(error.response.data.details) ) { const first = error.response.data.details[0]; setError("root", { type: "server", message: first?.message || "Validation failed", }); return; } const message = error?.response?.data?.error?.message || error?.response?.data?.message || error?.message || "Failed to create platform user. Please try again."; setError("root", { type: "server", message }); } }; return ( Cancel {isLoading ? "Creating..." : "Create User"} } >
{errors.root && (

{errors.root.message}

)}
({ value: r.id, label: r.name }))} value={roleIdValue || ""} onValueChange={(value) => setValue("role_id", value, { shouldValidate: true }) } error={errors.role_id?.message} /> setValue("status", value as "active" | "suspended" | "deleted", { shouldValidate: true, }) } error={errors.status?.message} />
); }; // Dedicated Edit Modal interface EditPlatformUserModalProps { isOpen: boolean; onClose: () => void; userId: string | null; onSubmit: (id: string, data: EditPlatformUserFormData) => Promise; isLoading: boolean; roles: Role[]; } const EditPlatformUserModal = ({ isOpen, onClose, userId, onSubmit, isLoading, roles, }: EditPlatformUserModalProps): ReactElement | null => { const [loadingData, setLoadingData] = useState(false); const [loadedUser, setLoadedUser] = useState(null); const { register, handleSubmit, setValue, watch, reset, setError, formState: { errors }, } = useForm({ resolver: zodResolver(editPlatformUserSchema), }); const statusValue = watch("status"); const roleIdValue = watch("role_id"); const selectedRole = roles.find((r) => r.id === roleIdValue); const primaryRoleName = loadedUser?.role_module_combinations?.[0]?.role_name || ""; const primaryRoleCode = loadedUser?.role_module_combinations?.[0]?.role_code || ""; const isSuperAdminUser = primaryRoleName === "Super Admin" || primaryRoleName === "super_admin" || primaryRoleCode === "super_admin" || selectedRole?.name === "Super Admin" || selectedRole?.code === "super_admin"; useEffect(() => { if (isOpen && userId) { setLoadingData(true); userService .getById(userId) .then((response) => { if (response.success && response.data) { const user = response.data; setLoadedUser(user); const primaryRole = user.role_module_combinations?.[0]?.role_id || ""; reset({ email: user.email || "", first_name: user.first_name || "", last_name: user.last_name || "", status: user.status || "active", role_id: primaryRole, }); } }) .catch((err) => { console.error("Failed to load platform user details", err); showToast.error("Failed to load user details"); onClose(); }) .finally(() => { setLoadingData(false); }); } else if (!isOpen) { setLoadedUser(null); } }, [isOpen, userId, reset, onClose]); const handleFormSubmit = async (data: EditPlatformUserFormData) => { if (!userId) return; try { await onSubmit(userId, data); } catch (error: any) { if ( error?.response?.data?.details && Array.isArray(error.response.data.details) ) { const first = error.response.data.details[0]; setError("root", { type: "server", message: first?.message || "Validation failed", }); return; } const message = error?.response?.data?.error?.message || error?.response?.data?.message || error?.message || "Failed to update platform user. Please try again."; setError("root", { type: "server", message }); } }; return ( Cancel {isLoading ? "Saving..." : "Save Changes"} } > {loadingData ? (

Loading user details...

) : (
{errors.root && (

{errors.root.message}

)}
{!isSuperAdminUser && ( <> ({ value: r.id, label: r.name }))} value={roleIdValue || ""} onValueChange={(value) => setValue("role_id", value, { shouldValidate: true }) } error={errors.role_id?.message} /> setValue( "status", value as "active" | "suspended" | "deleted", { shouldValidate: true }, ) } error={errors.status?.message} /> )}
)}
); }; // Dedicated View Modal interface ViewPlatformUserModalProps { isOpen: boolean; onClose: () => void; userId: string | null; } const ViewPlatformUserModal = ({ isOpen, onClose, userId, }: ViewPlatformUserModalProps): ReactElement | null => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { if (isOpen && userId) { setLoading(true); userService .getById(userId) .then((response) => { if (response.success && response.data) { setUser(response.data); } }) .catch((err) => { console.error("Failed to fetch user details", err); showToast.error("Failed to load user details"); }) .finally(() => { setLoading(false); }); } }, [isOpen, userId]); return ( Close } > {loading ? (

Loading details...

) : user ? (
{getUserInitials(user.first_name, user.last_name)}

{user.first_name} {user.last_name}

{user.email}

Status {user.status}
Role

{user.role_module_combinations?.[0]?.role_name || "Platform User"}

Joined Date

{formatDate(user.created_at)}

Last Updated

{formatDate(user.updated_at)}

) : (
No user data available.
)}
); }; export interface PlatformUsersTableRef { openNewModal: () => void; refresh: () => void; } export const PlatformUsersTable = forwardRef( (_, ref): ReactElement => { const { canCreate, canUpdate } = usePermissions(); const [users, setUsers] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [isCreateOpen, setIsCreateOpen] = useState(false); const [isCreating, setIsCreating] = useState(false); const [isEditOpen, setIsEditOpen] = useState(false); const [isUpdating, setIsUpdating] = useState(false); const [isViewOpen, setIsViewOpen] = useState(false); const [selectedUserId, setSelectedUserId] = useState(null); // Filter states const [search, setSearch] = useState(""); const [debouncedSearch, setDebouncedSearch] = useState(""); const [statusFilter, setStatusFilter] = useState(null); const [roleFilter, setRoleFilter] = useState(null); const [orderBy, setOrderBy] = useState(null); // Pagination states const [currentPage, setCurrentPage] = useState(1); const [limit, setLimit] = useState(10); const [pagination, setPagination] = useState({ page: 1, limit: 10, total: 0, totalPages: 1, hasMore: false, }); const [roles, setRoles] = useState([]); // Expose methods useImperativeHandle(ref, () => ({ openNewModal: () => setIsCreateOpen(true), refresh: () => fetchPlatformUsers(), })); // Debounce search useEffect(() => { const timer = setTimeout(() => { setDebouncedSearch(search); setCurrentPage(1); }, 300); return () => clearTimeout(timer); }, [search]); const fetchPlatformUsers = async () => { try { setIsLoading(true); setError(null); // Call backend with scope="platform" const response = await userService.getAll( currentPage, limit, statusFilter, orderBy, debouncedSearch, roleFilter, "platform", ); if (response.success) { setUsers(response.data); setPagination(response.pagination); } else { setError("Failed to fetch platform users"); } } catch (err: any) { setError( err?.response?.data?.error?.message || "Failed to load platform users", ); } finally { setIsLoading(false); } }; // Fetch users when parameters change useEffect(() => { fetchPlatformUsers(); }, [ currentPage, limit, statusFilter, roleFilter, orderBy, debouncedSearch, ]); // Fetch platform roles useEffect(() => { const fetchPlatformRoles = async () => { try { const response = await roleService.getAll( 1, 100, null, null, "platform", true, ); if (response.success) { setRoles(response.data); } } catch (err: any) { console.error("Failed to load platform roles", err); } }; fetchPlatformRoles(); }, []); const handleCreateUser = async (data: NewPlatformUserFormData) => { try { setIsCreating(true); const payload = { email: data.email, first_name: data.first_name, last_name: data.last_name, status: data.status, auth_provider: "local" as const, tenant_id: "00000000-0000-0000-0000-000000000001", role_module_combinations: [ { role_id: data.role_id, module_id: null }, ], department_id: null, designation_id: null, category: "tenant_user" as const, }; const response = await userService.create(payload); showToast.success( response.message || "Platform user created successfully", ); setIsCreateOpen(false); fetchPlatformUsers(); } catch (err: any) { throw err; } finally { setIsCreating(false); } }; const handleUpdateUser = async ( id: string, data: EditPlatformUserFormData, ) => { try { setIsUpdating(true); const payload = { email: data.email, first_name: data.first_name, last_name: data.last_name, status: data.status, tenant_id: "00000000-0000-0000-0000-000000000001", role_module_combinations: [ { role_id: data.role_id, module_id: null }, ], department_id: null, designation_id: null, }; const response = await userService.update(id, payload); showToast.success( response.message || "Platform user updated successfully", ); setIsEditOpen(false); fetchPlatformUsers(); } catch (err: any) { throw err; } finally { setIsUpdating(false); } }; const columns: Column[] = [ { key: "name", label: "Name", render: (user) => (
{getUserInitials(user.first_name, user.last_name)}
{user.first_name} {user.last_name}
), }, { key: "email", label: "Email", render: (user) => {user.email}, }, { key: "role", label: "Role", render: (user) => ( {user.role_module_combinations?.[0]?.role_name || "Platform User"} ), }, { key: "status", label: "Status", render: (user) => ( {user.status} ), }, { key: "created_at", label: "Joined Date", render: (user) => ( {formatDate(user.created_at)} ), mobileLabel: "Joined", }, { key: "actions", label: "Actions", align: "right", render: (user) => (
{ setSelectedUserId(user.id); setIsViewOpen(true); }} onEdit={ canUpdate("users") ? () => { setSelectedUserId(user.id); setIsEditOpen(true); } : undefined } />
), }, ]; const mobileCardRenderer = (user: User) => (
{getUserInitials(user.first_name, user.last_name)}

{user.first_name} {user.last_name}

{user.email}

{ setSelectedUserId(user.id); setIsViewOpen(true); }} onEdit={ canUpdate("users") ? () => { setSelectedUserId(user.id); setIsEditOpen(true); } : undefined } />
Role {user.role_module_combinations?.[0]?.role_name || "Platform User"}
Status
{user.status}
Joined {formatDate(user.created_at)}
); const handleClearFilters = () => { setSearch(""); setStatusFilter(null); setRoleFilter(null); setOrderBy(null); setCurrentPage(1); }; return ( <>
{ setStatusFilter(value as string | null); setCurrentPage(1); }} placeholder="All Status" /> ({ value: r.id, label: r.name }))} value={roleFilter} onChange={(value) => { setRoleFilter(value as string | null); setCurrentPage(1); }} placeholder="All Roles" /> { setOrderBy(value as string[] | null); setCurrentPage(1); }} placeholder="Default Sort" showIcon icon={} />
{canCreate("users") && ( setIsCreateOpen(true)} > New Platform User )} {(!!search || statusFilter !== null || roleFilter !== null || orderBy !== null) && ( )}
user.id} mobileCardRenderer={mobileCardRenderer} emptyMessage="No platform users found" isLoading={isLoading} error={error} /> {pagination.totalPages > 0 && ( { setCurrentPage(page); }} onLimitChange={(newLimit: number) => { setLimit(newLimit); setCurrentPage(1); }} /> )}
{/* Modal Declarations */} setIsCreateOpen(false)} onSubmit={handleCreateUser} isLoading={isCreating} roles={roles} /> { setIsEditOpen(false); setSelectedUserId(null); }} userId={selectedUserId} onSubmit={handleUpdateUser} isLoading={isUpdating} roles={roles} /> { setIsViewOpen(false); setSelectedUserId(null); }} userId={selectedUserId} /> ); }, ); PlatformUsersTable.displayName = "PlatformUsersTable";