modifications for krowd sdk
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { base44 } from "@/api/base44Client";
|
import { krowSDK } from "@/api/krowSDK";
|
||||||
|
|
||||||
const TOAST_LIMIT = 5
|
const TOAST_LIMIT = 5
|
||||||
const TOAST_REMOVE_DELAY = 1000000
|
const TOAST_REMOVE_DELAY = 1000000
|
||||||
@@ -95,7 +95,11 @@ function dispatch(action) {
|
|||||||
// Helper function to create notification in ActivityLog instead of toast
|
// Helper function to create notification in ActivityLog instead of toast
|
||||||
async function createNotification(title, description, variant) {
|
async function createNotification(title, description, variant) {
|
||||||
try {
|
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
|
// Determine icon and color based on variant and title
|
||||||
let icon_type = "check";
|
let icon_type = "check";
|
||||||
@@ -124,7 +128,7 @@ async function createNotification(title, description, variant) {
|
|||||||
activity_type = "staff_assigned";
|
activity_type = "staff_assigned";
|
||||||
}
|
}
|
||||||
|
|
||||||
await base44.entities.ActivityLog.create({
|
const payload = {
|
||||||
title: title.replace(/✅|❌|⚠️/g, '').trim(),
|
title: title.replace(/✅|❌|⚠️/g, '').trim(),
|
||||||
description: description || "",
|
description: description || "",
|
||||||
activity_type: activity_type,
|
activity_type: activity_type,
|
||||||
@@ -132,7 +136,10 @@ async function createNotification(title, description, variant) {
|
|||||||
is_read: false,
|
is_read: false,
|
||||||
icon_type: icon_type,
|
icon_type: icon_type,
|
||||||
icon_color: icon_color,
|
icon_color: icon_color,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
await krowSDK.entities.ActivityLog.create({ data: payload });
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to create notification:", error);
|
console.error("Failed to create notification:", error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,90 @@
|
|||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { base44 } from "@/api/base44Client";
|
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { createPageUrl } from "@/utils";
|
import { createPageUrl } from "@/utils";
|
||||||
|
import { krowSDK } from "@/api/krowSDK";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
import StaffForm from "@/components/staff/StaffForm";
|
import StaffForm from "@/components/staff/StaffForm";
|
||||||
|
|
||||||
export default function AddStaff() {
|
export default function AddStaff() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
const createStaffMutation = useMutation({
|
const createStaffMutation = useMutation({
|
||||||
mutationFn: (staffData) => base44.entities.Staff.create(staffData),
|
mutationFn: (staffPayload) => krowSDK.entities.Staff.create({ data: staffPayload }),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['staff'] });
|
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) => {
|
const handleSubmit = (formData) => {
|
||||||
createStaffMutation.mutate(staffData);
|
// 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 (
|
return (
|
||||||
@@ -29,11 +93,11 @@ export default function AddStaff() {
|
|||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => navigate(createPageUrl("Dashboard"))}
|
onClick={() => navigate(createPageUrl("StaffDirectory"))}
|
||||||
className="mb-4 hover:bg-slate-100"
|
className="mb-4 hover:bg-slate-100"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
Back to Dashboard
|
Back to Staff Directory
|
||||||
</Button>
|
</Button>
|
||||||
<h1 className="text-3xl md:text-4xl font-bold text-slate-900 mb-2">Add New Staff Member</h1>
|
<h1 className="text-3xl md:text-4xl font-bold text-slate-900 mb-2">Add New Staff Member</h1>
|
||||||
<p className="text-slate-600">Fill in the details to add a new team member</p>
|
<p className="text-slate-600">Fill in the details to add a new team member</p>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { base44 } from "@/api/base44Client";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { createPageUrl } from "@/utils";
|
import { createPageUrl } from "@/utils";
|
||||||
|
import { krowSDK } from "@/api/krowSDK";
|
||||||
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { UserPlus, Users, LayoutGrid, List as ListIcon, Phone, MapPin, Calendar, Star } from "lucide-react";
|
import { UserPlus, Users, LayoutGrid, List as ListIcon, Phone, MapPin, Calendar, Star } from "lucide-react";
|
||||||
import FilterBar from "@/components/staff/FilterBar";
|
import FilterBar from "@/components/staff/FilterBar";
|
||||||
import StaffCard from "@/components/staff/StaffCard";
|
|
||||||
import EmployeeCard from "@/components/staff/EmployeeCard";
|
import EmployeeCard from "@/components/staff/EmployeeCard";
|
||||||
import PageHeader from "@/components/common/PageHeader";
|
import PageHeader from "@/components/common/PageHeader";
|
||||||
|
|
||||||
@@ -18,50 +18,61 @@ export default function StaffDirectory() {
|
|||||||
const [locationFilter, setLocationFilter] = useState("all");
|
const [locationFilter, setLocationFilter] = useState("all");
|
||||||
const [viewMode, setViewMode] = useState("grid"); // "grid" or "list"
|
const [viewMode, setViewMode] = useState("grid"); // "grid" or "list"
|
||||||
|
|
||||||
const { data: user } = useQuery({
|
const { user: authUser } = useAuth(); // Firebase auth user
|
||||||
queryKey: ['current-user'],
|
|
||||||
queryFn: () => base44.auth.me(),
|
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'],
|
queryKey: ['staff'],
|
||||||
queryFn: () => base44.entities.Staff.list('-created_date'),
|
queryFn: () => krowSDK.entities.Staff.list(),
|
||||||
initialData: [],
|
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({
|
const { data: events } = useQuery({
|
||||||
queryKey: ['events-for-staff-filter'],
|
queryKey: ['events-for-staff-filter'],
|
||||||
queryFn: () => base44.entities.Event.list(),
|
queryFn: () => krowSDK.entities.Event.list(),
|
||||||
initialData: [],
|
initialData: { data: [] },
|
||||||
enabled: !!user
|
enabled: !!krowUser,
|
||||||
|
select: (data) => data.data || [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const visibleStaff = React.useMemo(() => {
|
const visibleStaff = React.useMemo(() => {
|
||||||
const userRole = user?.user_role || user?.role;
|
if (!krowUser || !staff) return [];
|
||||||
|
const userRole = krowUser.user_role || krowUser.role;
|
||||||
if (['admin', 'procurement'].includes(userRole)) {
|
|
||||||
|
if (['admin', 'procurement', 'operator', 'sector'].includes(userRole)) {
|
||||||
return staff;
|
return staff;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['operator', 'sector'].includes(userRole)) {
|
|
||||||
return staff;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userRole === 'vendor') {
|
if (userRole === 'vendor') {
|
||||||
return staff.filter(s =>
|
return staff.filter(s =>
|
||||||
s.vendor_id === user?.id ||
|
s.vendor_id === krowUser.id ||
|
||||||
s.vendor_name === user?.company_name ||
|
s.vendor_name === krowUser.company_name ||
|
||||||
s.created_by === user?.email
|
//s.created_by === krowUser.email
|
||||||
|
e.created_by === krowUser.id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userRole === 'client') {
|
if (userRole === 'client') {
|
||||||
const clientEvents = events.filter(e =>
|
const clientEvents = events.filter(e =>
|
||||||
e.client_email === user?.email ||
|
e.client_email === krowUser.email ||
|
||||||
e.business_name === user?.company_name ||
|
e.business_name === krowUser.company_name ||
|
||||||
e.created_by === user?.email
|
//e.created_by === krowUser.email
|
||||||
|
e.created_by === krowUser.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const assignedStaffIds = new Set();
|
const assignedStaffIds = new Set();
|
||||||
clientEvents.forEach(event => {
|
clientEvents.forEach(event => {
|
||||||
if (event.assigned_staff) {
|
if (event.assigned_staff) {
|
||||||
@@ -72,36 +83,37 @@ export default function StaffDirectory() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return staff.filter(s => assignedStaffIds.has(s.id));
|
return staff.filter(s => assignedStaffIds.has(s.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userRole === 'workforce') {
|
if (userRole === 'workforce') {
|
||||||
return staff;
|
return staff;
|
||||||
}
|
}
|
||||||
|
|
||||||
return staff;
|
return staff;
|
||||||
}, [staff, user, events]);
|
}, [staff, krowUser, events]);
|
||||||
|
|
||||||
const uniqueDepartments = [...new Set(visibleStaff.map(s => s.department).filter(Boolean))];
|
const uniqueDepartments = [...new Set(visibleStaff.map(s => s.department).filter(Boolean))];
|
||||||
const uniqueLocations = [...new Set(visibleStaff.map(s => s.hub_location).filter(Boolean))];
|
const uniqueLocations = [...new Set(visibleStaff.map(s => s.hub_location).filter(Boolean))];
|
||||||
|
|
||||||
const filteredStaff = visibleStaff.filter(member => {
|
const filteredStaff = visibleStaff.filter(member => {
|
||||||
const matchesSearch = !searchTerm ||
|
const matchesSearch = !searchTerm ||
|
||||||
member.employee_name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
member.employee_name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
member.position?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
member.position?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
member.manager?.toLowerCase().includes(searchTerm.toLowerCase());
|
member.manager?.toLowerCase().includes(searchTerm.toLowerCase());
|
||||||
|
|
||||||
const matchesDepartment = departmentFilter === "all" || member.department === departmentFilter;
|
const matchesDepartment = departmentFilter === "all" || member.department === departmentFilter;
|
||||||
const matchesLocation = locationFilter === "all" || member.hub_location === locationFilter;
|
const matchesLocation = locationFilter === "all" || member.hub_location === locationFilter;
|
||||||
|
|
||||||
return matchesSearch && matchesDepartment && matchesLocation;
|
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 getPageTitle = () => {
|
||||||
const userRole = user?.user_role || user?.role;
|
const userRole = krowUser?.user_role || krowUser?.role;
|
||||||
if (userRole === 'vendor') return "My Staff Directory";
|
if (userRole === 'vendor') return "My Staff Directory";
|
||||||
if (userRole === 'client') return "Event Staff Directory";
|
if (userRole === 'client') return "Event Staff Directory";
|
||||||
if (userRole === 'workforce') return "Team Directory";
|
if (userRole === 'workforce') return "Team Directory";
|
||||||
@@ -109,14 +121,14 @@ export default function StaffDirectory() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getPageSubtitle = () => {
|
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 === 'vendor') return `${filteredStaff.length} of your staff members`;
|
||||||
if (userRole === 'client') return `${filteredStaff.length} staff assigned to your events`;
|
if (userRole === 'client') return `${filteredStaff.length} staff assigned to your events`;
|
||||||
if (userRole === 'workforce') return `${filteredStaff.length} team members`;
|
if (userRole === 'workforce') return `${filteredStaff.length} team members`;
|
||||||
return `${filteredStaff.length} ${filteredStaff.length === 1 ? 'member' : 'members'} found`;
|
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) return "bg-red-100 text-red-700";
|
||||||
if (percentage >= 90) return "bg-green-100 text-green-700";
|
if (percentage >= 90) return "bg-green-100 text-green-700";
|
||||||
if (percentage >= 50) return "bg-yellow-100 text-yellow-700";
|
if (percentage >= 50) return "bg-yellow-100 text-yellow-700";
|
||||||
@@ -126,7 +138,7 @@ export default function StaffDirectory() {
|
|||||||
return (
|
return (
|
||||||
<div className="p-4 md:p-8">
|
<div className="p-4 md:p-8">
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={getPageTitle()}
|
title={getPageTitle()}
|
||||||
subtitle={getPageSubtitle()}
|
subtitle={getPageSubtitle()}
|
||||||
actions={
|
actions={
|
||||||
|
|||||||
Reference in New Issue
Block a user