import React, { useEffect, useState, useMemo } from "react"; import { motion } from "framer-motion"; import { Search, Filter, MapPin, Sparkles, Download, Linkedin, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Slider } from "@/components/ui/slider"; import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"; import { Checkbox } from "@/components/ui/checkbox"; // --------------------------------------------------------------------------- // Server-side filtering via /api/targets with cursor-based pagination. // Component passes query params + limit/cursor and falls back to FAKE_DATA // if the API isn't wired yet. // --------------------------------------------------------------------------- export type TargetRow = { id: string; companyName: string; businessDescription: string; location: string; employees: string; type: "Founder-owned" | "Family-owned" | "Independent" | "PE-backed"; industry: string; productLine: string[]; revenueM?: number; founded?: number; ceo: { name: string; linkedin?: string }; hasLinkedIn: boolean; }; const FAKE_DATA: TargetRow[] = [ { id: "1", companyName: "AeroFast Components", businessDescription: "Manufacturer and distributor of aerospace-grade fasteners and kitting solutions.", location: "Dallas, TX", employees: "120-180", type: "Founder-owned", industry: "Aerospace & Defense", productLine: ["fasteners", "CNC machining", "kitting"], revenueM: 35, founded: 1996, ceo: { name: "Kelly Rhodes", linkedin: "https://www.linkedin.com/in/example" }, hasLinkedIn: true }, { id: "2", companyName: "Midwest Pack & Fill", businessDescription: "Contract packaging partner specializing in blister packs and retail-ready displays.", location: "Fort Wayne, IN", employees: "80-110", type: "Family-owned", industry: "Packaging & Contract Manufacturing", productLine: ["contract packaging", "blister packs"], revenueM: 22, founded: 2007, ceo: { name: "Jordan Patel", linkedin: "https://www.linkedin.com/in/example2" }, hasLinkedIn: true }, { id: "3", companyName: "Gulf Coast Sealants", businessDescription: "Formulates industrial adhesives and sealants for construction and OEM applications.", location: "Houston, TX", employees: "200-260", type: "Independent", industry: "Chemicals & Materials", productLine: ["adhesives", "sealants"], revenueM: 48, founded: 1988, ceo: { name: "Morgan Ellis" }, hasLinkedIn: false }, { id: "4", companyName: "Pacific Valve Solutions", businessDescription: "Distributor of flow-control valves and maintenance services for industrial plants.", location: "Stockton, CA", employees: "60-90", type: "Founder-owned", industry: "Industrial Distribution", productLine: ["valves", "actuators", "MRO services"], revenueM: 18, founded: 2001, ceo: { name: "Sierra Vaughn", linkedin: "https://www.linkedin.com/in/example3" }, hasLinkedIn: true }, { id: "5", companyName: "Atlantic Thermo Controls", businessDescription: "Designs and assembles temperature-control panels for food & beverage processing.", location: "Raleigh, NC", employees: "45-70", type: "Founder-owned", industry: "Industrial Automation", productLine: ["control panels", "PLC programming"], revenueM: 12, founded: 2015, ceo: { name: "Nate Gardner", linkedin: "https://www.linkedin.com/in/example4" }, hasLinkedIn: true }, { id: "6", companyName: "Rocky Mountain Plastics", businessDescription: "Custom injection molding with secondary assembly for medical and consumer products.", location: "Boise, ID", employees: "90-140", type: "PE-backed", industry: "Plastics Manufacturing", productLine: ["injection molding", "assembly"], revenueM: 28, founded: 1999, ceo: { name: "Taylor Kim", linkedin: "https://www.linkedin.com/in/example5" }, hasLinkedIn: true }, { id: "7", companyName: "Great Lakes Coatings", businessDescription: "Specialty coatings for industrial OEMs; batch and toll manufacturing.", location: "Grand Rapids, MI", employees: "70-100", type: "Family-owned", industry: "Chemicals & Materials", productLine: ["coatings", "toll manufacturing"], revenueM: 20, founded: 1994, ceo: { name: "Renee Alvarez", linkedin: "https://www.linkedin.com/in/example6" }, hasLinkedIn: true }, { id: "8", companyName: "TriState Bearings & Power", businessDescription: "Power transmission distributor with in-house repair and kitting services.", location: "Cincinnati, OH", employees: "110-160", type: "Independent", industry: "Industrial Distribution", productLine: ["bearings", "power transmission", "repair"], revenueM: 32, founded: 1982, ceo: { name: "Will O’Connor" }, hasLinkedIn: false }, { id: "9", companyName: "Sunbelt Labeling Systems", businessDescription: "Industrial labeling and print-&-apply systems; service and integration.", location: "Tampa, FL", employees: "30-55", type: "Founder-owned", industry: "Industrial Technology", productLine: ["labeling", "print & apply", "integration"], revenueM: 8, founded: 2016, ceo: { name: "Avery Thomas", linkedin: "https://www.linkedin.com/in/example7" }, hasLinkedIn: true }, { id: "10", companyName: "Blue Ridge Machining", businessDescription: "Precision CNC machining for aerospace and defense primes; AS9100 certified.", location: "Greenville, SC", employees: "55-85", type: "Founder-owned", industry: "Aerospace & Defense", productLine: ["CNC machining", "5-axis", "AS9100"], revenueM: 14, founded: 2009, ceo: { name: "Chris Dean", linkedin: "https://www.linkedin.com/in/example8" }, hasLinkedIn: true }, ]; async function loadTargetsServer(params: { q?: string; include?: string[]; founderOnly?: boolean; liOnly?: boolean; limit?: number; cursor?: string | null }) { const qs = new URLSearchParams(); if (params.q) qs.set("q", params.q); if (params.include?.length) qs.set("include", params.include.join(",")); if (params.founderOnly) qs.set("founderOnly", "true"); if (params.liOnly) qs.set("liOnly", "true"); if (params.limit) qs.set("limit", String(params.limit)); if (params.cursor) qs.set("cursor", params.cursor); try { const res = await fetch(`/api/targets?${qs.toString()}`, { cache: "no-store" }); if (!res.ok) throw new Error("Bad response"); const json = await res.json(); return json as { items: TargetRow[]; nextCursor?: string | null; total?: number; asOf?: string }; } catch { // fallback: slice/filter local seed to simulate pagination let items = FAKE_DATA; const { q, include, founderOnly, liOnly, limit = 20, cursor } = params; if (q) items = items.filter(r => r.companyName.toLowerCase().includes(q.toLowerCase()) || r.industry.toLowerCase().includes(q.toLowerCase()) || r.productLine.join(" ").toLowerCase().includes(q.toLowerCase())); if (include?.length) items = items.filter(r => include.some(t => r.productLine.includes(t))); if (founderOnly) items = items.filter(r => r.type === "Founder-owned" || r.type === "Family-owned"); if (liOnly) items = items.filter(r => !!r.ceo.linkedin); const offset = cursor ? parseInt(cursor, 10) || 0 : 0; const slice = items.slice(offset, offset + limit); const nextCursor = offset + limit < items.length ? String(offset + limit) : null; return { items: slice, nextCursor, total: items.length, asOf: new Date().toISOString() }; } } // --------------------------------------------------------------------------- // UI — Results Page (fixed-height table with vertical scroll; ~20 rows visible) // --------------------------------------------------------------------------- export default function BoltOnResultsMockServerSide() { const [rows, setRows] = useState ([]); const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [error, setError] = useState (null); // selection const [selectedIds, setSelectedIds] = useState >(new Set()); const selectedCount = selectedIds.size; const allVisibleSelected = useMemo(() => rows.length > 0 && rows.every(r => selectedIds.has(r.id)), [rows, selectedIds]); // pagination state const [nextCursor, setNextCursor] = useState (null); const LIMIT = 20; // filters (sent to server) const [query, setQuery] = useState(""); const [includeProducts, setIncludeProducts] = useState ([]); const [excludeTerms, setExcludeTerms] = useState ([]); // placeholder client-side only for now const [region, setRegion] = useState("United States"); // placeholder for future server filter const [distance, setDistance] = useState ([250]); // placeholder for future server filter const [founderOnly, setFounderOnly] = useState(false); const [liOnly, setLiOnly] = useState(false); const [showFilters, setShowFilters] = useState(true); // initial load & whenever server-side filters change: reset and fetch first page useEffect(() => { let live = true; setLoading(true); setRows([]); setSelectedIds(new Set()); setNextCursor(null); loadTargetsServer({ q: query, include: includeProducts, founderOnly, liOnly, limit: LIMIT, cursor: null }) .then((res) => { if (!live) return; setRows(res.items || []); setNextCursor(res.nextCursor ?? null); setLoading(false); }) .catch(() => { if (!live) return; setError("Failed to load targets"); setLoading(false); }); return () => { live = false; }; }, [query, includeProducts.join(","), founderOnly, liOnly]); async function loadMore() { if (!nextCursor || loadingMore) return; setLoadingMore(true); try { const res = await loadTargetsServer({ q: query, include: includeProducts, founderOnly, liOnly, limit: LIMIT, cursor: nextCursor }); setRows((prev) => [...prev, ...(res.items || [])]); setNextCursor(res.nextCursor ?? null); } catch (e) { setError("Failed to load more"); } finally { setLoadingMore(false); } } const toggleRow = (id: string) => { setSelectedIds((prev) => { const n = new Set(prev); if (n.has(id)) n.delete(id); else n.add(id); return n; }); }; const toggleSelectAllVisible = () => { setSelectedIds((prev) => { const n = new Set(prev); const allSelected = rows.every(r => n.has(r.id)); if (allSelected) { rows.forEach(r => n.delete(r.id)); } else { rows.forEach(r => n.add(r.id)); } return n; }); }; const buildExportRows = () => { const dataset = selectedIds.size ? rows.filter(r => selectedIds.has(r.id)) : rows; return dataset.map((r) => ({ "Company Name": r.companyName, "Business Description": r.businessDescription, "Location": r.location, "Employees": r.employees, "Type": r.type, "Industry": r.industry, "Product Line": r.productLine.join("; "), "Revenue (M)": r.revenueM ?? "", "Founded": r.founded ?? "", "CEO": r.ceo.name, })); }; const exportExcel = async () => { const rowsForExport = buildExportRows(); // If SheetJS (XLSX) is available globally, use it; otherwise fall back to CSV const XLSX = (globalThis as any).XLSX; if (XLSX && XLSX.utils) { const ws = XLSX.utils.json_to_sheet(rowsForExport); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Targets"); const wbout = XLSX.write(wb, { bookType: "xlsx", type: "array" }); const blob = new Blob([wbout], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `bolt_on_targets_${new Date().toISOString().slice(0,10)}.xlsx`; a.click(); URL.revokeObjectURL(url); return; } // CSV fallback (Excel-compatible) const headers = Object.keys(rowsForExport[0] || {}); const csvLines = [headers.join(",")]; for (const row of rowsForExport) { const line = headers.map(h => { const v = (row as any)[h]; const s = v === null || v === undefined ? "" : String(v); return s.includes(",") || s.includes("\n") ? `"${s.replace(/"/g, '""')}"` : s; }).join(","); csvLines.push(line); } const blob = new Blob([csvLines.join("\n")], { type: "text/csv;charset=utf-8;" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `bolt_on_targets_${new Date().toISOString().slice(0,10)}.csv`; a.click(); URL.revokeObjectURL(url); }; const addInclude = (v: string) => v && setIncludeProducts((s) => Array.from(new Set([...s, v]))); const addExclude = (v: string) => v && setExcludeTerms((s) => Array.from(new Set([...s, v]))); const removeInclude = (t: string) => setIncludeProducts((s) => s.filter((x) => x !== t)); const removeExclude = (t: string) => setExcludeTerms((s) => s.filter((x) => x !== t)); return (

Bolt‑On Sourcing

{selectedCount > 0 ? `${selectedCount} selected` : ""}
{showFilters && (
Results — {loading ? "Loading…" : `${rows.length} Companies`}
setQuery(e.target.value)} />
{selectedCount > 0 ? `Export will include ${selectedCount} selected rows` : "No rows selected — export will include all loaded results"}
toggleSelectAllVisible()} aria-label=\"Select all loaded\" />
{selectedCount > 0 ? `Export will include ${selectedCount} selected rows` : "No rows selected — export will include all loaded results"}
{includeProducts.map((p) => ( {p} ))}
{excludeTerms.map((p) => ( {p} ))}
)}
Results — {loading ? "Loading…" : `${rows.length} Companies`} {error &&
{error}
} {/* Fixed-height scrollable container (~20 rows visible). Adjust height as needed. */}
{loading ? ( ) : ( rows.map((r) => ( )) )}
Company Name Business Description Location Employees Type Industry Product Line Revenue (M) Founded CEO
Loading targets…
toggleRow(r.id)} aria-label={`Select ${r.companyName}`} />
{r.companyName} {r.businessDescription} {r.location} {r.employees} {r.type} {r.industry}
{r.productLine.map((p) => ( {p} ))}
{r.revenueM ?? "—"} {r.founded ?? "—"}
{r.ceo.name}
{r.hasLinkedIn ? "LinkedIn available" : "Contact unknown"}
{r.ceo.linkedin && ( )}
{/* Load more button below the scrollable grid */}
{selectedCount > 0 ? `${selectedCount} selected` : ""}
); } // --- Small component to add chips ----------------------------------------- function AddChip({ onAdd, placeholder }: { onAdd: (v: string) => void; placeholder: string }) { const [open, setOpen] = useState(false); const [val, setVal] = useState(""); return (
setVal(e.target.value)} placeholder="Type and press Add" />
); } /* ===========================================================================' Next.js App Router API route stub — drop this in: /app/api/targets/route.ts Adds server-side filtering for q/include/founderOnly/liOnly + cursor pagination. This demo uses a simple index-based cursor; in production, switch to keyset pagination (e.g., base64 encode last {score,id}) for stability at scale. ============================================================================ */ // route.ts // --------- // export const dynamic = 'force-dynamic'; // import { NextResponse } from 'next/server'; // // type TargetRow = { // id: string; companyName: string; businessDescription: string; location: string; employees: string; // type: 'Founder-owned' | 'Family-owned' | 'Independent' | 'PE-backed'; industry: string; productLine: string[]; // revenueM?: number; founded?: number; ceo: { name: string; linkedin?: string }; hasLinkedIn: boolean; // }; // // const DATA: TargetRow[] = [ // { id: '1', companyName: 'AeroFast Components', businessDescription: 'Manufacturer of aerospace fasteners.', location: 'Dallas, TX', employees: '120-180', type: 'Founder-owned', industry: 'Aerospace & Defense', productLine: ['fasteners','kitting'], revenueM: 35, founded: 1996, ceo: { name: 'Kelly Rhodes', linkedin: 'https://www.linkedin.com/in/example' }, hasLinkedIn: true }, // { id: '2', companyName: 'Midwest Pack & Fill', businessDescription: 'Contract packaging partner.', location: 'Fort Wayne, IN', employees: '80-110', type: 'Family-owned', industry: 'Packaging & Contract Manufacturing', productLine: ['contract packaging','blister packs'], revenueM: 22, founded: 2007, ceo: { name: 'Jordan Patel', linkedin: 'https://www.linkedin.com/in/example2' }, hasLinkedIn: true }, // { id: '3', companyName: 'Gulf Coast Sealants', businessDescription: 'Industrial adhesives and sealants.', location: 'Houston, TX', employees: '200-260', type: 'Independent', industry: 'Chemicals & Materials', productLine: ['adhesives','sealants'], revenueM: 48, founded: 1988, ceo: { name: 'Morgan Ellis' }, hasLinkedIn: false }, // ]; // // export async function GET(req: Request) { // const { searchParams } = new URL(req.url); // const q = (searchParams.get('q') || '').toLowerCase(); // const include = (searchParams.get('include') || '').split(',').filter(Boolean); // const founderOnly = searchParams.get('founderOnly') === 'true'; // const liOnly = searchParams.get('liOnly') === 'true'; // const limit = Math.min(parseInt(searchParams.get('limit') || '20', 10) || 20, 100); // const cursor = searchParams.get('cursor'); // // let items = DATA; // if (q) items = items.filter(r => r.companyName.toLowerCase().includes(q) || r.industry.toLowerCase().includes(q) || r.productLine.join(' ').toLowerCase().includes(q)); // if (include.length) items = items.filter(r => include.some(t => r.productLine.includes(t))); // if (founderOnly) items = items.filter(r => r.type === 'Founder-owned' || r.type === 'Family-owned'); // if (liOnly) items = items.filter(r => !!r.ceo.linkedin); // // // Simple index-based cursor for demo // const offset = cursor ? parseInt(cursor, 10) || 0 : 0; // const page = items.slice(offset, offset + limit); // const nextCursor = offset + limit < items.length ? String(offset + limit) : null; // // return NextResponse.json({ items: page, nextCursor, total: items.length, asOf: new Date().toISOString() }); // }

DataMindAI

  • Home
  • Databases
  • Company Search
  • LP Search
  • New Page
  • Copy of New Page
  • Bolt-On Search
GET STARTED
  • Home
  • Databases
  • Company Search
  • LP Search
  • New Page
  • Copy of New Page
  • Bolt-On Search

Get in touch

+555 5555 5555

mymail@mailservice.com

DataMindAI
  • Home
  • Databases
  • Company Search
  • LP Search
  • New Page
  • Copy of New Page
  • Bolt-On Search

AI Designed for Private Equity.

Learn more

Contact

mymail@mailservice.com
+555 5555 5555

Address

Street, Maryland, United States

© 2026 
All Rights Reserved | Company Name