diff --git a/frontend-web/src/components/ui/use-toast.jsx b/frontend-web/src/components/ui/use-toast.jsx index cc33246c..a200e225 100644 --- a/frontend-web/src/components/ui/use-toast.jsx +++ b/frontend-web/src/components/ui/use-toast.jsx @@ -1,5 +1,5 @@ import React from "react" -import { base44 } from "@/api/base44Client"; +import { krowSDK } from "@/api/krowSDK"; const TOAST_LIMIT = 5 const TOAST_REMOVE_DELAY = 1000000 @@ -95,7 +95,11 @@ function dispatch(action) { // Helper function to create notification in ActivityLog instead of toast async function createNotification(title, description, variant) { try { - const user = await base44.auth.me(); + const user = await krowSDK.auth.me(); + if (!user) { + console.warn("Cannot create notification: user not authenticated."); + return; + } // Determine icon and color based on variant and title let icon_type = "check"; @@ -124,7 +128,7 @@ async function createNotification(title, description, variant) { activity_type = "staff_assigned"; } - await base44.entities.ActivityLog.create({ + const payload = { title: title.replace(/✅|❌|⚠️/g, '').trim(), description: description || "", activity_type: activity_type, @@ -132,7 +136,10 @@ async function createNotification(title, description, variant) { is_read: false, icon_type: icon_type, icon_color: icon_color, - }); + }; + + await krowSDK.entities.ActivityLog.create({ data: payload }); + } catch (error) { console.error("Failed to create notification:", error); } diff --git a/frontend-web/src/pages/AddStaff.jsx b/frontend-web/src/pages/AddStaff.jsx index 807f402b..c1a3bba5 100644 --- a/frontend-web/src/pages/AddStaff.jsx +++ b/frontend-web/src/pages/AddStaff.jsx @@ -1,26 +1,90 @@ -import React, { useState } from "react"; -import { base44 } from "@/api/base44Client"; +import React from "react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useNavigate } from "react-router-dom"; import { createPageUrl } from "@/utils"; +import { krowSDK } from "@/api/krowSDK"; import { Button } from "@/components/ui/button"; +import { useToast } from "@/components/ui/use-toast"; import { ArrowLeft } from "lucide-react"; import StaffForm from "@/components/staff/StaffForm"; export default function AddStaff() { const navigate = useNavigate(); const queryClient = useQueryClient(); + const { toast } = useToast(); const createStaffMutation = useMutation({ - mutationFn: (staffData) => base44.entities.Staff.create(staffData), + mutationFn: (staffPayload) => krowSDK.entities.Staff.create({ data: staffPayload }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['staff'] }); - navigate(createPageUrl("Dashboard")); + toast({ + title: "✅ Staff Member Added", + description: "The new staff member has been successfully created.", + }); + navigate(createPageUrl("StaffDirectory")); + }, + onError: (error) => { + toast({ + title: "❌ Error Creating Staff", + description: error.message || "An unknown error occurred.", + variant: "destructive", + }); }, }); - const handleSubmit = (staffData) => { - createStaffMutation.mutate(staffData); + const handleSubmit = (formData) => { + // 1. Map snake_case from form to camelCase for GraphQL + // 2. Transform enum values to uppercase + // 3. Add required fields not in the form + // 4. Filter out fields not in the mutation + const employmentTypeMap = { + "Full Time": "FULL_TIME", + "Part Time": "PART_TIME", + "On call": "ON_CALL", + "Weekends": "WEEKENDS", + "Specific Days": "SPECIFIC_DAYS", + "Seasonal": "SEASONAL", + "Medical Leave": "MEDICAL_LEAVE", + }; + + const englishLevelMap = { + "Fluent": "FLUENT", + "Intermediate": "INTERMEDIATE", + "Basic": "BASIC", + "None": "NONE", + }; + + const payload = { + // --- Fields from error messages --- + employeeName: formData.employee_name, + employmentType: employmentTypeMap[formData.employment_type], + english: englishLevelMap[formData.english], + backgroundCheckStatus: 'NOT_REQUIRED', // Default as it's missing from form + + // --- Other likely fields (from form) --- + contactNumber: formData.contact_number, + hubLocation: formData.hub_location, + profileType: formData.profile_type, + reliabilityScore: parseInt(formData.reliability_score) || 100, + + // --- Fields from form that might match schema --- + email: formData.email, + position: formData.position, + department: formData.department, + manager: formData.manager, + rate: parseFloat(formData.rate) || 0, + notes: formData.notes, + rating: parseFloat(formData.rating) || 0, + }; + + // Remove any keys with undefined values to keep the payload clean + Object.keys(payload).forEach(key => { + if (payload[key] === undefined || payload[key] === null) { + delete payload[key]; + } + }); + + createStaffMutation.mutate(payload); }; return ( @@ -29,11 +93,11 @@ export default function AddStaff() {

Add New Staff Member

Fill in the details to add a new team member

diff --git a/frontend-web/src/pages/StaffDirectory.jsx b/frontend-web/src/pages/StaffDirectory.jsx index 29fda88f..88ce6f0c 100644 --- a/frontend-web/src/pages/StaffDirectory.jsx +++ b/frontend-web/src/pages/StaffDirectory.jsx @@ -1,14 +1,14 @@ import React, { useState } from "react"; -import { base44 } from "@/api/base44Client"; import { useQuery } from "@tanstack/react-query"; import { Link } from "react-router-dom"; import { createPageUrl } from "@/utils"; +import { krowSDK } from "@/api/krowSDK"; +import { useAuth } from "@/hooks/useAuth"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { UserPlus, Users, LayoutGrid, List as ListIcon, Phone, MapPin, Calendar, Star } from "lucide-react"; import FilterBar from "@/components/staff/FilterBar"; -import StaffCard from "@/components/staff/StaffCard"; import EmployeeCard from "@/components/staff/EmployeeCard"; import PageHeader from "@/components/common/PageHeader"; @@ -18,50 +18,61 @@ export default function StaffDirectory() { const [locationFilter, setLocationFilter] = useState("all"); const [viewMode, setViewMode] = useState("grid"); // "grid" or "list" - const { data: user } = useQuery({ - queryKey: ['current-user'], - queryFn: () => base44.auth.me(), + const { user: authUser } = useAuth(); // Firebase auth user + + const { data: krowUser, isLoading: isLoadingUser } = useQuery({ + queryKey: ['krow-user', authUser?.uid], + queryFn: () => krowSDK.entities.User.get({ id: authUser.uid }), // Changed from .filter() to .get() + enabled: !!authUser?.uid, + select: (response) => response?.data?.user, // Adjusted to get single user object }); - const { data: staff, isLoading } = useQuery({ + const { data: staff, isLoading: isLoadingStaff } = useQuery({ queryKey: ['staff'], - queryFn: () => base44.entities.Staff.list('-created_date'), + queryFn: () => krowSDK.entities.Staff.list(), initialData: [], + select: (response) => { + // The API returns { data: { staffs: [...] } }, so we need to access the nested array. + if (response && response.data && Array.isArray(response.data.staffs)) { + return response.data.staffs; + } + return []; // Return empty array if the structure is not as expected. + }, }); const { data: events } = useQuery({ queryKey: ['events-for-staff-filter'], - queryFn: () => base44.entities.Event.list(), - initialData: [], - enabled: !!user + queryFn: () => krowSDK.entities.Event.list(), + initialData: { data: [] }, + enabled: !!krowUser, + select: (data) => data.data || [], }); const visibleStaff = React.useMemo(() => { - const userRole = user?.user_role || user?.role; - - if (['admin', 'procurement'].includes(userRole)) { + if (!krowUser || !staff) return []; + const userRole = krowUser.user_role || krowUser.role; + + if (['admin', 'procurement', 'operator', 'sector'].includes(userRole)) { return staff; } - - if (['operator', 'sector'].includes(userRole)) { - return staff; - } - + if (userRole === 'vendor') { - return staff.filter(s => - s.vendor_id === user?.id || - s.vendor_name === user?.company_name || - s.created_by === user?.email + return staff.filter(s => + s.vendor_id === krowUser.id || + s.vendor_name === krowUser.company_name || + //s.created_by === krowUser.email + e.created_by === krowUser.id ); } - + if (userRole === 'client') { - const clientEvents = events.filter(e => - e.client_email === user?.email || - e.business_name === user?.company_name || - e.created_by === user?.email + const clientEvents = events.filter(e => + e.client_email === krowUser.email || + e.business_name === krowUser.company_name || + //e.created_by === krowUser.email + e.created_by === krowUser.id ); - + const assignedStaffIds = new Set(); clientEvents.forEach(event => { if (event.assigned_staff) { @@ -72,36 +83,37 @@ export default function StaffDirectory() { }); } }); - + return staff.filter(s => assignedStaffIds.has(s.id)); } - + if (userRole === 'workforce') { return staff; } - + return staff; - }, [staff, user, events]); + }, [staff, krowUser, events]); const uniqueDepartments = [...new Set(visibleStaff.map(s => s.department).filter(Boolean))]; const uniqueLocations = [...new Set(visibleStaff.map(s => s.hub_location).filter(Boolean))]; const filteredStaff = visibleStaff.filter(member => { - const matchesSearch = !searchTerm || + const matchesSearch = !searchTerm || member.employee_name?.toLowerCase().includes(searchTerm.toLowerCase()) || member.position?.toLowerCase().includes(searchTerm.toLowerCase()) || member.manager?.toLowerCase().includes(searchTerm.toLowerCase()); - + const matchesDepartment = departmentFilter === "all" || member.department === departmentFilter; const matchesLocation = locationFilter === "all" || member.hub_location === locationFilter; - + return matchesSearch && matchesDepartment && matchesLocation; }); - const canAddStaff = ['admin', 'procurement', 'operator', 'sector', 'vendor'].includes(user?.user_role || user?.role); + const canAddStaff = krowUser && ['admin', 'procurement', 'operator', 'sector', 'vendor'].includes(krowUser.user_role || krowUser.role); + const isLoading = isLoadingStaff || isLoadingUser; const getPageTitle = () => { - const userRole = user?.user_role || user?.role; + const userRole = krowUser?.user_role || krowUser?.role; if (userRole === 'vendor') return "My Staff Directory"; if (userRole === 'client') return "Event Staff Directory"; if (userRole === 'workforce') return "Team Directory"; @@ -109,14 +121,14 @@ export default function StaffDirectory() { }; const getPageSubtitle = () => { - const userRole = user?.user_role || user?.role; + const userRole = krowUser?.user_role || krowUser?.role; if (userRole === 'vendor') return `${filteredStaff.length} of your staff members`; if (userRole === 'client') return `${filteredStaff.length} staff assigned to your events`; if (userRole === 'workforce') return `${filteredStaff.length} team members`; return `${filteredStaff.length} ${filteredStaff.length === 1 ? 'member' : 'members'} found`; }; - - const getCoverageColor = (percentage) => { + + const getCoverageColor = (percentage) => { if (!percentage) return "bg-red-100 text-red-700"; if (percentage >= 90) return "bg-green-100 text-green-700"; if (percentage >= 50) return "bg-yellow-100 text-yellow-700"; @@ -126,7 +138,7 @@ export default function StaffDirectory() { return (
-