import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Mail, Phone, MapPin, Search, Edit2, X, Trash2, Sparkles, } from "lucide-react"; import { useState, useMemo, useEffect } from "react"; import { aethexToast } from "@/lib/aethex-toast"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; // API Base URL for fetch requests const API_BASE = import.meta.env.VITE_API_BASE || ""; interface TeamMember { id: string; user_id?: string; email: string; full_name: string; position?: string; department?: string; phone?: string; avatar_url?: string; role: "owner" | "admin" | "founder" | "staff" | "employee"; is_active?: boolean; hired_date?: string; created_at?: string; updated_at?: string; } export default function AdminStaffDirectory() { const [searchQuery, setSearchQuery] = useState(""); const [teamMembers, setTeamMembers] = useState([]); const [loading, setLoading] = useState(true); const [editingMember, setEditingMember] = useState(null); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [formData, setFormData] = useState(null); const [isSaving, setIsSaving] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [isSeeding, setIsSeeding] = useState(false); // Fetch staff members from API useEffect(() => { const fetchMembers = async () => { try { setLoading(true); const response = await fetch(`${API_BASE}/api/staff/members`); if (!response.ok) throw new Error("Failed to fetch staff members"); const data = await response.json(); setTeamMembers(data || []); } catch (error) { console.error("Error fetching staff members:", error); aethexToast.error({ title: "Failed to load staff members", description: error instanceof Error ? error.message : "Unknown error", }); setTeamMembers([]); } finally { setLoading(false); } }; fetchMembers(); }, []); const handleSeedData = async () => { try { setIsSeeding(true); const response = await fetch(`${API_BASE}/api/staff/members/seed`); if (!response.ok) { throw new Error("Failed to seed data: " + response.statusText); } // Read response body as text first to avoid stream issues const text = await response.text(); if (!text) { throw new Error("No response from server"); } const result = JSON.parse(text); setTeamMembers(result.members || []); aethexToast.success({ title: "Sample data created", description: `Added ${result.count} staff members. You can now edit them with your actual data.`, }); } catch (error) { console.error("Error seeding data:", error); aethexToast.error({ title: "Failed to seed data", description: error instanceof Error ? error.message : "Unknown error", }); } finally { setIsSeeding(false); } }; const filteredMembers = useMemo(() => { return teamMembers.filter( (member) => member.full_name.toLowerCase().includes(searchQuery.toLowerCase()) || (member.position && member.position.toLowerCase().includes(searchQuery.toLowerCase())) || (member.department && member.department .toLowerCase() .includes(searchQuery.toLowerCase())) || member.email.toLowerCase().includes(searchQuery.toLowerCase()), ); }, [searchQuery, teamMembers]); const getRoleBadgeColor = (role: TeamMember["role"]) => { const colors: Record = { owner: "bg-purple-100 text-purple-900 dark:bg-purple-900/30 dark:text-purple-200", admin: "bg-blue-100 text-blue-900 dark:bg-blue-900/30 dark:text-blue-200", founder: "bg-pink-100 text-pink-900 dark:bg-pink-900/30 dark:text-pink-200", staff: "bg-green-100 text-green-900 dark:bg-green-900/30 dark:text-green-200", employee: "bg-gray-100 text-gray-900 dark:bg-gray-900/30 dark:text-gray-200", }; return colors[role]; }; const handleEditClick = (member: TeamMember) => { setEditingMember(member); setFormData({ ...member }); setIsEditDialogOpen(true); }; const handleSaveEdit = async () => { if (!formData || !editingMember) return; try { setIsSaving(true); const response = await fetch( `${API_BASE}/api/staff/members-detail?id=${editingMember.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ full_name: formData.full_name, email: formData.email, position: formData.position, department: formData.department, phone: formData.phone, location: formData.location, role: formData.role, }), }, ); const updatedMember = await response.json(); if (!response.ok) { throw new Error( updatedMember.details || updatedMember.error || "Failed to save", ); } setTeamMembers( teamMembers.map((m) => (m.id === updatedMember.id ? updatedMember : m)), ); aethexToast.success({ title: "Member updated", description: `${formData.full_name} has been saved.`, }); setIsEditDialogOpen(false); setEditingMember(null); setFormData(null); } catch (error) { console.error("Error saving staff member:", error); aethexToast.error({ title: "Failed to save", description: error instanceof Error ? error.message : "Unknown error", }); } finally { setIsSaving(false); } }; const handleDelete = async (memberId: string, memberName: string) => { if ( !confirm( `Are you sure you want to delete ${memberName}? This action cannot be undone.`, ) ) { return; } try { setIsDeleting(true); const response = await fetch( `${API_BASE}/api/staff/members-detail?id=${memberId}`, { method: "DELETE", }, ); const result = await response.json(); if (!response.ok) { throw new Error(result.details || result.error || "Failed to delete"); } setTeamMembers(teamMembers.filter((m) => m.id !== memberId)); aethexToast.success({ title: "Member deleted", description: `${memberName} has been removed.`, }); if (isEditDialogOpen && editingMember?.id === memberId) { setIsEditDialogOpen(false); setEditingMember(null); } } catch (error) { console.error("Error deleting staff member:", error); aethexToast.error({ title: "Failed to delete", description: error instanceof Error ? error.message : "Unknown error", }); } finally { setIsDeleting(false); } }; const handleFormChange = (field: keyof TeamMember, value: string) => { if (formData) { setFormData({ ...formData, [field]: value }); } }; if (loading) { return (

Team Directory

Loading staff members...

Loading...

); } return (

Team Directory

{teamMembers.length} staff members ยท Manage team information

setSearchQuery(e.target.value)} className="pl-10" />
{teamMembers.length === 0 && !loading ? (

No staff members yet. Start with sample data to get started.

This creates 8 sample team members that you can edit to match your actual team.

) : filteredMembers.length > 0 ? (
{filteredMembers.map((member) => (
{member.full_name} {member.position || "Position not set"}
{member.role}
{member.department || "Department not set"}
{member.phone && ( )} {member.location && (
{member.location}
)} {member.hired_date && (
Hired: {new Date(member.hired_date).toLocaleDateString()}
)}
))}
) : teamMembers.length > 0 ? (

No team members match your search

) : null} {/* Edit Dialog */} Edit Team Member Update member information and save changes {formData && (
handleFormChange("full_name", e.target.value) } placeholder="Full name" />
handleFormChange("email", e.target.value)} placeholder="email@example.com" />
handleFormChange("position", e.target.value)} placeholder="Job title" />
handleFormChange("department", e.target.value) } placeholder="Department name" />
handleFormChange("phone", e.target.value)} placeholder="Phone number" />
handleFormChange("location", e.target.value)} placeholder="City, State or office location" />
)}
); }