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
✅ Quick Setup (3 Steps):
Click the button above to register
Create your account with ${data.email}
Start managing your operations smoothly!
⏰ Time-Sensitive: This invitation expires in 7 days. Don't miss out!
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} .
⏰ 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}?
{
setPreSelectedHub(hubName);
setInviteData({ ...inviteData, hub: hubName });
setShowInviteMemberDialog(true);
}}
>
Invite Now
),
});
},
});
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}
)}
handleEditMember(member)}
>
Edit
{member.is_active ? (
handleDeactivateMember(member)}
>
) : (
handleActivateMember(member)}
>
)}
);
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
setShowAddHubDialog(true)}
className="hover:bg-slate-50 hover:border-[#0A39DF] hover:text-[#0A39DF] transition-all"
>
Create Hub
createTestInviteMutation.mutate()}
disabled={createTestInviteMutation.isPending || !userTeam?.id}
className="bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white shadow-lg font-bold"
size="lg"
>
{createTestInviteMutation.isPending ? (
<>
Loading...
>
) : (
<>
🎯 Get Started Now →
>
)}
setShowInviteMemberDialog(true)}
>
Invite Member
{/* Filters and View Toggle */}
setSearchTerm(e.target.value)}
className="pl-10 border-slate-300 focus:border-[#0A39DF] transition-all"
/>
All Departments
{uniqueDepartments.map(dept => (
{dept}
))}
setViewMode("grid")}
className={viewMode === "grid" ? "bg-[#0A39DF] hover:bg-[#0A39DF]/90 shadow-sm" : "hover:bg-white"}
>
setViewMode("list")}
className={viewMode === "list" ? "bg-[#0A39DF] hover:bg-[#0A39DF]/90 shadow-sm" : "hover:bg-white"}
>
{/* 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 && (
#
Name
Title
Role
Department
Hub
Hub Address
Actions
{filteredMembers(activeMembers).map((member, index) => {
const memberHub = teamHubs.find(h => h.hub_name === member.hub);
return (
{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'}
handleEditMember(member)}
className="h-8 w-8 hover:bg-blue-50 hover:text-[#0A39DF]"
>
handleDeactivateMember(member)}
className="h-8 w-8 hover:bg-red-50 hover:text-red-600"
>
);
})}
)}
{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 && (
setShowInviteMemberDialog(true)}
>
Invite First Member
)}
)}
{/* Deactivated Members Tab */}
{viewMode === "grid" && filteredMembers(deactivatedMembers).length > 0 && (
{filteredMembers(deactivatedMembers).map(renderMemberCard)}
)}
{viewMode === "list" && filteredMembers(deactivatedMembers).length > 0 && (
#
Name
Title
Role
Department
Hub
Hub Address
Actions
{filteredMembers(deactivatedMembers).map((member, index) => {
const memberHub = teamHubs.find(h => h.hub_name === member.hub);
return (
{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'}
handleEditMember(member)}
className="h-8 w-8 hover:bg-blue-50 hover:text-[#0A39DF]"
>
handleActivateMember(member)}
className="h-8 w-8 hover:bg-green-50 hover:text-green-600"
>
);
})}
)}
{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()}
resendInviteMutation.mutate(invite)}
disabled={resendInviteMutation.isPending}
className="text-[#0A39DF] border-[#0A39DF] hover:bg-[#0A39DF] hover:text-white transition-all flex-shrink-0"
>
{resendInviteMutation.isPending ? (
<>
Sending...
>
) : (
<>
Resend
>
)}
))}
) : (
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.manager_name && (
)}
{
setPreSelectedHub(hub.hub_name);
setInviteData({ ...inviteData, hub: hub.hub_name });
setShowInviteMemberDialog(true);
}}
>
Invite Member
{/* Quick Stats */}
Team Members
{activeMembers.filter(m => m.hub === hub.hub_name).length}
Departments
{hub.departments?.length || 0}
{/* Departments Section */}
Departments
{
setSelectedHubForDept(hub);
setShowAddHubDepartmentDialog(true);
}}
>
Create Department
{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
{
setSelectedHubForDept(hub);
setShowAddHubDepartmentDialog(true);
}}
>
Create First Department
)}
))}
{/* Add New Hub Button */}
setShowAddHubDialog(true)}
size="lg"
className="w-full bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white shadow-lg h-16"
>
Create New Hub
) : (
Hub Name
Address
Manager
Departments
Members
Actions
{teamHubs.map((hub) => (
{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
{
setSelectedHubForDept(hub);
setShowAddHubDepartmentDialog(true);
}}
>
Dept
{
setPreSelectedHub(hub.hub_name);
setInviteData({ ...inviteData, hub: hub.hub_name });
setShowInviteMemberDialog(true);
}}
>
Invite
))}
setShowAddHubDialog(true)}
size="lg"
className="w-full bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white shadow-lg h-12"
>
Create New Hub
)
) : (
No Hubs Yet
Create your first hub location to organize your team by physical locations and departments
setShowAddHubDialog(true)}>
Create First Hub
)}
{/* Favorites Tab */}
{/* Header Section */}
Preferred Staff
Your go-to professionals for high-priority assignments
{userTeam?.favorite_staff_count || 0}
Favorites
setShowAddFavoriteDialog(true)} className="bg-gradient-to-r from-amber-500 to-orange-600 hover:from-amber-600 hover:to-orange-700 text-white shadow-lg">
Add Favorite
{/* 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' })}
removeFromFavorites(fav.staff_id)}
className="flex-1 border-2 border-amber-300 text-amber-700 hover:bg-amber-100 hover:border-amber-400 font-semibold"
>
Remove
))}
) : (
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' })}
removeFromFavorites(fav.staff_id)}
className="border-amber-300 text-amber-700 hover:bg-amber-100"
>
Remove
))}
)
) : (
No Favorites Yet
Build your dream team by marking your most reliable and skilled staff members as favorites
setShowAddFavoriteDialog(true)} size="lg" className="bg-gradient-to-r from-amber-500 to-orange-600 hover:from-amber-600 hover:to-orange-700 text-white shadow-lg">
Add Your First Favorite
)}
{/* Blocked Staff Tab */}
{/* Header Section */}
Blocked Staff
Staff members excluded from future assignments
{userTeam?.blocked_staff_count || 0}
Blocked
setShowAddBlockedDialog(true)} variant="outline" className="border-2 border-red-300 text-red-700 hover:bg-red-50 hover:border-red-400 font-semibold shadow-sm">
Block Staff
{/* 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' })}
removeFromBlocked(blocked.staff_id)}
className="border-2 border-green-300 text-green-700 hover:bg-green-50 hover:border-green-400 font-semibold"
>
Unblock
))}
) : (
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' })}
removeFromBlocked(blocked.staff_id)}
className="border-green-300 text-green-700 hover:bg-green-50"
>
Unblock
))}
)
) : (
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.
setShowInviteMemberDialog(false)}>Cancel
{inviteMemberMutation.isPending ? (
<>
Sending invitation...
>
) : (
"Send invitation"
)}
{/* Department Dialog */}
{editingDepartment ? 'Edit Department' : 'Add New Department'}
Manage the list of departments available for your team members.
Department Name
setNewDepartment(e.target.value)}
placeholder="e.g., Operations, Sales, HR"
className="mt-2"
/>
Current Departments:
{uniqueDepartments.length > 0 ? (
uniqueDepartments.map(dept => (
{dept}
{
setEditingDepartment(dept);
setNewDepartment(dept);
}}
>
handleDeleteDepartment(dept)}
>
))
) : (
No departments added yet. Add your first department above.
)}
{
setShowDepartmentDialog(false);
setEditingDepartment(null);
setNewDepartment("");
}}
>
Cancel
{editingDepartment ? 'Update' : 'Add'} Department
{/* Edit Member Dialog */}
Edit Team Member
{editingMember && (
)}
setShowEditMemberDialog(false)}>Cancel
{updateMemberMutation.isPending ? (
<>
Updating...
>
) : (
<>
Update Member
>
)}
{/* Add Hub Dialog */}
Create New Hub
Add a new location hub for your team members
setShowAddHubDialog(false)} size="lg">Cancel
createHubMutation.mutate(newHub)}
size="lg"
className="bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white shadow-lg"
disabled={!newHub.hub_name || !newHub.manager_name || !newHub.manager_position || createHubMutation.isPending}
>
{createHubMutation.isPending ? (
<>
Creating Hub...
>
) : (
<>
Create Hub
>
)}
{/* Add Hub Department Dialog */}
Add Department
to {selectedHubForDept?.hub_name}
{
setShowAddHubDepartmentDialog(false);
setNewHubDepartment({ department_name: "", cost_center: "" });
}}>Cancel
{
const updatedDepartments = [...(selectedHubForDept.departments || []), newHubDepartment];
await base44.entities.TeamHub.update(selectedHubForDept.id, {
departments: updatedDepartments
});
// Also add department to team's global department list
const teamDepartments = userTeam?.departments || [];
if (!teamDepartments.includes(newHubDepartment.department_name)) {
await base44.entities.Team.update(userTeam.id, {
departments: [...teamDepartments, newHubDepartment.department_name]
});
queryClient.invalidateQueries({ queryKey: ['user-team', user?.id, userRole] });
}
queryClient.invalidateQueries({ queryKey: ['team-hubs-main', userTeam?.id] });
setShowAddHubDepartmentDialog(false);
setNewHubDepartment({ department_name: "", cost_center: "" });
toast({ title: "✅ Department Added", description: `Department added to ${selectedHubForDept.hub_name}` });
}}
className="bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white"
disabled={!newHubDepartment.department_name}
>
Add Department
{/* 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}
))}
setShowAddFavoriteDialog(false)}>Cancel
{/* Add Blocked Staff Dialog */}
Block Staff Member
Reason for blocking *
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}
))}
{
setShowAddBlockedDialog(false);
setBlockReason("");
}}>Cancel
);
}