From a3e0585672dc97fc35e5d5918b0f411207da3e12 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Tue, 11 Nov 2025 02:03:25 +0000 Subject: [PATCH] Update AdminStaffDirectory to fetch real data from API and persist edits cgen-6dd6a418cc15482da46858d7a22ca5b5 --- .../components/admin/AdminStaffDirectory.tsx | 396 ++++++++++++------ 1 file changed, 258 insertions(+), 138 deletions(-) diff --git a/client/components/admin/AdminStaffDirectory.tsx b/client/components/admin/AdminStaffDirectory.tsx index 600ce7f7..f2860552 100644 --- a/client/components/admin/AdminStaffDirectory.tsx +++ b/client/components/admin/AdminStaffDirectory.tsx @@ -2,8 +2,9 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com 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 } from "lucide-react"; -import { useState, useMemo } from "react"; +import { Mail, Phone, MapPin, Search, Edit2, X, Trash2 } from "lucide-react"; +import { useState, useMemo, useEffect } from "react"; +import { aethexToast } from "@/lib/aethex-toast"; import { Dialog, DialogContent, @@ -15,73 +16,68 @@ import { interface TeamMember { id: string; - name: string; - position: string; - department: string; + user_id?: string; email: string; - phone: string; - location: string; + full_name: string; + position?: string; + department?: string; + phone?: string; + avatar_url?: string; role: "owner" | "admin" | "founder" | "staff" | "employee"; - avatar?: string; + 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 teamMembers: TeamMember[] = [ - { - id: "1", - name: "Alex Chen", - position: "CEO & Founder", - department: "Executive", - email: "alex@aethex.dev", - phone: "+1 (555) 123-4567", - location: "San Francisco, CA", - role: "owner", - }, - { - id: "2", - name: "Jordan Martinez", - position: "CTO", - department: "Engineering", - email: "jordan@aethex.dev", - phone: "+1 (555) 234-5678", - location: "Austin, TX", - role: "admin", - }, - { - id: "3", - name: "Sam Patel", - position: "Community Manager", - department: "Community", - email: "sam@aethex.dev", - phone: "+1 (555) 345-6789", - location: "Remote", - role: "staff", - }, - { - id: "4", - name: "Taylor Kim", - position: "Operations Lead", - department: "Operations", - email: "taylor@aethex.dev", - phone: "+1 (555) 456-7890", - location: "New York, NY", - role: "staff", - }, - ]; + // Fetch staff members from API + useEffect(() => { + const fetchMembers = async () => { + try { + setLoading(true); + const response = await fetch("/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 filteredMembers = useMemo(() => { return teamMembers.filter( (member) => - member.name.toLowerCase().includes(searchQuery.toLowerCase()) || - member.position.toLowerCase().includes(searchQuery.toLowerCase()) || - member.department.toLowerCase().includes(searchQuery.toLowerCase()) + 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]); + }, [searchQuery, teamMembers]); const getRoleBadgeColor = (role: TeamMember["role"]) => { const colors: Record = { @@ -100,100 +96,211 @@ export default function AdminStaffDirectory() { setIsEditDialogOpen(true); }; - const handleSaveEdit = () => { - if (!formData) return; - // In a real app, this would POST to /api/staff/members/:id - console.log("Saving member:", formData); - // Update the local team members list - const updatedMembers = teamMembers.map((m) => - m.id === formData.id ? formData : m - ); - setIsEditDialogOpen(false); - setEditingMember(null); - setFormData(null); + const handleSaveEdit = async () => { + if (!formData || !editingMember) return; + + try { + setIsSaving(true); + const response = await fetch( + `/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, + role: formData.role, + }), + } + ); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.details || error.error); + } + + const updatedMember = await response.json(); + 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 handleFormChange = (field: keyof TeamMember, value: string) => { + 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/staff/members-detail?id=${memberId}`, { + method: "DELETE", + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.details || error.error); + } + + 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

- AeThex staff members and contractors + {teamMembers.length} staff members ยท Manage team information

setSearchQuery(e.target.value)} className="pl-10" />
-
- {filteredMembers.map((member) => ( - - -
-
- {member.name} - - {member.position} - + {filteredMembers.length > 0 ? ( +
+ {filteredMembers.map((member) => ( + + +
+
+ + {member.full_name} + + + {member.position || "Position not set"} + +
+
+ + {member.role} + + +
-
- - {member.role} - - + {member.email} +
-
-
- {member.department} -
- - - - -
- - {member.location} -
-
- - ))} -
- - {filteredMembers.length === 0 && ( + {member.phone && ( + + )} + {member.hired_date && ( +
+ Hired: {new Date(member.hired_date).toLocaleDateString()} +
+ )} + + + ))} +
+ ) : (

- No team members match your search + {teamMembers.length === 0 + ? "No staff members yet. Create one to get started." + : "No team members match your search"}

)} @@ -211,10 +318,10 @@ export default function AdminStaffDirectory() { {formData && (
- + handleFormChange("name", e.target.value)} + value={formData.full_name} + onChange={(e) => handleFormChange("full_name", e.target.value)} placeholder="Full name" />
@@ -232,7 +339,7 @@ export default function AdminStaffDirectory() {
handleFormChange("position", e.target.value)} placeholder="Job title" /> @@ -241,7 +348,7 @@ export default function AdminStaffDirectory() {
handleFormChange("department", e.target.value)} placeholder="Department name" /> @@ -250,27 +357,21 @@ export default function AdminStaffDirectory() {
handleFormChange("phone", e.target.value)} placeholder="Phone number" />
-
- - handleFormChange("location", e.target.value)} - placeholder="City, State" - /> -
-