diff --git a/src/data/mockPartnerData.ts b/src/data/mockPartnerData.ts index 23f7fba..5fe30e4 100644 --- a/src/data/mockPartnerData.ts +++ b/src/data/mockPartnerData.ts @@ -87,6 +87,29 @@ export const mockPartners: Partner[] = [ }, tags: ['Corporate', 'High Value'], joinedAt: subDays(new Date(), 5).toISOString(), + verificationStatus: 'Verified', + }, + { + id: 'p5', + name: 'New Age Vendors', + type: 'Vendor', + status: 'Active', + logo: '', + primaryContact: { + name: 'John Doe', + email: 'john@newage.com', + role: 'Owner' + }, + metrics: { + activeDeals: 0, + totalRevenue: 0, + openBalance: 0, + lastActivity: new Date().toISOString(), + eventsCount: 0, + }, + tags: ['New'], + joinedAt: new Date().toISOString(), + verificationStatus: 'Pending', } ]; diff --git a/src/features/partners/PartnerDirectory.tsx b/src/features/partners/PartnerDirectory.tsx index 01d4246..cb69cd9 100644 --- a/src/features/partners/PartnerDirectory.tsx +++ b/src/features/partners/PartnerDirectory.tsx @@ -1,34 +1,147 @@ -import { useState } from 'react'; +import { useState, useMemo } from 'react'; +import { Search, Filter, Plus, Check } from 'lucide-react'; import { AppLayout } from '@/components/layout/AppLayout'; -import { PartnerFilters } from './components/PartnerFilters'; import { PartnerCard } from './components/PartnerCard'; import { mockPartners } from '@/data/mockPartnerData'; +import { AddPartnerSheet } from './components/AddPartnerSheet'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, + DropdownMenuItem +} from "@/components/ui/dropdown-menu"; +import { cn } from '@/lib/utils'; // Assuming cn exists export default function PartnerDirectory() { const [searchQuery, setSearchQuery] = useState(''); + const [statusFilters, setStatusFilters] = useState([]); + const [activeTab, setActiveTab] = useState("all"); - const filteredPartners = mockPartners.filter(partner => - partner.name.toLowerCase().includes(searchQuery.toLowerCase()) || - partner.type.toLowerCase().includes(searchQuery.toLowerCase()) || - partner.primaryContact.name.toLowerCase().includes(searchQuery.toLowerCase()) - ); + const allStatuses = ['Active', 'Invited', 'Suspended']; + + const filteredPartners = useMemo(() => { + let result = mockPartners; + + // 1. Filter by Tab (KYC status) + if (activeTab === 'pending_kyc') { + result = result.filter(p => p.verificationStatus === 'Pending'); + } + + // 2. Filter by Search + if (searchQuery) { + const lowerQuery = searchQuery.toLowerCase(); + result = result.filter(partner => + partner.name.toLowerCase().includes(lowerQuery) || + partner.type.toLowerCase().includes(lowerQuery) || + partner.primaryContact.name.toLowerCase().includes(lowerQuery) + ); + } + + // 3. Filter by Status + if (statusFilters.length > 0) { + result = result.filter(p => statusFilters.includes(p.status)); + } + + return result; + }, [mockPartners, searchQuery, statusFilters, activeTab]); + + const toggleStatusFilter = (status: string) => { + setStatusFilters(current => + current.includes(status) + ? current.filter(s => s !== status) + : [...current, status] + ); + }; return ( -
+

Partner Management

Manage your relationships with venues, promoters, sponsors, and vendors.

-
- console.log('Filter clicked')} - onAdd={() => console.log('Add clicked')} - /> +
+
+ + setSearchQuery(e.target.value)} + /> +
+ +
+ + + + + + Filter by Status + + {allStatuses.map(status => ( + toggleStatusFilter(status)} + > + {status} + + ))} + {statusFilters.length > 0 && ( + <> + + setStatusFilters([])} + className="justify-center text-error font-medium" + > + Clear Filters + + + )} + + + + + + +
+ + + All Partners + + Pending KYC + {mockPartners.filter(p => p.verificationStatus === 'Pending').length > 0 && ( + + {mockPartners.filter(p => p.verificationStatus === 'Pending').length} + + )} + + + + + {/* Render grid... handled below */} + + + {/* Render grid... handled below */} + + + {filteredPartners.length > 0 ? (
{filteredPartners.map(partner => ( diff --git a/src/features/partners/components/AddPartnerSheet.tsx b/src/features/partners/components/AddPartnerSheet.tsx new file mode 100644 index 0000000..424591a --- /dev/null +++ b/src/features/partners/components/AddPartnerSheet.tsx @@ -0,0 +1,221 @@ + +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import * as z from "zod"; +import { Loader2, Upload } from "lucide-react"; + +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { toast } from "sonner"; + +const partnerFormSchema = z.object({ + name: z.string().min(2, "Name must be at least 2 characters"), + type: z.enum(['Venue', 'Promoter', 'Sponsor', 'Vendor', 'Affiliate', 'Other']), + email: z.string().email("Invalid email address"), + phone: z.string().optional(), + website: z.string().url().optional().or(z.literal("")), + address: z.string().optional(), + contactName: z.string().min(2, "Contact name required"), + contactRole: z.string().optional(), +}); + +type FormValues = z.infer; + +interface AddPartnerSheetProps { + children: React.ReactNode; +} + +export function AddPartnerSheet({ children }: AddPartnerSheetProps) { + const [open, setOpen] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + + const form = useForm({ + resolver: zodResolver(partnerFormSchema), + defaultValues: { + name: "", + type: "Venue", + email: "", + phone: "", + website: "", + address: "", + contactName: "", + contactRole: "", + }, + }); + + async function onSubmit(data: FormValues) { + setIsSubmitting(true); + // Simulate API call + await new Promise((resolve) => setTimeout(resolve, 1500)); + + console.log("Partner created:", data); + toast.success("Partner added successfully"); + + setIsSubmitting(false); + setOpen(false); + form.reset(); + } + + return ( + + + {children} + + + + Add New Partner + + Onboard a new partner to the platform. + + + +
+ + + ( + + Company Name + + + + + + )} + /> + + ( + + Partner Type + + + + )} + /> + +
+

Primary Contact

+ + ( + + Contact Name + + + + + + )} + /> + +
+ ( + + Email + + + + + + )} + /> + ( + + Phone + + + + + + )} + /> +
+
+ + ( + + Website + + + + + + )} + /> + +
+ + +
+ + +
+
+ ); +} diff --git a/src/features/partners/components/PartnerCard.tsx b/src/features/partners/components/PartnerCard.tsx index 46e8eb9..18dba95 100644 --- a/src/features/partners/components/PartnerCard.tsx +++ b/src/features/partners/components/PartnerCard.tsx @@ -50,12 +50,23 @@ export function PartnerCard({ partner }: PartnerCardProps) { navigate(`/partners/${partner.id}`)}>View Details Assign to Event - Suspend Partner + {partner.status === 'Suspended' ? ( + Revoke Suspension + ) : ( + Suspend Partner + )}
+ {partner.verificationStatus === 'Pending' && ( +
+ KYC Verification Pending + +
+ )} +

diff --git a/src/types/partner.ts b/src/types/partner.ts index 61643e7..8b6dc3a 100644 --- a/src/types/partner.ts +++ b/src/types/partner.ts @@ -33,6 +33,7 @@ export const PartnerSchema = z.object({ tags: z.array(z.string()).default([]), notes: z.string().optional(), joinedAt: z.string(), + verificationStatus: z.enum(['Pending', 'Verified', 'Rejected']).default('Verified'), }); export type Partner = z.infer;