import React, { useState, useMemo } from "react"; import { base44 } from "@/api/base44Client"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Link, useNavigate } from "react-router-dom"; import { createPageUrl } from "@/utils"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { Textarea } from "@/components/ui/textarea"; import { Search, MapPin, Users, Star, DollarSign, TrendingUp, MessageSquare, CheckCircle, Award, Filter, Grid, List, Phone, Mail, Building2, Zap, ArrowRight, ChevronDown, ChevronUp, UserCheck, Briefcase, Shield, Crown, X, Edit2, Clock, Target } from "lucide-react"; import { useToast } from "@/components/ui/use-toast"; export default function VendorMarketplace() { const navigate = useNavigate(); const { toast } = useToast(); const queryClient = useQueryClient(); const [searchTerm, setSearchTerm] = useState(""); const [regionFilter, setRegionFilter] = useState("all"); const [categoryFilter, setCategoryFilter] = useState("all"); const [sortBy, setSortBy] = useState("rating"); const [viewMode, setViewMode] = useState("grid"); const [contactModal, setContactModal] = useState({ open: false, vendor: null }); const [message, setMessage] = useState(""); const [expandedVendors, setExpandedVendors] = useState({}); const { data: user } = useQuery({ queryKey: ['current-user-marketplace'], queryFn: () => base44.auth.me(), }); const { data: vendors = [] } = useQuery({ queryKey: ['approved-vendors'], queryFn: async () => { const allVendors = await base44.entities.Vendor.list(); return allVendors.filter(v => v.approval_status === 'approved' && v.is_active); }, }); const { data: vendorRates = [] } = useQuery({ queryKey: ['vendor-rates-marketplace'], queryFn: () => base44.entities.VendorRate.list(), }); const { data: staff = [] } = useQuery({ queryKey: ['vendor-staff-count'], queryFn: () => base44.entities.Staff.list(), }); const { data: events = [] } = useQuery({ queryKey: ['events-vendor-marketplace'], queryFn: () => base44.entities.Event.list(), initialData: [], }); const { data: businesses = [] } = useQuery({ queryKey: ['businesses-vendor-marketplace'], queryFn: () => base44.entities.Business.list(), initialData: [], }); const vendorsWithMetrics = useMemo(() => { return vendors.map(vendor => { const rates = vendorRates.filter(r => r.vendor_name === vendor.legal_name || r.vendor_id === vendor.id); const vendorStaff = staff.filter(s => s.vendor_name === vendor.legal_name); const avgRate = rates.length > 0 ? rates.reduce((sum, r) => sum + (r.client_rate || 0), 0) / rates.length : 0; const minRate = rates.length > 0 ? Math.min(...rates.map(r => r.client_rate || 999)) : 0; const rating = 4.5 + (Math.random() * 0.5); const completedJobs = Math.floor(Math.random() * 200) + 50; const vendorEvents = events.filter(e => e.vendor_name === vendor.legal_name || e.vendor_id === vendor.id ); const uniqueClients = new Set( vendorEvents.map(e => e.business_name || e.client_email) ).size; const userSector = user?.sector || user?.company_name; const sectorClients = businesses.filter(b => b.sector === userSector || b.area === user?.area ); const clientsInSector = new Set( vendorEvents .filter(e => sectorClients.some(sc => sc.business_name === e.business_name || sc.contact_name === e.client_name )) .map(e => e.business_name || e.client_email) ).size; const ratesByCategory = rates.reduce((acc, rate) => { const category = rate.category || 'Other'; if (!acc[category]) { acc[category] = []; } acc[category].push(rate); return acc; }, {}); return { ...vendor, rates, ratesByCategory, avgRate, minRate, rating, completedJobs, staffCount: vendorStaff.length, responseTime: `${Math.floor(Math.random() * 3) + 1}h`, totalClients: uniqueClients, clientsInSector: clientsInSector, }; }); }, [vendors, vendorRates, staff, events, businesses, user]); const filteredVendors = useMemo(() => { let filtered = vendorsWithMetrics; if (searchTerm) { const lower = searchTerm.toLowerCase(); filtered = filtered.filter(v => v.legal_name?.toLowerCase().includes(lower) || v.doing_business_as?.toLowerCase().includes(lower) || v.service_specialty?.toLowerCase().includes(lower) ); } if (regionFilter !== "all") { filtered = filtered.filter(v => v.region === regionFilter); } if (categoryFilter !== "all") { filtered = filtered.filter(v => v.service_specialty === categoryFilter); } filtered.sort((a, b) => { switch (sortBy) { case "rating": return b.rating - a.rating; case "price-low": return a.minRate - b.minRate; case "price-high": return b.avgRate - a.avgRate; case "staff": return b.staffCount - a.staffCount; default: return 0; } }); return filtered; }, [vendorsWithMetrics, searchTerm, regionFilter, categoryFilter, sortBy]); const preferredVendor = vendorsWithMetrics.find(v => v.id === user?.preferred_vendor_id); const otherVendors = filteredVendors.filter(v => v.id !== user?.preferred_vendor_id); const uniqueRegions = [...new Set(vendors.map(v => v.region).filter(Boolean))]; const uniqueCategories = [...new Set(vendors.map(v => v.service_specialty).filter(Boolean))]; const setPreferredMutation = useMutation({ mutationFn: (vendor) => base44.auth.updateMe({ preferred_vendor_id: vendor.id, preferred_vendor_name: vendor.legal_name || vendor.doing_business_as }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['current-user'] }); queryClient.invalidateQueries({ queryKey: ['current-user-marketplace'] }); toast({ title: "✅ Preferred Vendor Set", description: "All new orders will route to this vendor by default", }); }, }); const removePreferredMutation = useMutation({ mutationFn: () => base44.auth.updateMe({ preferred_vendor_id: null, preferred_vendor_name: null }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['current-user'] }); toast({ title: "✅ Preferred Vendor Removed", description: "You can now select a new preferred vendor", }); }, }); const handleContactVendor = (vendor) => { setContactModal({ open: true, vendor }); setMessage(`Hi ${vendor.legal_name},\n\nI'm interested in your services for an upcoming event. Could you please provide more information about your availability and pricing?\n\nBest regards,\n${user?.full_name || 'Client'}`); }; const handleSendMessage = async () => { if (!message.trim()) { toast({ title: "Message required", description: "Please enter a message to send.", variant: "destructive", }); return; } try { await base44.entities.Conversation.create({ participants: [ { id: user?.id, name: user?.full_name, role: "client" }, { id: contactModal.vendor.id, name: contactModal.vendor.legal_name, role: "vendor" } ], conversation_type: "client-vendor", is_group: false, subject: `Inquiry from ${user?.full_name || 'Client'}`, last_message: message.substring(0, 100), last_message_at: new Date().toISOString(), status: "active" }); toast({ title: "✅ Message sent!", description: `Your message has been sent to ${contactModal.vendor.legal_name}`, }); setContactModal({ open: false, vendor: null }); setMessage(""); } catch (error) { toast({ title: "Failed to send message", description: error.message, variant: "destructive", }); } }; const handleCreateOrder = (vendor) => { sessionStorage.setItem('selectedVendor', JSON.stringify({ id: vendor.id, name: vendor.legal_name, rates: vendor.rates })); navigate(createPageUrl("CreateEvent")); toast({ title: "Vendor selected", description: `${vendor.legal_name} will be used for this order.`, }); }; const toggleVendorRates = (vendorId) => { setExpandedVendors(prev => ({ ...prev, [vendorId]: !prev[vendorId] })); }; return (
Find the perfect vendor partner for your staffing needs
Your default vendor for all new orders
{preferredVendor.staffCount}
Staff
{preferredVendor.rating.toFixed(1)}
Rating
98%
Fill Rate
{preferredVendor.responseTime}
Response
${Math.round(preferredVendor.minRate)}
From/hr
{preferredVendor.completedJobs}
Jobs
Priority Support
Faster responses
Dedicated Manager
Direct contact
Better Rates
Volume pricing
Choose a default vendor for faster ordering and streamlined operations. You'll get priority support and better rates.
Vendors
{vendors.length}
Approved
Staff
{staff.length}
Available
Avg Rate
${Math.round(vendorsWithMetrics.reduce((sum, v) => sum + v.avgRate, 0) / vendorsWithMetrics.length || 0)}
Per hour
Rating
4.7
Average
DBA: {vendor.doing_business_as}
)}Starting from
${vendor.minRate}
per hour
in your area
You Pay
${rate.client_rate?.toFixed(0)}
per hour
| Vendor | Specialty | Location | Rating | Clients | Staff | Min Rate | Actions |
|---|---|---|---|---|---|---|---|
|
{vendor.legal_name} {vendor.completedJobs} jobs completed |
{vendor.service_specialty || '—'} |
|
|
{vendor.clientsInSector > 0 ? (
|
|
${vendor.minRate}
/hour
|
|
Try adjusting your filters