import React, { useState, useMemo, useCallback } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Search, UserPlus, Filter, MoreVertical, Edit3, Trash2, Mail, Shield, Eye, DollarSign, X, Check, ChevronDown, Crown, AlertTriangle, Users, RefreshCw, Ban, UserCheck, Clock, Briefcase, ArrowUpDown, } from 'lucide-react'; import { toast } from 'sonner'; const ROLE_OPTIONS = [ { key: 'ADMIN', label: 'Admin / Manager' }, { key: 'SALES_REP', label: 'Sales Rep' }, { key: 'CANVASSER', label: 'Canvasser' }, { key: 'SUBCONTRACTOR', label: 'Subcontractor' }, ]; const STATUS_CONFIG = { active: { label: 'Active', bg: 'bg-emerald-50 dark:bg-emerald-500/10', border: 'border-emerald-200 dark:border-emerald-500/20', text: 'text-emerald-600 dark:text-emerald-400' }, invited: { label: 'Invited', bg: 'bg-amber-50 dark:bg-amber-500/10', border: 'border-amber-200 dark:border-amber-500/20', text: 'text-amber-600 dark:text-amber-400' }, suspended: { label: 'Suspended', bg: 'bg-red-50 dark:bg-red-500/10', border: 'border-red-200 dark:border-red-500/20', text: 'text-red-500 dark:text-red-400' }, }; const inputClasses = "w-full bg-zinc-50 dark:bg-[#18181b] border border-zinc-200 dark:border-white/10 rounded-xl px-4 py-2.5 text-sm text-zinc-900 dark:text-white outline-none focus:border-blue-500/50 transition-colors"; // ──────────────────────────────────────────────────── // Invite Member Modal // ──────────────────────────────────────────────────── const InviteMemberModal = ({ isOpen, onClose, onInvite, existingMembers, users }) => { const [inviteMethod, setInviteMethod] = useState('email'); const [email, setEmail] = useState(''); const [name, setName] = useState(''); const [roleKey, setRoleKey] = useState(''); const [userSearch, setUserSearch] = useState(''); const [selectedUser, setSelectedUser] = useState(null); const existingIds = new Set(existingMembers.map(m => m.userId)); const existingEmails = new Set(existingMembers.map(m => m.email.toLowerCase())); const searchResults = useMemo(() => { if (!userSearch.trim() || inviteMethod !== 'username') return []; const term = userSearch.toLowerCase(); return (users || []) .filter(u => (u.type === 'employee' || u.type === 'owner' || u.type === 'contractor' || u.type === 'subcontractor') && !existingIds.has(u.id) && (u.name?.toLowerCase().includes(term) || u.email?.toLowerCase().includes(term)) ) .slice(0, 5); }, [userSearch, users, existingIds, inviteMethod]); const handleSubmit = () => { if (!roleKey) { toast.error('Please select a role'); return; } if (inviteMethod === 'email') { if (!email.trim() || !email.includes('@')) { toast.error('Please enter a valid email address'); return; } if (existingEmails.has(email.toLowerCase())) { toast.error('This email is already a member of the organization'); return; } onInvite({ userId: `usr_${Date.now()}`, name: name.trim() || email.split('@')[0], email: email.trim().toLowerCase(), roleKey, status: 'invited', }); } else { if (!selectedUser) { toast.error('Please select a user'); return; } onInvite({ userId: selectedUser.id, name: selectedUser.name, email: selectedUser.email, roleKey, status: 'invited', }); } setEmail(''); setName(''); setRoleKey(''); setUserSearch(''); setSelectedUser(null); onClose(); toast.success('Invitation sent successfully'); }; if (!isOpen) return null; return ( e.stopPropagation()} > {/* Header */}

Invite Team Member

Add a new member to your organization

{/* Body */}
{/* Invite Method Tabs */}
{[ { key: 'email', label: 'Email Invite', icon: Mail }, { key: 'username', label: 'Search Users', icon: Search }, ].map(tab => ( ))}
{inviteMethod === 'email' ? (
setEmail(e.target.value)} placeholder="colleague@company.com" className={inputClasses} />
setName(e.target.value)} placeholder="John Doe (optional)" className={inputClasses} />
) : (
{ setUserSearch(e.target.value); setSelectedUser(null); }} placeholder="Search by name or email..." className={`${inputClasses} pl-9`} />
{searchResults.length > 0 && !selectedUser && (
{searchResults.map(u => ( ))}
)} {selectedUser && (
{selectedUser.name?.charAt(0)}
{selectedUser.name}
{selectedUser.email}
)} {userSearch.trim() && searchResults.length === 0 && !selectedUser && (

No matching users found

)}
)} {/* Role Selection */}
{/* Footer */}
); }; // ──────────────────────────────────────────────────── // Confirmation Dialog // ──────────────────────────────────────────────────── const ConfirmDialog = ({ isOpen, onClose, onConfirm, title, message, confirmLabel, confirmColor }) => { if (!isOpen) return null; const colorMap = { red: 'bg-red-500 hover:bg-red-600', blue: 'bg-blue-500 hover:bg-blue-600', emerald: 'bg-emerald-500 hover:bg-emerald-600', amber: 'bg-amber-500 hover:bg-amber-600', }; return ( e.stopPropagation()} >

{title}

{message}

); }; // ──────────────────────────────────────────────────── // View Permissions Modal // ──────────────────────────────────────────────────── const ViewPermissionsModal = ({ isOpen, onClose, member, permissions, permissionModules, roles }) => { if (!isOpen || !member) return null; const role = roles.find(r => r.key === member.roleKey); const isOwner = member.roleKey === 'OWNER'; return ( e.stopPropagation()} >
{member.name.charAt(0)}

{member.name}

{role?.name || member.roleKey} — Permissions

{isOwner && (
Org Owner has full access to all modules
)} {Object.entries(permissionModules).map(([moduleKey, mod]) => { const memberPerms = isOwner ? mod.actions : (permissions[moduleKey]?.[member.roleKey] || []); return (
{mod.label}
{mod.actions.map(action => { const has = memberPerms.includes(action); return ( {has && } {action} ); })}
); })}
); }; // ──────────────────────────────────────────────────── // Member Action Menu // ──────────────────────────────────────────────────── const MemberActionMenu = ({ member, onAction, isOpen, onToggle, currentUserRoleKey }) => { const isOwnerMember = member.roleKey === 'OWNER'; const canManage = currentUserRoleKey === 'OWNER' || (currentUserRoleKey === 'ADMIN' && !isOwnerMember && member.roleKey !== 'ADMIN'); const canManageAdmin = currentUserRoleKey === 'OWNER'; const isManageable = isOwnerMember ? false : member.roleKey === 'ADMIN' ? canManageAdmin : canManage; const actions = []; if (isManageable) { actions.push({ key: 'editRole', label: 'Edit Role', icon: Edit3 }); } if (member.status === 'invited' && isManageable) { actions.push({ key: 'resendInvite', label: 'Resend Invite', icon: Mail }); } if (member.status === 'active' && isManageable) { actions.push({ key: 'suspend', label: 'Disable Account', icon: Ban }); } if (member.status === 'suspended' && isManageable) { actions.push({ key: 'reactivate', label: 'Reactivate', icon: UserCheck }); } actions.push({ key: 'viewPermissions', label: 'View Permissions', icon: Eye }); actions.push({ key: 'viewCommission', label: 'Commission Settings', icon: DollarSign }); if (isManageable) { actions.push({ key: 'remove', label: 'Remove Member', icon: Trash2, danger: true }); } return (
{isOpen && ( {actions.map(action => ( ))} )}
); }; // ──────────────────────────────────────────────────── // Edit Role Inline // ──────────────────────────────────────────────────── const EditRolePanel = ({ member, roles, onSave, onCancel }) => { const [newRole, setNewRole] = useState(member.roleKey); const currentRole = roles.find(r => r.key === member.roleKey); const targetRole = roles.find(r => r.key === newRole); const isPromotion = () => { const order = ['SUBCONTRACTOR', 'CANVASSER', 'SALES_REP', 'ADMIN']; return order.indexOf(newRole) > order.indexOf(member.roleKey); }; return (
Current: {currentRole?.name} New: {newRole !== member.roleKey && ( {targetRole?.name} )}
{newRole !== member.roleKey && (
{isPromotion() ? '↑ Promotion' : '↓ Demotion'}: {currentRole?.name} → {targetRole?.name}
)}
); }; // ──────────────────────────────────────────────────── // Main Component // ──────────────────────────────────────────────────── const OrgTeamManagement = ({ members, roles, users, permissions, permissionModules, currentUserRoleKey, onAddMember, onUpdateMember, onRemoveMember, onSuspendMember, onReactivateMember, onResendInvite, }) => { const [searchTerm, setSearchTerm] = useState(''); const [filterRole, setFilterRole] = useState('all'); const [filterStatus, setFilterStatus] = useState('all'); const [sortField, setSortField] = useState('name'); const [sortDir, setSortDir] = useState('asc'); const [showInviteModal, setShowInviteModal] = useState(false); const [openMenuId, setOpenMenuId] = useState(null); const [editingRoleId, setEditingRoleId] = useState(null); const [confirmDialog, setConfirmDialog] = useState(null); const [viewPermsMember, setViewPermsMember] = useState(null); const canInvite = currentUserRoleKey === 'OWNER' || currentUserRoleKey === 'ADMIN'; const filteredMembers = useMemo(() => { let result = [...members]; if (searchTerm.trim()) { const term = searchTerm.toLowerCase(); result = result.filter(m => m.name.toLowerCase().includes(term) || m.email.toLowerCase().includes(term) || m.roleKey.toLowerCase().includes(term) ); } if (filterRole !== 'all') { result = result.filter(m => m.roleKey === filterRole); } if (filterStatus !== 'all') { result = result.filter(m => m.status === filterStatus); } result.sort((a, b) => { let valA, valB; switch (sortField) { case 'name': valA = a.name.toLowerCase(); valB = b.name.toLowerCase(); break; case 'role': valA = a.roleKey; valB = b.roleKey; break; case 'status': valA = a.status; valB = b.status; break; case 'joinedAt': valA = a.joinedAt; valB = b.joinedAt; break; case 'lastActivity': valA = a.lastActivity || ''; valB = b.lastActivity || ''; break; case 'jobs': valA = a.assignedJobsCount || 0; valB = b.assignedJobsCount || 0; break; default: valA = a.name; valB = b.name; } if (valA < valB) return sortDir === 'asc' ? -1 : 1; if (valA > valB) return sortDir === 'asc' ? 1 : -1; return 0; }); return result; }, [members, searchTerm, filterRole, filterStatus, sortField, sortDir]); const toggleSort = useCallback((field) => { if (sortField === field) { setSortDir(prev => prev === 'asc' ? 'desc' : 'asc'); } else { setSortField(field); setSortDir('asc'); } }, [sortField]); const handleAction = useCallback((actionKey, member) => { switch (actionKey) { case 'editRole': setEditingRoleId(member.id); break; case 'resendInvite': onResendInvite(member.id); break; case 'suspend': setConfirmDialog({ title: 'Disable Account', message: `Are you sure you want to suspend ${member.name}? They will lose access to the organization until reactivated.`, confirmLabel: 'Suspend', confirmColor: 'red', onConfirm: () => { onSuspendMember(member.id); toast.success(`${member.name} has been suspended`); }, }); break; case 'reactivate': onReactivateMember(member.id); toast.success(`${member.name} has been reactivated`); break; case 'viewPermissions': setViewPermsMember(member); break; case 'viewCommission': toast.info(`Commission settings for ${member.name} — check the User Overrides section`); break; case 'remove': setConfirmDialog({ title: 'Remove Member', message: `Are you sure you want to remove ${member.name} from the organization? This action cannot be undone.`, confirmLabel: 'Remove', confirmColor: 'red', onConfirm: () => { onRemoveMember(member.id); toast.success(`${member.name} has been removed from the organization`); }, }); break; default: break; } }, [onResendInvite, onSuspendMember, onReactivateMember, onRemoveMember]); const handleRoleSave = useCallback((memberId, newRoleKey) => { const member = members.find(m => m.id === memberId); const newRole = roles.find(r => r.key === newRoleKey); onUpdateMember(memberId, { roleKey: newRoleKey }); setEditingRoleId(null); toast.success(`${member?.name}'s role updated to ${newRole?.name}`); }, [members, roles, onUpdateMember]); const roleCounts = useMemo(() => { const counts = {}; roles.forEach(r => { counts[r.key] = members.filter(m => m.roleKey === r.key).length; }); return counts; }, [members, roles]); const statusCounts = useMemo(() => ({ active: members.filter(m => m.status === 'active').length, invited: members.filter(m => m.status === 'invited').length, suspended: members.filter(m => m.status === 'suspended').length, }), [members]); const SortHeader = ({ field, children, className = '' }) => ( ); return (
{/* Quick Stats */}
{members.length}
Total Members
{statusCounts.active}
Active
{statusCounts.invited}
Invited
{statusCounts.suspended}
Suspended
{/* Toolbar */}
{/* Search */}
setSearchTerm(e.target.value)} className="w-full bg-zinc-50 dark:bg-[#18181b] border border-zinc-200 dark:border-white/10 rounded-xl pl-9 pr-4 py-2 text-sm text-zinc-900 dark:text-white placeholder-zinc-400 dark:placeholder-zinc-600 outline-none focus:border-blue-500/50 transition-colors" />
{/* Role Filter */}
{/* Status Filter */}
{canInvite && ( )}
{/* Desktop Table */}
{filteredMembers.map(member => { const role = roles.find(r => r.key === member.roleKey); const sc = STATUS_CONFIG[member.status] || STATUS_CONFIG.active; const isEditing = editingRoleId === member.id; return ( {/* Member */} {/* Role */} {/* Status */} {/* Joined */} {/* Last Active */} {/* Jobs */} {/* Actions */} {/* Edit Role Inline */} {isEditing && ( )} ); })}
Member Org Role Status Joined Last Active Jobs Actions
{member.name.charAt(0)}
{member.name} {member.roleKey === 'OWNER' && }
{member.email}
{role?.name || member.roleKey} {sc.label}
{member.joinedAt}
{member.lastActivity || '—'}
{member.assignedJobsCount || 0}
setOpenMenuId(openMenuId === member.id ? null : member.id)} />
setEditingRoleId(null)} />
{filteredMembers.length === 0 && (

No members found

Try adjusting your search or filters

)}
{/* Mobile Cards */}
{filteredMembers.map(member => { const role = roles.find(r => r.key === member.roleKey); const sc = STATUS_CONFIG[member.status] || STATUS_CONFIG.active; const isEditing = editingRoleId === member.id; return (
{member.name.charAt(0)}
{member.name} {member.roleKey === 'OWNER' && }
{member.email}
setOpenMenuId(openMenuId === member.id ? null : member.id)} />
{role?.name} {sc.label} {member.assignedJobsCount || 0} jobs {member.joinedAt}
{isEditing && ( setEditingRoleId(null)} /> )}
); })} {filteredMembers.length === 0 && (

No members found

Try adjusting your search or filters

)}
{/* Result Count */}
Showing {filteredMembers.length} of {members.length} members {(filterRole !== 'all' || filterStatus !== 'all' || searchTerm.trim()) && ( )}
{/* Modals */} setShowInviteModal(false)} onInvite={onAddMember} existingMembers={members} users={users} /> setConfirmDialog(null)} onConfirm={confirmDialog?.onConfirm || (() => {})} title={confirmDialog?.title || ''} message={confirmDialog?.message || ''} confirmLabel={confirmDialog?.confirmLabel || 'Confirm'} confirmColor={confirmDialog?.confirmColor || 'red'} /> setViewPermsMember(null)} member={viewPermsMember} permissions={permissions} permissionModules={permissionModules} roles={roles} />
); }; export default OrgTeamManagement;