diff --git a/apps/web/src/features/workforce/directory/AddStaff.tsx b/apps/web/src/features/workforce/directory/AddStaff.tsx index fc9f9e38..e22a1b70 100644 --- a/apps/web/src/features/workforce/directory/AddStaff.tsx +++ b/apps/web/src/features/workforce/directory/AddStaff.tsx @@ -4,17 +4,36 @@ import { Button } from "@/common/components/ui/button"; import { ArrowLeft } from "lucide-react"; import StaffForm from "./components/StaffForm"; import DashboardLayout from "@/features/layouts/DashboardLayout"; -import { workforceService } from "@/services/workforceService"; +import { useCreateStaff } from "@/dataconnect-generated/react"; +import { dataConnect } from "@/features/auth/firebase"; import type { Staff } from "../type"; export default function AddStaff() { const navigate = useNavigate(); const [isSubmitting, setIsSubmitting] = useState(false); + const { mutateAsync: createStaff } = useCreateStaff(dataConnect); const handleSubmit = async (staffData: Omit) => { setIsSubmitting(true); try { - await workforceService.entities.Staff.create(staffData); + await createStaff({ + userId: `user_${Math.random().toString(36).substr(2, 9)}`, // Placeholder UID + fullName: staffData.employee_name, + role: staffData.position, + level: staffData.profile_type, + email: staffData.email, + phone: staffData.contact_number, + photoUrl: staffData.photo, + initial: staffData.initial, + bio: staffData.notes, + skills: staffData.skills, + averageRating: staffData.averageRating, + reliabilityScore: staffData.reliability_score, + onTimeRate: staffData.shift_coverage_percentage, + totalShifts: staffData.total_shifts, + city: staffData.city, + addres: staffData.address, + }); navigate("/staff"); } catch (error) { console.error("Failed to create staff", error); diff --git a/apps/web/src/features/workforce/directory/EditStaff.tsx b/apps/web/src/features/workforce/directory/EditStaff.tsx index 49b34a94..3a7ec507 100644 --- a/apps/web/src/features/workforce/directory/EditStaff.tsx +++ b/apps/web/src/features/workforce/directory/EditStaff.tsx @@ -1,48 +1,75 @@ -import { useState, useEffect } from "react"; +import { useState, useMemo } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { Button } from "@/common/components/ui/button"; -import { ArrowLeft, Loader2 } from "lucide-react"; +import { ArrowLeft, Loader2, Edit, Save, X, User, FileText, Briefcase, Shield } from "lucide-react"; import StaffForm from "./components/StaffForm"; import DashboardLayout from "@/features/layouts/DashboardLayout"; -import { workforceService } from "@/services/workforceService"; +import { useGetStaffById, useUpdateStaff } from "@/dataconnect-generated/react"; +import { dataConnect } from "@/features/auth/firebase"; import type { Staff } from "../type"; +import { Badge } from "@/common/components/ui/badge"; export default function EditStaff() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); - const [staff, setStaff] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [isSubmitting, setIsSubmitting] = useState(false); const [isEditing, setIsEditing] = useState(false); - useEffect(() => { - const fetchStaff = async () => { - if (!id) return; - setIsLoading(true); - try { - const staffList = await workforceService.entities.Staff.list(); - const foundStaff = staffList.find(s => s.id === id); - if (foundStaff) { - setStaff(foundStaff); - } else { - console.error("Staff member not found"); - navigate("/staff"); - } - } catch (error) { - console.error("Failed to fetch staff", error); - } finally { - setIsLoading(false); - } - }; - fetchStaff(); - }, [id, navigate]); + const { data: staffData, isLoading, refetch } = useGetStaffById(dataConnect, { id: id || "" }); + const { mutateAsync: updateStaff } = useUpdateStaff(dataConnect); + const [isSubmitting, setIsSubmitting] = useState(false); + + const staff = useMemo(() => { + if (!staffData?.staff) return null; + const s = staffData.staff; + return { + id: s.id, + employee_name: s.fullName, + initial: s.initial || "", + position: s.role || "", + position_2: "", + profile_type: s.level || "", + employment_type: s.employmentType || "", + manager: s.manager || "", + email: s.email || "", + contact_number: s.phone || "", + phone: s.phone || "", + photo: s.photoUrl || "", + status: "Active", // Default for now + skills: (s.skills as string[]) || [], + averageRating: s.averageRating || 0, + reliability_score: s.reliabilityScore || 0, + shift_coverage_percentage: s.onTimeRate || 0, + total_shifts: s.totalShifts || 0, + department: s.department || "", + city: s.city || "", + address: s.addres || "", // Using misspelled field from schema + notes: s.bio || "", + } as Staff; + }, [staffData]); const handleSubmit = async (staffData: Staff) => { if (!id) return; setIsSubmitting(true); try { - await workforceService.entities.Staff.update(id, staffData); - setStaff(staffData); + await updateStaff({ + id: id, + fullName: staffData.employee_name, + role: staffData.position, + level: staffData.profile_type, + email: staffData.email, + phone: staffData.contact_number, + photoUrl: staffData.photo, + initial: staffData.initial, + bio: staffData.notes, + skills: staffData.skills, + averageRating: staffData.averageRating, + reliabilityScore: staffData.reliability_score, + onTimeRate: staffData.shift_coverage_percentage, + totalShifts: staffData.total_shifts, + city: staffData.city, + addres: staffData.address, + }); + await refetch(); setIsEditing(false); } catch (error) { console.error("Failed to update staff", error); @@ -67,7 +94,7 @@ export default function EditStaff() { return ( {staff && ( -
- {!isEditing && ( -
- +
+ {/* Profile Header */} +
+
+
+ {staff.photo ? ( + {staff.employee_name} + ) : ( + staff.employee_name.charAt(0) + )} +
+
+

{staff.employee_name}

+
+ + {staff.status || 'Active'} + + + {staff.position || 'No Role Assigned'} + {staff.profile_type && ( + <> + + {staff.profile_type} + + )} +
+
- )} - {isEditing && ( -
- - + +
+ {!isEditing ? ( + + ) : ( + <> + + + + )}
- )} +
+ = { - admin: { - id: "admin-001", - email: "admin@krow.com", - role: "admin", - user_role: "admin", - name: "Admin User", - company_name: "Krow Workforce", - }, - vendor: { - id: "vendor-001", - email: "vendor@staffagency.com", - role: "vendor", - user_role: "vendor", - name: "Vendor User", - company_name: "Staff Agency Pro", - }, - client: { - id: "client-001", - email: "client@eventco.com", - role: "client", - user_role: "client", - name: "Client User", - company_name: "Event Co.", - }, - operator: { - id: "operator-001", - email: "operator@krow.com", - role: "operator", - user_role: "operator", - name: "Operator User", - company_name: "Krow Workforce", - }, -}; - -const mockStaff = [ - { - id: "staff-001", - employee_name: "Sarah Johnson", - position: "Event Coordinator", - photo: "https://i.pravatar.cc/150?u=staff-001", - photo_url: "https://i.pravatar.cc/150?u=staff-001", - profile_type: "Senior", - email: "sarah.johnson@example.com", - contact_number: "+1 (555) 123-4567", - status: "Active", - skills: ["Event Management", "Customer Service", "Coordination"], - department: "Events", - hub_location: "New York", - averageRating: 4.8, - reliability_score: 95, - shift_coverage_percentage: 98, - vendor_id: "vendor-001", - vendor_name: "Staff Agency Pro", - created_by: "vendor@staffagency.com", - created_date: new Date().toISOString(), - last_active: new Date(Date.now() - 1000 * 60 * 60 * 1).toISOString(), - employment_type: "Contract", - manager: "John Smith", - cancellation_count: 0, - no_show_count: 0, - total_shifts: 145, - }, - { - id: "staff-002", - employee_name: "Michael Chen", - position: "Logistics Manager", - photo: "https://i.pravatar.cc/150?u=staff-002", - photo_url: "https://i.pravatar.cc/150?u=staff-002", - profile_type: "Intermediate", - email: "michael.chen@example.com", - contact_number: "+1 (555) 234-5678", - status: "Active", - skills: ["Logistics", "Operations", "Planning"], - department: "Operations", - hub_location: "Los Angeles", - averageRating: 4.6, - reliability_score: 88, - shift_coverage_percentage: 85, - vendor_id: "vendor-001", - vendor_name: "Staff Agency Pro", - created_by: "vendor@staffagency.com", - created_date: new Date().toISOString(), - last_active: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3).toISOString(), - employment_type: "Full-time", - manager: "Jane Williams", - cancellation_count: 2, - no_show_count: 1, - total_shifts: 156, - }, - { - id: "staff-003", - employee_name: "Emma Rodriguez", - position: "Customer Service Rep", - photo: "https://i.pravatar.cc/150?u=staff-003", - photo_url: "https://i.pravatar.cc/150?u=staff-003", - profile_type: "Junior", - email: "emma.rodriguez@example.com", - contact_number: "+1 (555) 345-6789", - status: "Pending", - skills: ["Customer Service", "Communication"], - department: "Support", - hub_location: "Chicago", - averageRating: 4.3, - reliability_score: 72, - shift_coverage_percentage: 65, - vendor_id: "vendor-001", - vendor_name: "Staff Agency Pro", - created_by: "vendor@staffagency.com", - created_date: new Date().toISOString(), - last_active: new Date(Date.now() - 1000 * 60 * 60 * 24 * 10).toISOString(), - employment_type: "Part-time", - manager: "Robert Brown", - cancellation_count: 5, - no_show_count: 3, - total_shifts: 89, - }, - { - id: "staff-004", - employee_name: "James Wilson", - position: "Security Officer", - photo: "https://i.pravatar.cc/150?u=staff-004", - photo_url: "https://i.pravatar.cc/150?u=staff-004", - profile_type: "Senior", - email: "james.wilson@example.com", - contact_number: "+1 (555) 456-7890", - status: "Active", - skills: ["Security", "Safety"], - department: "Security", - hub_location: "Miami", - averageRating: 4.9, - reliability_score: 99, - shift_coverage_percentage: 100, - vendor_id: "vendor-001", - vendor_name: "Staff Agency Pro", - created_by: "vendor@staffagency.com", - created_date: new Date().toISOString(), - last_active: new Date(Date.now() - 1000 * 60 * 2).toISOString(), - employment_type: "Full-time", - manager: "Patricia Davis", - cancellation_count: 0, - no_show_count: 0, - total_shifts: 198, - }, - { - id: "staff-005", - employee_name: "Lisa Anderson", - position: "HR Specialist", - photo: "https://i.pravatar.cc/150?u=staff-005", - photo_url: "https://i.pravatar.cc/150?u=staff-005", - profile_type: "Intermediate", - email: "lisa.anderson@example.com", - contact_number: "+1 (555) 567-8901", - status: "Suspended", - skills: ["HR", "Recruitment"], - department: "Human Resources", - hub_location: "New York", - averageRating: 4.5, - reliability_score: 91, - shift_coverage_percentage: 92, - vendor_id: "vendor-001", - vendor_name: "Staff Agency Pro", - created_by: "vendor@staffagency.com", - created_date: new Date().toISOString(), - last_active: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30).toISOString(), - employment_type: "Full-time", - manager: "John Smith", - cancellation_count: 1, - no_show_count: 0, - total_shifts: 167, - }, - { - id: "staff-006", - employee_name: "David Martinez", - position: "Data Analyst", - photo: "https://i.pravatar.cc/150?u=staff-006", - photo_url: "https://i.pravatar.cc/150?u=staff-006", - profile_type: "Senior", - email: "david.martinez@example.com", - contact_number: "+1 (555) 678-9012", - status: "Active", - skills: ["Data Analysis", "Reporting", "SQL"], - department: "Analytics", - hub_location: "San Francisco", - averageRating: 4.7, - reliability_score: 93, - shift_coverage_percentage: 87, - vendor_id: "vendor-001", - vendor_name: "Staff Agency Pro", - created_by: "vendor@staffagency.com", - created_date: new Date().toISOString(), - last_active: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5).toISOString(), - employment_type: "Contract", - manager: "Michael Thompson", - cancellation_count: 1, - no_show_count: 1, - total_shifts: 134, - }, - { - id: "staff-007", - employee_name: "Jessica Lee", - position: "Project Manager", - photo: "https://i.pravatar.cc/150?u=staff-007", - photo_url: "https://i.pravatar.cc/150?u=staff-007", - profile_type: "Senior", - email: "jessica.lee@example.com", - contact_number: "+1 (555) 789-0123", - status: "Active", - skills: ["Project Management", "Agile"], - department: "Projects", - hub_location: "Boston", - averageRating: 4.4, - reliability_score: 85, - shift_coverage_percentage: 79, - vendor_id: "vendor-001", - vendor_name: "Staff Agency Pro", - created_by: "vendor@staffagency.com", - created_date: new Date().toISOString(), - last_active: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString(), - employment_type: "Full-time", - manager: "Sarah Johnson", - cancellation_count: 3, - no_show_count: 1, - total_shifts: 142, - }, - { - id: "staff-008", - employee_name: "Kevin Thompson", - position: "Business Analyst", - photo: "https://i.pravatar.cc/150?u=staff-008", - photo_url: "https://i.pravatar.cc/150?u=staff-008", - profile_type: "Intermediate", - email: "kevin.thompson@example.com", - contact_number: "+1 (555) 890-1234", - status: "Pending", - skills: ["Business Analysis", "Strategy"], - department: "Strategy", - hub_location: "Austin", - averageRating: 4.2, - reliability_score: 68, - shift_coverage_percentage: 72, - vendor_id: "vendor-001", - vendor_name: "Staff Agency Pro", - created_by: "vendor@staffagency.com", - created_date: new Date().toISOString(), - last_active: new Date(Date.now() - 1000 * 60 * 60 * 24 * 14).toISOString(), - employment_type: "Part-time", - manager: "Robert Brown", - cancellation_count: 6, - no_show_count: 2, - total_shifts: 95, - }, - { - id: "staff-009", - employee_name: "Nicole White", - position: "Marketing Manager", - photo: "https://i.pravatar.cc/150?u=staff-009", - photo_url: "https://i.pravatar.cc/150?u=staff-009", - profile_type: "Senior", - email: "nicole.white@example.com", - contact_number: "+1 (555) 901-2345", - status: "Active", - skills: ["Marketing", "Branding"], - department: "Marketing", - hub_location: "Seattle", - averageRating: 4.6, - reliability_score: 89, - shift_coverage_percentage: 86, - vendor_id: "vendor-001", - vendor_name: "Staff Agency Pro", - created_by: "vendor@staffagency.com", - created_date: new Date().toISOString(), - last_active: new Date(Date.now() - 1000 * 60 * 60 * 24 * 4).toISOString(), - employment_type: "Full-time", - manager: "Patricia Davis", - cancellation_count: 2, - no_show_count: 0, - total_shifts: 178, - }, -] as unknown as Staff[]; - -const mockEvents: Event[] = [ - { - id: "event-001", - business_name: "Event Co.", - client_email: "client@eventco.com", - created_by: "client@eventco.com", - assigned_staff: [ - { staff_id: "staff-001" }, - { staff_id: "staff-004" }, - ], - }, - { - id: "event-002", - business_name: "Event Co.", - client_email: "client@eventco.com", - created_by: "client@eventco.com", - assigned_staff: [ - { staff_id: "staff-002" }, - { staff_id: "staff-009" }, - ], - }, -]; - -/** - * Simulates API delay for realistic behavior - */ -const delay = (ms: number = 500) => - new Promise((resolve) => setTimeout(resolve, ms)); - -/** - * Workforce Service - Mock implementation - */ -export const workforceService = { - auth: { - /** - * Get current user (mocked) - * In production, this would verify Firebase auth session - */ - me: async (): Promise => { - await delay(800); - // Return a random user for demonstration - const users = Object.values(mockUsers); - return users[Math.floor(Math.random() * users.length)]; - }, - - /** - * Sign out user (mocked) - */ - logout: async (): Promise => { - await delay(300); - console.log("User logged out (mock)"); - }, - }, - - entities: { - Staff: { - /** - * List all staff members - * @param sortBy - Sort field (e.g., '-created_date' for descending) - */ - list: async (sortBy?: string): Promise => { - await delay(1200); - - const staffList = [...mockStaff]; - - // Simple sorting logic - if (sortBy === "-created_date") { - staffList.sort( - (a, b) => - new Date(b.created_date || 0).getTime() - - new Date(a.created_date || 0).getTime() - ); - } else if (sortBy === "created_date") { - staffList.sort( - (a, b) => - new Date(a.created_date || 0).getTime() - - new Date(b.created_date || 0).getTime() - ); - } - - return staffList; - }, - - /** - * Get single staff member by ID - */ - get: async (id: string): Promise => { - await delay(600); - return mockStaff.find((s) => s.id === id) || null; - }, - - /** - * Create new staff member - */ - create: async (staff: Partial): Promise => { - await delay(1000); - const newStaff: Staff = { - ...staff, - id: `staff-${Date.now()}`, - created_date: new Date().toISOString(), - } as Staff; - mockStaff.push(newStaff); - return newStaff; - }, - - /** - * Update staff member - */ - update: async (id: string, updates: Partial): Promise => { - await delay(800); - const index = mockStaff.findIndex((s) => s.id === id); - if (index === -1) throw new Error("Staff not found"); - mockStaff[index] = { ...mockStaff[index], ...updates }; - return mockStaff[index]; - }, - - /** - * Delete staff member - */ - delete: async (id: string): Promise => { - await delay(600); - const index = mockStaff.findIndex((s) => s.id === id); - if (index === -1) throw new Error("Staff not found"); - mockStaff.splice(index, 1); - }, - }, - - Event: { - /** - * List all events - */ - list: async (): Promise => { - await delay(1000); - return [...mockEvents]; - }, - - /** - * Get single event by ID - */ - get: async (id: string): Promise => { - await delay(600); - return mockEvents.find((e) => e.id === id) || null; - }, - }, - }, -}; - -export default workforceService;