From 9276300017eaffe7a95a932aaac88f6a343d858d Mon Sep 17 00:00:00 2001 From: Kavish Shah Date: Mon, 5 May 2025 01:16:52 -0700 Subject: [PATCH 1/2] feat(raw-deals): add list-view bulk-actions and toggle --- components/DealContainer.tsx | 136 +++++++++++++++++++++++++++++++++++ components/DealListItem.tsx | 49 +++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 components/DealContainer.tsx create mode 100644 components/DealListItem.tsx diff --git a/components/DealContainer.tsx b/components/DealContainer.tsx new file mode 100644 index 0000000..0bcfc28 --- /dev/null +++ b/components/DealContainer.tsx @@ -0,0 +1,136 @@ +"use client"; +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Grid, List } from "lucide-react"; +import DealCard from "@/components/DealCard"; +import DealListItem from "@/components/DealListItem"; +import type { Deal, UserRole } from "@prisma/client"; +import DeleteDealFromDB from "@/app/actions/delete-deal"; + +interface DealContainerProps { + data: Deal[]; + userRole: UserRole; + currentPage: number; + totalPages: number; + totalCount: number; +} + +export default function DealContainer({ + data, + userRole, +}: DealContainerProps) { + const router = useRouter(); + const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); + const [selectedIds, setSelectedIds] = useState>(new Set()); + + const allSelected = data.length > 0 && selectedIds.size === data.length; + + function toggleAll() { + setSelectedIds(allSelected ? new Set() : new Set(data.map(d => d.id))); + } + function toggleOne(id: string) { + setSelectedIds(prev => { + const next = new Set(prev); + next.has(id) ? next.delete(id) : next.add(id); + return next; + }); + } + + async function handleBulkDelete() { + for (const id of selectedIds) { + const deal = data.find(d => d.id === id); + if (deal) await DeleteDealFromDB(deal.dealType, id); + } + router.refresh(); + setSelectedIds(new Set()); + } + + function handleBulkScreen() { + if (!selectedIds.size) return; + const ids = Array.from(selectedIds).join(","); + router.push(`/raw-deals/screen?ids=${ids}`); + } + + return ( + <> +
+
+ + +
+ + {viewMode === "list" && ( +
+ + + + + +
+ )} +
+ + {viewMode === "grid" ? ( +
+ {data.map(deal => ( + + ))} +
+ ) : ( +
+ {data.map(deal => ( + toggleOne(deal.id)} + /> + ))} +
+ )} + + ); +} + diff --git a/components/DealListItem.tsx b/components/DealListItem.tsx new file mode 100644 index 0000000..4f8893f --- /dev/null +++ b/components/DealListItem.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import Link from "next/link"; +import type { Deal } from "@prisma/client"; + +interface Props { + deal: Deal; + selected: boolean; + onToggle: () => void; +} + +export default function DealListItem({ deal, selected, onToggle }: Props) { + return ( +
+
+ +
+

+ {deal.dealCaption} +

+
+ Revenue: ${deal.revenue} · EBITDA: ${deal.ebitda} ·{" "} + {deal.industry || "—"} +
+
+
+ +
+ + View Details + + + Screen Deal + +
+
+ ); +} + From 23139a4e665932bf41575798fc7dcbdce58f4826 Mon Sep 17 00:00:00 2001 From: Kavish Shah Date: Mon, 5 May 2025 03:29:33 -0700 Subject: [PATCH 2/2] refactor(raw-deals): wire page.tsx to use DealContainer and normalize dealTypes --- app/(protected)/raw-deals/page.tsx | 116 ++++++++++++++--------------- 1 file changed, 57 insertions(+), 59 deletions(-) diff --git a/app/(protected)/raw-deals/page.tsx b/app/(protected)/raw-deals/page.tsx index fba2634..396ad8c 100644 --- a/app/(protected)/raw-deals/page.tsx +++ b/app/(protected)/raw-deals/page.tsx @@ -1,50 +1,54 @@ +// app/(protected)/raw-deals/page.tsx import React, { Suspense } from "react"; -import DealCardSkeleton from "@/components/skeletons/DealCardSkeleton"; import { Metadata } from "next"; -import prismaDB from "@/lib/prisma"; +import DealCardSkeleton from "@/components/skeletons/DealCardSkeleton"; import DealCard from "@/components/DealCard"; -import getCurrentUserRole from "@/lib/data/current-user-role"; -import GetDeals, { GetAllDeals } from "@/app/actions/get-deal"; import SearchDeals from "@/components/SearchDeal"; -import Pagination from "@/components/pagination"; -import { setTimeout } from "timers/promises"; -import DealTypeFilter from "@/components/DealTypeFilter"; -import { DealType } from "@prisma/client"; import SearchDealsSkeleton from "@/components/skeletons/SearchDealsSkeleton"; import SearchEbitdaDeals from "@/components/SearchEbitdaDeals"; -import DealTypeFilterSkeleton from "@/components/skeletons/DealTypeFilterSkeleton"; -import UserDealFilter from "@/components/UserDealFilter"; +import DealTypeFilter from "@/components/DealTypeFilter"; +import Pagination from "@/components/pagination"; +import getCurrentUserRole from "@/lib/data/current-user-role"; +import { DealType } from "@prisma/client"; +import { GetAllDeals } from "@/app/actions/get-deal"; +import DealContainer from "@/components/DealContainer"; export const metadata: Metadata = { - title: "Raw Deals", - description: "View the raw deals", + title: "Inferred Deals", + description: "View the inferred deals scraped using AI", }; -// After -type SearchParams = Promise<{ [key: string]: string | undefined }>; +type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>; -const RawDealsPage = async (props: { searchParams: SearchParams }) => { - const searchParams = await props.searchParams; - const search = searchParams?.query || ""; - const currentPage = Number(searchParams?.page) || 1; - const limit = Number(searchParams?.limit) || 20; +export default async function RawDealsPage(props: { + searchParams: SearchParams; +}) { + const params = await props.searchParams; + + const search = Array.isArray(params.query) + ? params.query[0] + : params.query ?? ""; + const currentPage = + Number(Array.isArray(params.page) ? params.page[0] : params.page) || 1; + const limit = + Number(Array.isArray(params.limit) ? params.limit[0] : params.limit) || 20; const offset = (currentPage - 1) * limit; + const ebitda = + Array.isArray(params.ebitda) ? params.ebitda[0] : params.ebitda ?? ""; - const ebitda = searchParams?.ebitda || ""; - const userId = searchParams?.userId || ""; - // Ensure dealTypes is always an array - const dealTypes = - typeof searchParams?.dealType === "string" - ? [searchParams.dealType] - : searchParams?.dealType || []; + const rawDealTypes = Array.isArray(params.dealType) + ? params.dealType + : params.dealType + ? [params.dealType] + : []; + const dealTypes = rawDealTypes as DealType[]; const { data, totalPages, totalCount } = await GetAllDeals({ search, offset, limit, - dealTypes: dealTypes as DealType[], + dealTypes, ebitda, - userId, }); const currentUserRole = await getCurrentUserRole(); @@ -52,20 +56,19 @@ const RawDealsPage = async (props: { searchParams: SearchParams }) => { return (
-

Raw Deals

+

Raw Deals

- Browse through our collection of unprocessed deals gathered from - various sources including manual entries, bulk uploads, external - website scraping, and AI-inferred opportunities. + Browse our unprocessed deals from manual entries, bulk uploads, + web-scraping and AI-driven inferences.

-
-

- Total Deals: {totalCount} +
+

+ Total Deals: {totalCount}

-
+
Page {currentPage} of {totalPages}
@@ -77,33 +80,28 @@ const RawDealsPage = async (props: { searchParams: SearchParams }) => { }> -
- }> - - }> - - +

-
- {data.length === 0 ? ( -
-

- No deals found matching your criteria. -

-
- ) : ( -
- {data.map((e) => ( - - ))} -
- )} -
+ {data.length === 0 ? ( +
+

+ No deals found matching your criteria. +

+
+ ) : ( + + )} +
); -}; +} -export default RawDealsPage;