From d373fbe2691bdd7e849d20fb317063388c2dad49 Mon Sep 17 00:00:00 2001 From: dhinesh-m24 Date: Wed, 4 Feb 2026 14:04:26 +0530 Subject: [PATCH] feat: Implement business client list view and add client --- apps/web/src/common/components/ui/badge.tsx | 2 + .../features/business/clients/AddClient.tsx | 380 ++++++++++++++++++ .../features/business/clients/ClientList.tsx | 280 +++++++++++++ .../features/business/clients/EditClient.tsx | 8 + apps/web/src/routes.tsx | 8 +- 5 files changed, 677 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/features/business/clients/AddClient.tsx create mode 100644 apps/web/src/features/business/clients/ClientList.tsx create mode 100644 apps/web/src/features/business/clients/EditClient.tsx diff --git a/apps/web/src/common/components/ui/badge.tsx b/apps/web/src/common/components/ui/badge.tsx index fd33e2e7..43ee64f9 100644 --- a/apps/web/src/common/components/ui/badge.tsx +++ b/apps/web/src/common/components/ui/badge.tsx @@ -13,6 +13,8 @@ const badgeVariants = cva( "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + success: + "border-transparent bg-emerald-500 text-white hover:bg-emerald-500/80", outline: "text-foreground", }, }, diff --git a/apps/web/src/features/business/clients/AddClient.tsx b/apps/web/src/features/business/clients/AddClient.tsx new file mode 100644 index 00000000..e277f72c --- /dev/null +++ b/apps/web/src/features/business/clients/AddClient.tsx @@ -0,0 +1,380 @@ +import { Button } from "@/common/components/ui/button"; +import { Input } from "@/common/components/ui/input"; +import { Label } from "@/common/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/common/components/ui/select"; +import { Textarea } from "@/common/components/ui/textarea"; +import DashboardLayout from "@/features/layouts/DashboardLayout"; +import { ArrowLeft, Loader2, Save, X, Mail } from "lucide-react"; +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useQueryClient } from "@tanstack/react-query"; +import { useSelector } from "react-redux"; +import type { RootState } from "@/store/store"; +import { + useCreateBusiness, + useCreateTeamHub +} from "@/dataconnect-generated/react"; +import { + BusinessArea, + BusinessSector, + BusinessStatus, + BusinessRateGroup +} from "@/dataconnect-generated"; +import { dataConnect } from "@/features/auth/firebase"; +import { motion, AnimatePresence } from "framer-motion"; + +export default function AddClient() { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const { user } = useSelector((state: RootState) => state.auth); + + const [showSnackbar, setShowSnackbar] = useState(false); + const [snackbarMessage, setSnackbarMessage] = useState(""); + + const [formData, setFormData] = useState({ + businessName: "", + companyLogoUrl: "", + contactName: "", + phone: "", + email: "", + hubBuilding: "", + address: "", + city: "", + area: BusinessArea.BAY_AREA, + sector: BusinessSector.OTHER, + rateGroup: BusinessRateGroup.STANDARD, + status: BusinessStatus.ACTIVE, + notes: "" + }); + + const { mutateAsync: createBusiness, isPending: isCreatingBusiness } = useCreateBusiness(dataConnect); + const { mutateAsync: createHub, isPending: isCreatingHub } = useCreateTeamHub(dataConnect); + + const handleChange = (field: string, value: any) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!user?.uid) return; + + try { + // 1. Create the business record + const businessResult = await createBusiness({ + businessName: formData.businessName, + contactName: formData.contactName, + userId: user.uid, + companyLogoUrl: formData.companyLogoUrl, + phone: formData.phone, + email: formData.email, + hubBuilding: formData.hubBuilding, + address: formData.address, + city: formData.city, + area: formData.area, + sector: formData.sector, + rateGroup: formData.rateGroup, + status: formData.status, + notes: formData.notes + }); + + const businessId = businessResult.business_insert.id; + + // 2. Automatically create the client's first "hub" or location + await createHub({ + teamId: businessId, + hubName: `${formData.businessName} - Main Hub`, + address: formData.address || "Main Office", + city: formData.city, + isActive: true + }); + + // 3. Show snackbar for welcome email + setSnackbarMessage(`Welcome email sent to ${formData.contactName} (${formData.email})`); + setShowSnackbar(true); + + // Invalidate queries and navigate after a delay to show snackbar + queryClient.invalidateQueries({ queryKey: ['businesses'] }); + + setTimeout(() => { + navigate("/clients"); + }, 3000); + + } catch (error) { + console.error("Error creating client partnership:", error); + setSnackbarMessage("Failed to create client partnership. Please try again."); + setShowSnackbar(true); + } + }; + + const isPending = isCreatingBusiness || isCreatingHub; + + return ( + navigate("/clients")} + leadingIcon={} + > + Back to Directory + + } + > +
+
+
+ {/* Business Name & Company Logo */} +
+
+ + handleChange('businessName', e.target.value)} + placeholder="Enter business name" + required + /> +
+ +
+ + handleChange('companyLogoUrl', e.target.value)} + placeholder="https://example.com/logo.png" + /> +

Optional: URL to company logo image

+
+
+ + {/* Primary Contact */} +
+ + handleChange('contactName', e.target.value)} + placeholder="Contact name" + required + /> +
+ + {/* Contact Number & Email */} +
+
+ + handleChange('phone', e.target.value)} + placeholder="(555) 123-4567" + /> +
+ +
+ + handleChange('email', e.target.value)} + placeholder="business@example.com" + required + /> +
+
+ + {/* Hub / Building */} +
+ + handleChange('hubBuilding', e.target.value)} + placeholder="Building name or location" + /> +
+ + {/* Billing Address */} +
+ + handleChange('address', e.target.value)} + placeholder="Street address" + required + /> +
+ + {/* City & Area */} +
+
+ + handleChange('city', e.target.value)} + placeholder="City" + required + /> +
+ +
+ + +
+
+ + {/* Sector & Rate Group */} +
+
+ + +
+ +
+ + +
+
+ + {/* Status */} +
+ + +
+ + {/* Notes */} +
+ +