import React, { useState, useEffect } from "react"; import { base44 } from "@/api/base44Client"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Link, useNavigate } from "react-router-dom"; import { createPageUrl } from "@/utils"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Users, Plus, Search, Building2, MapPin, UserCheck, Mail, Edit, Loader2, Trash2, UserX, LayoutGrid, List as ListIcon, RefreshCw, Send, Filter, Star, UserPlus } from "lucide-react"; import PageHeader from "../components/common/PageHeader"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { useToast } from "@/components/ui/use-toast"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; export default function Teams() { const navigate = useNavigate(); const queryClient = useQueryClient(); const { toast } = useToast(); const [searchTerm, setSearchTerm] = useState(""); const [departmentFilter, setDepartmentFilter] = useState("all"); const [viewMode, setViewMode] = useState("grid"); const [activeTab, setActiveTab] = useState("active"); const [showInviteMemberDialog, setShowInviteMemberDialog] = useState(false); const [showEditMemberDialog, setShowEditMemberDialog] = useState(false); const [showDepartmentDialog, setShowDepartmentDialog] = useState(false); const [showDeleteTeamDialog, setShowDeleteTeamDialog] = useState(false); const [editingMember, setEditingMember] = useState(null); const [editingDepartment, setEditingDepartment] = useState(null); const [teamToDelete, setTeamToDelete] = useState(null); const [newDepartment, setNewDepartment] = useState(""); const [favoriteSearch, setFavoriteSearch] = useState(""); const [blockedSearch, setBlockedSearch] = useState(""); const [showAddFavoriteDialog, setShowAddFavoriteDialog] = useState(false); const [showAddBlockedDialog, setShowAddBlockedDialog] = useState(false); const [blockReason, setBlockReason] = useState(""); const [showAddHubDialog, setShowAddHubDialog] = useState(false); const [showAddHubDepartmentDialog, setShowAddHubDepartmentDialog] = useState(false); const [selectedHubForDept, setSelectedHubForDept] = useState(null); const [preSelectedHub, setPreSelectedHub] = useState(null); const [newHubDepartment, setNewHubDepartment] = useState({ department_name: "", cost_center: "" }); const [inviteData, setInviteData] = useState({ email: "", full_name: "", role: "member", hub: "", department: "", }); const [newHub, setNewHub] = useState({ hub_name: "", address: "", manager_name: "", manager_position: "", manager_email: "" }); const [isGoogleMapsLoaded, setIsGoogleMapsLoaded] = useState(false); const addressInputRef = React.useRef(null); const autocompleteRef = React.useRef(null); const { data: user } = useQuery({ queryKey: ['current-user-teams'], queryFn: () => base44.auth.me(), }); const userRole = user?.user_role || user?.role; /** * CRITICAL ISOLATION LAYER: * Each role (Admin, Procurement, Operator, Sector, Client, Vendor, Workforce) * gets their OWN isolated team that ONLY they can see and manage. * * Security Rules: * - Teams are filtered by owner_id === current user's ID * - Vendors CANNOT see Procurement teams * - Procurement CANNOT see Vendor teams * - Operators CANNOT see Client teams * - NO cross-layer visibility is allowed * * This ensures complete data isolation across all organizational layers. */ const { data: userTeam } = useQuery({ queryKey: ['user-team', user?.id, userRole], queryFn: async () => {debugger; if (!user?.id) { console.warn("⚠️ No user ID found - cannot fetch team"); return null; } // SECURITY: Fetch ALL teams and filter by owner_id // This ensures only THIS user's team is returned const allTeams = await base44.entities.Team.list('-created_date'); // Find ONLY teams owned by this specific user let team = allTeams.find(t => t.ownerId === user.id); console.log( team); // ISOLATION VERIFICATION if (team && team.ownerId !== user.id) { console.error("🚨 SECURITY VIOLATION: Team owner mismatch!"); return null; } // Auto-create team if doesn't exist (first time user accesses Teams) if (!team && user.id) { console.log(`✅ Creating new isolated team for ${userRole} user: ${user.email}`); const teamName = user.companyName || `${user.fullName}'s Team` || "My Team"; try { team = await base44.entities.Team.create({ teamName: teamName, ownerId: user.id, // CRITICAL: Links team to THIS user only ownerName: user.fullName || user.email, ownerRole: userRole, // Tracks which layer this team belongs to //email: user.email, //phone: user.phone || "", //total_members: 0, //active_members: 0, //total_hubs: 0, favoriteStaff: 0, blockedStaff: 0, //departments: [], // Initialize with an empty array for departments }); console.log(`✅ Team created successfully for ${userRole}: ${team.id}`); } catch (err) { console.error("🔥 EXCEPTION in Team.list:", err); } } // FINAL VERIFICATION: Ensure team belongs to current user if (team) { console.log(`🔒 Team loaded for ${userRole}: ${team.teamName} (Owner: ${team.ownerId})`); // Double-check ownership if (team.ownerId !== user.id) { console.error("🚨 CRITICAL: Attempted to load team not owned by current user!"); toast({ title: "⛔ Access Denied", description: "You don't have permission to view this team.", variant: "destructive", }); return null; } } return team; }, enabled: !!user?.id, }); /** * CRITICAL ISOLATION LAYER FOR TEAM MEMBERS: * Only fetch members that belong to THIS user's team. * Members from other teams/layers are NEVER visible. */ const { data: teamMembers = [] } = useQuery({ queryKey: ['team-members', userTeam?.id, user?.id], queryFn: async () => { if (!userTeam?.id) { console.log("⚠️ No team ID - returning empty members list"); return []; } // Fetch all members and filter by team_id const allMembers = await base44.entities.TeamMember.list('-created_date'); // SECURITY: Only return members that belong to THIS user's team const filteredMembers = allMembers.filter(m => m.team_id === userTeam.id); console.log(`🔒 Loaded ${filteredMembers.length} members for team ${userTeam.id}`); // VERIFICATION: Ensure all returned members belong to the correct team const invalidMembers = filteredMembers.filter(m => m.team_id !== userTeam.id); if (invalidMembers.length > 0) { console.error("🚨 SECURITY VIOLATION: Found members from other teams!"); return []; } return filteredMembers; }, enabled: !!userTeam?.id && !!user?.id, initialData: [], }); // Fetch pending invitations const { data: pendingInvites = [] } = useQuery({ queryKey: ['team-invites', userTeam?.id], queryFn: async () => { if (!userTeam?.id) return []; const allInvites = await base44.entities.TeamMemberInvite.list('-invited_date'); return allInvites.filter(inv => inv.team_id === userTeam.id && inv.invite_status === 'pending'); }, enabled: !!userTeam?.id, initialData: [], }); const { data: allStaff = [] } = useQuery({ queryKey: ['staff-for-favorites'], queryFn: () => base44.entities.Staff.list(), enabled: !!userTeam?.id, initialData: [], }); const { data: teamHubs = [] } = useQuery({ queryKey: ['team-hubs-main', userTeam?.id], queryFn: async () => { if (!userTeam?.id) return []; const allHubs = await base44.entities.TeamHub.list('-created_date'); return allHubs.filter(h => h.team_id === userTeam.id); }, enabled: !!userTeam?.id, initialData: [], }); // Get unique departments from both team settings and existing team members const teamDepartments = userTeam?.departments || []; const memberDepartments = [...new Set(teamMembers.map(m => m.department).filter(Boolean))]; const uniqueDepartments = [...new Set([...teamDepartments, ...memberDepartments])]; // Separate active and deactivated members const activeMembers = teamMembers.filter(m => m.is_active !== false); const deactivatedMembers = teamMembers.filter(m => m.is_active === false); const createTestInviteMutation = useMutation({ mutationFn: async () => { if (!userTeam?.id) { throw new Error("Team not found. Cannot create test invite."); } if (!user?.email && !user?.full_name) { throw new Error("User identity not found. Cannot create test invite."); } const inviteCode = `TEAM-${Math.floor(10000 + Math.random() * 90000)}`; // Use the first hub if available, or empty string const firstHub = teamHubs.length > 0 ? teamHubs[0].hub_name : ""; const firstDept = uniqueDepartments.length > 0 ? uniqueDepartments[0] : "Operations"; const invite = await base44.entities.TeamMemberInvite.create({ team_id: userTeam.id, team_name: userTeam.team_name || "Team", invite_code: inviteCode, email: "demo@example.com", full_name: "Demo User", role: "member", hub: firstHub, department: firstDept, invited_by: user?.email || user?.full_name, invite_status: "PENDING", invited_date: new Date().toISOString(), expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString() }); return invite; }, onSuccess: (invite) => { navigate(createPageUrl("Onboarding") + `?invite=${invite.invite_code}`); }, onError: (error) => { toast({ title: "❌ Failed to Create Test Invite", description: error.message, variant: "destructive", }); }, }); const inviteMemberMutation = useMutation({ mutationFn: async (data) => { if (!userTeam?.id) { throw new Error("No team found. Please try refreshing the page."); } if (!user?.email && !user?.full_name) { throw new Error("Unable to identify who is sending the invite. Please try logging out and back in."); } // Create hub if it doesn't exist, or update hub with new department const existingHub = teamHubs.find(h => h.hub_name === data.hub); if (data.hub && !existingHub) { // Create new hub with department await base44.entities.TeamHub.create({ team_id: userTeam.id, hub_name: data.hub, address: "", is_active: true, departments: data.department ? [{ department_name: data.department, cost_center: "" }] : [] }); queryClient.invalidateQueries({ queryKey: ['team-hubs-main', userTeam?.id] }); } else if (existingHub && data.department) { // Add department to existing hub if it doesn't exist const hubDepartments = existingHub.departments || []; const departmentExists = hubDepartments.some(d => d.department_name === data.department); if (!departmentExists) { await base44.entities.TeamHub.update(existingHub.id, { departments: [...hubDepartments, { department_name: data.department, cost_center: "" }] }); queryClient.invalidateQueries({ queryKey: ['team-hubs-main', userTeam?.id] }); } } const inviteCode = `TEAM-${Math.floor(10000 + Math.random() * 90000)}`; const invite = await base44.entities.TeamMemberInvite.create({ team_id: userTeam.id, team_name: userTeam.team_name || "Team", invite_code: inviteCode, email: data.email, full_name: data.full_name, role: data.role, hub: data.hub || "", department: data.department || "", invited_by: user?.email || user?.full_name, invite_status: "PENDING", invited_date: new Date().toISOString(), expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString() }); const registerUrl = `${window.location.origin}${createPageUrl('Onboarding')}?invite=${inviteCode}`; await base44.integrations.Core.SendEmail({ from_name: userTeam.team_name || "KROW", to: data.email, subject: `🚀 Welcome to KROW! You've been invited to ${data.hub || userTeam.team_name}`, body: `
🎉

You're Invited!

Join ${data.hub ? `the ${data.hub} hub` : userTeam.team_name || 'our team'}

Hi ${data.full_name || 'there'} 👋

Great news! ${user?.full_name || user?.email} has invited you to join ${data.hub || userTeam.team_name || 'the team'} as a ${data.role}.

🚀 Why KROW?

  • Seamless Operations: Manage your workforce effortlessly
  • Smart Scheduling: AI-powered shift assignments
  • Real-Time Updates: Stay connected with your team
  • Simplified Workflow: Everything you need in one place
🎯 Get Started Now →

✅ Quick Setup (3 Steps):

  1. Click the button above to register
  2. Create your account with ${data.email}
  3. Start managing your operations smoothly!

Time-Sensitive: This invitation expires in 7 days. Don't miss out!

Your invite code: ${inviteCode}

Questions? Reach out to ${user?.email || 'support'}

Powered by KROW - Workforce Control Tower

` }); return { invite }; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['team-members', userTeam?.id] }); queryClient.invalidateQueries({ queryKey: ['team-invites', userTeam?.id] }); setShowInviteMemberDialog(false); setPreSelectedHub(null); setInviteData({ email: "", full_name: "", role: "member", hub: "", department: "", }); toast({ title: "✅ Invitation Sent!", description: `Email invitation sent to ${inviteData.email}`, }); }, onError: (error) => { toast({ title: "❌ Failed to Send Invitation", description: error.message, variant: "destructive", }); }, }); const resendInviteMutation = useMutation({ mutationFn: async (invite) => { const registerUrl = `${window.location.origin}${createPageUrl('Onboarding')}?invite=${invite.invite_code}`; await base44.integrations.Core.SendEmail({ from_name: userTeam.team_name || "Team", to: invite.email, subject: `Reminder: You're invited to join ${userTeam.team_name || 'our team'}!`, body: `

📬 Reminder: Team Invitation

Join ${userTeam.team_name || 'our team'}

Hi ${invite.full_name || 'there'},

This is a reminder that ${invite.invited_by} has invited you to join ${userTeam.team_name || 'our team'} as a ${invite.role}.

Register & Join Team →

⏰ Important: This invitation will expire soon. Please register at your earliest convenience.

Your invite code: ${invite.invite_code}
Questions? Contact ${user?.email || 'the team admin'}

` }); return invite; }, onSuccess: () => { toast({ title: "✅ Invitation Resent!", description: "The invitation has been sent again", }); }, onError: (error) => { toast({ title: "❌ Failed to Resend", description: error.message, variant: "destructive", }); }, }); const updateMemberMutation = useMutation({ mutationFn: ({ id, data }) => base44.entities.TeamMember.update(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['team-members', userTeam?.id] }); setShowEditMemberDialog(false); setEditingMember(null); toast({ title: "✅ Member Updated", description: "Team member updated successfully", }); }, }); const deactivateMemberMutation = useMutation({ mutationFn: ({ id }) => base44.entities.TeamMember.update(id, { is_active: false }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['team-members', userTeam?.id] }); toast({ title: "✅ Member Deactivated", description: "Team member has been deactivated", }); }, }); const activateMemberMutation = useMutation({ mutationFn: ({ id }) => base44.entities.TeamMember.update(id, { is_active: true }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['team-members', userTeam?.id] }); toast({ title: "✅ Member Activated", description: "Team member has been activated", }); }, }); const handleInviteMember = () => { inviteMemberMutation.mutate(inviteData); }; const handleEditMember = (member) => { setEditingMember(member); setShowEditMemberDialog(true); }; const handleUpdateMember = () => { updateMemberMutation.mutate({ id: editingMember.id, data: editingMember }); }; const handleDeactivateMember = (member) => { deactivateMemberMutation.mutate({ id: member.id }); }; const handleActivateMember = (member) => { activateMemberMutation.mutate({ id: member.id }); }; const handleAddDepartment = () => { setEditingDepartment(null); setNewDepartment(""); setShowDepartmentDialog(true); }; const handleSaveDepartment = async () => { if (!newDepartment.trim()) { toast({ title: "⚠️ Invalid Department", description: "Department name cannot be empty", variant: "destructive", }); return; } if (!userTeam?.id) { toast({ title: "⚠️ Error", description: "Team not found. Please refresh the page.", variant: "destructive", }); return; } try { const currentDepartments = userTeam.departments || []; let updatedDepartments; if (editingDepartment) { // Update existing department updatedDepartments = currentDepartments.map(dept => dept === editingDepartment ? newDepartment.trim() : dept ); } else { // Add new department if (currentDepartments.includes(newDepartment.trim())) { toast({ title: "⚠️ Duplicate Department", description: "This department already exists", variant: "destructive", }); return; } updatedDepartments = [...currentDepartments, newDepartment.trim()]; } // Update the team with new departments list await base44.entities.Team.update(userTeam.id, { departments: updatedDepartments }); // Refresh team data queryClient.invalidateQueries({ queryKey: ['user-team', user?.id, userRole] }); toast({ title: "✅ Department Saved", description: `Department "${newDepartment}" has been ${editingDepartment ? 'updated' : 'added'}`, }); setShowDepartmentDialog(false); setEditingDepartment(null); setNewDepartment(""); } catch (error) { toast({ title: "❌ Error", description: "Failed to save department. Please try again.", variant: "destructive", }); } }; const handleDeleteDepartment = async (deptToDelete) => { if (!userTeam?.id) return; try { const currentDepartments = userTeam.departments || []; const updatedDepartments = currentDepartments.filter(dept => dept !== deptToDelete); await base44.entities.Team.update(userTeam.id, { departments: updatedDepartments }); queryClient.invalidateQueries({ queryKey: ['user-team', user?.id, userRole] }); toast({ title: "✅ Department Deleted", description: `Department "${deptToDelete}" has been removed`, }); } catch (error) { toast({ title: "❌ Error", description: "Failed to delete department. Please try again.", variant: "destructive", }); } }; const updateTeamMutation = useMutation({ mutationFn: ({ id, data }) => base44.entities.Team.update(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['user-team', user?.id, userRole] }); toast({ title: "✅ Team Updated", description: "Team updated successfully", }); }, }); const addToFavorites = (staff) => { const favoriteStaff = userTeam.favorite_staff || []; const newFavorite = { staff_id: staff.id, staff_name: staff.employee_name, position: staff.position, added_date: new Date().toISOString() }; updateTeamMutation.mutate({ id: userTeam.id, data: { favorite_staff: [...favoriteStaff, newFavorite], favorite_staff_count: favoriteStaff.length + 1 } }); setShowAddFavoriteDialog(false); }; const removeFromFavorites = (staffId) => { const favoriteStaff = (userTeam.favorite_staff || []).filter(f => f.staff_id !== staffId); updateTeamMutation.mutate({ id: userTeam.id, data: { favorite_staff: favoriteStaff, favorite_staff_count: favoriteStaff.length } }); }; const addToBlocked = (staff) => { const blockedStaff = userTeam.blocked_staff || []; const newBlocked = { staff_id: staff.id, staff_name: staff.employee_name, reason: blockReason, blocked_date: new Date().toISOString() }; updateTeamMutation.mutate({ id: userTeam.id, data: { blocked_staff: [...blockedStaff, newBlocked], blocked_staff_count: blockedStaff.length + 1 } }); setShowAddBlockedDialog(false); setBlockReason(""); }; const removeFromBlocked = (staffId) => { const blockedStaff = (userTeam.blocked_staff || []).filter(b => b.staff_id !== staffId); updateTeamMutation.mutate({ id: userTeam.id, data: { blocked_staff: blockedStaff, blocked_staff_count: blockedStaff.length } }); }; // Load Google Maps script useEffect(() => { if (window.google?.maps?.places) { setIsGoogleMapsLoaded(true); return; } const script = document.createElement('script'); script.src = `https://maps.googleapis.com/maps/api/js?key=AIzaSyBkP7xH4NvR6C6vZ8Y3J7qX2QW8Z9vN3Zc&libraries=places`; script.async = true; script.onload = () => setIsGoogleMapsLoaded(true); document.head.appendChild(script); }, []); // Initialize autocomplete useEffect(() => { if (isGoogleMapsLoaded && addressInputRef.current && showAddHubDialog && !autocompleteRef.current) { autocompleteRef.current = new window.google.maps.places.Autocomplete(addressInputRef.current, { types: ['address'], componentRestrictions: { country: 'us' } }); autocompleteRef.current.addListener('place_changed', () => { const place = autocompleteRef.current.getPlace(); if (place.formatted_address) { setNewHub({ ...newHub, address: place.formatted_address }); } }); } }, [isGoogleMapsLoaded, showAddHubDialog]); const createHubMutation = useMutation({ mutationFn: (hubData) => base44.entities.TeamHub.create({ ...hubData, team_id: userTeam.id, is_active: true }), onSuccess: (createdHub) => { queryClient.invalidateQueries({ queryKey: ['team-hubs-main', userTeam?.id] }); setShowAddHubDialog(false); const hubName = newHub.hub_name; setNewHub({ hub_name: "", address: "", manager_name: "", manager_position: "", manager_email: "" }); autocompleteRef.current = null; // Show success with invite action toast({ title: "✅ Hub Created Successfully!", description: (
Ready to invite members to {hubName}?
), }); }, }); const filteredMembers = (members) => members.filter(member => { const matchesSearch = !searchTerm || member.member_name?.toLowerCase().includes(searchTerm.toLowerCase()) || member.email?.toLowerCase().includes(searchTerm.toLowerCase()) || member.title?.toLowerCase().includes(searchTerm.toLowerCase()); const matchesDepartment = departmentFilter === "all" || member.department === departmentFilter; return matchesSearch && matchesDepartment; }); const getRoleTitle = () => { const titles = { admin: "Admin Team", procurement: "Procurement Team", operator: "Operator Team", sector: "Sector Team", client: "Client Team", vendor: "Vendor Team", workforce: "Workforce Team" }; return titles[userRole] || "Team"; }; const getIsolatedSubtitle = () => { return `${activeMembers.length} active • ${deactivatedMembers.length} deactivated • ${pendingInvites.length} pending invites`; }; const renderMemberCard = (member) => (
{member.member_name?.split(' ').map(n => n[0]).join('') || '?'} {!member.is_active && (
)}

{member.member_name}

{member.role}

{member.title && (

{member.title}

)}
{member.email && (
{member.email}
)}
{member.department && ( {member.department} )} {member.hub && ( {member.hub} )}
{member.is_active ? ( ) : ( )}
); return (
{/* Security Notice Banner */}

Isolated Team: You can only see and manage YOUR team members. {userRole === 'vendor' && " Procurement teams are NOT visible to you."} {userRole === 'procurement' && " Vendor teams are NOT visible to you."} {userRole === 'operator' && " Other layer teams are NOT visible to you."}

{/* Team Members Section */}
Team Management

Manage roles, permissions, and team organization

{/* Filters and View Toggle */}
setSearchTerm(e.target.value)} className="pl-10 border-slate-300 focus:border-[#0A39DF] transition-all" />
{/* Tabs for Active, Deactivated, Invitations, Hubs, Favorites, Blocked */} Active ({activeMembers.length}) Deactivated ({deactivatedMembers.length}) Invitations ({pendingInvites.length}) Hubs ({teamHubs.length}) Favorites ({userTeam?.favorite_staff_count || 0}) Blocked ({userTeam?.blocked_staff_count || 0}) {/* Active Members Tab */} {viewMode === "grid" && filteredMembers(activeMembers).length > 0 && (
{filteredMembers(activeMembers).map(renderMemberCard)}
)} {viewMode === "list" && filteredMembers(activeMembers).length > 0 && (
{filteredMembers(activeMembers).map((member, index) => { const memberHub = teamHubs.find(h => h.hub_name === member.hub); return ( ); })}
# Name Title Role Department Hub Hub Address Actions
{index + 1}
{member.member_name?.split(' ').map(n => n[0]).join('') || '?'}

{member.member_name}

{member.email}

{member.title || '-'} {member.role} {member.department || 'No Department'} {member.hub || 'No Hub'} {memberHub?.address || 'No Address'}
)} {filteredMembers(activeMembers).length === 0 && (

No Active Members

{activeMembers.length === 0 ? 'Invite your first team member to get started' : 'No active members match your filters'}

{activeMembers.length === 0 && ( )}
)}
{/* Deactivated Members Tab */} {viewMode === "grid" && filteredMembers(deactivatedMembers).length > 0 && (
{filteredMembers(deactivatedMembers).map(renderMemberCard)}
)} {viewMode === "list" && filteredMembers(deactivatedMembers).length > 0 && (
{filteredMembers(deactivatedMembers).map((member, index) => { const memberHub = teamHubs.find(h => h.hub_name === member.hub); return ( ); })}
# Name Title Role Department Hub Hub Address Actions
{index + 1}
{member.member_name?.split(' ').map(n => n[0]).join('') || '?'}

{member.member_name}

{member.email}

{member.title || '-'} {member.role} {member.department || 'No Department'} {member.hub || 'No Hub'} {memberHub?.address || 'No Address'}
)} {filteredMembers(deactivatedMembers).length === 0 && (

No Deactivated Members

All your team members are currently active

)}
{/* Pending Invitations Tab */} {pendingInvites.length > 0 ? (
{pendingInvites.map((invite) => (

{invite.full_name}

{invite.email}

{invite.role} Invited {new Date(invite.invited_date).toLocaleDateString()}
))}
) : (

No Pending Invitations

All invitations have been accepted or expired

)}
{/* Hubs Tab */}
{teamHubs.length > 0 ? ( viewMode === "grid" ? (
{teamHubs.map((hub) => (
{/* Hub Header */}

{hub.hub_name}

{hub.address && (

{hub.address}

)} {hub.manager_name && (
{hub.manager_name}
{hub.manager_email && ( {hub.manager_email} )}
)}
{/* Quick Stats */}

Team Members

{activeMembers.filter(m => m.hub === hub.hub_name).length}

Departments

{hub.departments?.length || 0}

{/* Departments Section */}

Departments

{hub.departments && hub.departments.length > 0 ? (
{hub.departments.map((dept, idx) => (
{dept.department_name}
{dept.cost_center && (

Cost Center: {dept.cost_center}

)}
{dept.manager_name && ( {dept.manager_name} )}
{/* Team members in this department */} {activeMembers.filter(m => m.hub === hub.hub_name && m.department === dept.department_name).length > 0 && (

Team ({activeMembers.filter(m => m.hub === hub.hub_name && m.department === dept.department_name).length})

{activeMembers.filter(m => m.hub === hub.hub_name && m.department === dept.department_name).map((member) => (
handleEditMember(member)}>
{member.member_name?.split(' ').map(n => n[0]).join('') || '?'}

{member.member_name}

{member.title || member.role}

))}
)}
))}
) : (

No departments yet

)}
))} {/* Add New Hub Button */}
) : (
Hub Name Address Manager Departments Members Actions {teamHubs.map((hub) => (
{hub.hub_name}
{hub.address || '—'}

{hub.manager_name || '—'}

{hub.manager_email && (

{hub.manager_email}

)}
{hub.departments?.length || 0} depts {activeMembers.filter(m => m.hub === hub.hub_name).length} members
))}
) ) : (

No Hubs Yet

Create your first hub location to organize your team by physical locations and departments

)}
{/* Favorites Tab */}
{/* Header Section */}

Preferred Staff

Your go-to professionals for high-priority assignments

{userTeam?.favorite_staff_count || 0}

Favorites

{/* Search */}
setFavoriteSearch(e.target.value)} className="pl-12 h-12 bg-white border-2 border-amber-200 focus:border-amber-400 text-base" />
{userTeam?.favorite_staff && userTeam.favorite_staff.length > 0 ? ( viewMode === "grid" ? (
{userTeam.favorite_staff.filter(f => !favoriteSearch || f.staff_name?.toLowerCase().includes(favoriteSearch.toLowerCase()) || f.position?.toLowerCase().includes(favoriteSearch.toLowerCase()) ).map((fav) => (
{fav.staff_name?.charAt(0)}

{fav.staff_name}

{fav.position}

Added {new Date(fav.added_date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}

))}
) : ( Staff Member Position Added Date Actions {userTeam.favorite_staff.filter(f => !favoriteSearch || f.staff_name?.toLowerCase().includes(favoriteSearch.toLowerCase()) || f.position?.toLowerCase().includes(favoriteSearch.toLowerCase()) ).map((fav) => (
{fav.staff_name?.charAt(0)}
{fav.staff_name}
{fav.position} {new Date(fav.added_date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
))}
) ) : (

No Favorites Yet

Build your dream team by marking your most reliable and skilled staff members as favorites

)}
{/* Blocked Staff Tab */}
{/* Header Section */}

Blocked Staff

Staff members excluded from future assignments

{userTeam?.blocked_staff_count || 0}

Blocked

{/* Search */}
setBlockedSearch(e.target.value)} className="pl-12 h-12 bg-white border-2 border-red-200 focus:border-red-400 text-base" />
{userTeam?.blocked_staff && userTeam.blocked_staff.length > 0 ? ( viewMode === "grid" ? (
{userTeam.blocked_staff.filter(b => !blockedSearch || b.staff_name?.toLowerCase().includes(blockedSearch.toLowerCase()) ).map((blocked) => (
{blocked.staff_name?.charAt(0)}

{blocked.staff_name}

Block Reason

{blocked.reason || 'No reason provided'}

Blocked on {new Date(blocked.blocked_date).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}
))}
) : ( Staff Member Reason Blocked Date Actions {userTeam.blocked_staff.filter(b => !blockedSearch || b.staff_name?.toLowerCase().includes(blockedSearch.toLowerCase()) ).map((blocked) => (
{blocked.staff_name?.charAt(0)}
{blocked.staff_name}
{blocked.reason || 'No reason provided'} {new Date(blocked.blocked_date).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}
))}
) ) : (

No Blocked Staff

All staff members are currently eligible for assignments

Block staff members who should not be assigned to your events

)}
{/* Invite Member Dialog */} Invite team member

Assign specific roles and permissions to control what each team member can access and manage.

{ const firstName = e.target.value; const lastName = inviteData.full_name.split(' ').slice(1).join(' '); setInviteData({ ...inviteData, full_name: `${firstName} ${lastName}`.trim() }); }} placeholder="" />
{ const firstName = inviteData.full_name.split(' ')[0] || ""; const lastName = e.target.value; setInviteData({ ...inviteData, full_name: `${firstName} ${lastName}`.trim() }); }} placeholder="" />
setInviteData({ ...inviteData, email: e.target.value })} placeholder="" />
setInviteData({ ...inviteData, hub: e.target.value })} placeholder="e.g., BVG300" list="existing-hubs" /> {teamHubs.map((hub) => { const isRecent = new Date() - new Date(hub.created_date) < 24 * 60 * 60 * 1000; return ( ); })}

{preSelectedHub && ✨ Pre-selected from hub creation • } {teamHubs.find(h => h.hub_name === inviteData.hub) ? '✓ Existing hub' : inviteData.hub ? '+ Will create new hub' : 'Type to search or create'}

setInviteData({ ...inviteData, department: e.target.value })} placeholder="e.g., Catering FOH" list="existing-departments" /> {uniqueDepartments.map((dept) => (
{/* Department Dialog */} {editingDepartment ? 'Edit Department' : 'Add New Department'} Manage the list of departments available for your team members.
setNewDepartment(e.target.value)} placeholder="e.g., Operations, Sales, HR" className="mt-2" />

Current Departments:

{uniqueDepartments.length > 0 ? ( uniqueDepartments.map(dept => (
{dept}
)) ) : (

No departments added yet. Add your first department above.

)}
{/* Edit Member Dialog */} Edit Team Member {editingMember && (
setEditingMember({ ...editingMember, member_name: e.target.value })} placeholder="John Doe" />
setEditingMember({ ...editingMember, email: e.target.value })} placeholder="john@example.com" />
setEditingMember({ ...editingMember, phone: e.target.value })} placeholder="+1 (555) 123-4567" />
setEditingMember({ ...editingMember, title: e.target.value })} placeholder="Manager" />
setEditingMember({ ...editingMember, hub: e.target.value })} placeholder="e.g., BVG300" list="existing-hubs-edit" /> {teamHubs.map((hub) => (
)}
{/* Add Hub Dialog */}
Create New Hub Add a new location hub for your team members

Quick Tip: When inviting members, they can type this hub name (e.g., "BVG300") and it will auto-suggest!

setNewHub({ ...newHub, hub_name: e.target.value })} placeholder="e.g., BVG300, Main Office, Downtown Location" className="text-lg h-12 border-2" />
setNewHub({ ...newHub, address: e.target.value })} placeholder="Start typing address... (e.g., 300 Bayview Dr)" className="h-11" />
setNewHub({ ...newHub, manager_name: e.target.value })} placeholder="Manager Name *" className="h-11" required /> setNewHub({ ...newHub, manager_position: e.target.value })} placeholder="Position/Title *" className="h-11" required />
setNewHub({ ...newHub, manager_email: e.target.value })} placeholder="manager@example.com" className="h-11" />
{/* Add Hub Department Dialog */}
Add Department to {selectedHubForDept?.hub_name}
setNewHubDepartment({ ...newHubDepartment, department_name: e.target.value })} placeholder="e.g., Catering FOH, Catering BOH, Operations" className="h-11 text-base" />
setNewHubDepartment({ ...newHubDepartment, cost_center: e.target.value })} placeholder="CC-12345" className="h-11 font-mono" />

Note: Team members can be assigned to this department when invited or during profile setup.

{/* Add Favorite Staff Dialog */} Add Favorite Staff
{allStaff.filter(s => !(userTeam?.favorite_staff || []).some(f => f.staff_id === s.id)).map((staff) => ( addToFavorites(staff)}>
{staff.employee_name?.charAt(0)}

{staff.employee_name}

{staff.position}

))}
{/* Add Blocked Staff Dialog */} Block Staff Member
setBlockReason(e.target.value)} placeholder="Performance issues, policy violation, etc." />
{allStaff.filter(s => !(userTeam?.blocked_staff || []).some(b => b.staff_id === s.id)).map((staff) => ( addToBlocked(staff)}>
{staff.employee_name?.charAt(0)}

{staff.employee_name}

{staff.position}

))}
); }