refactor - fix: find page also search by project/business name

This commit is contained in:
Sosokker 2024-11-06 22:53:19 +07:00
parent 6269406772
commit a0e25d0268
11 changed files with 287 additions and 123 deletions

6
package-lock.json generated
View File

@ -11230,9 +11230,9 @@
}
},
"node_modules/supabase": {
"version": "1.207.9",
"resolved": "https://registry.npmjs.org/supabase/-/supabase-1.207.9.tgz",
"integrity": "sha512-BJPwsAd2UBIpQawcQV3/xKHEZ8YrrkHYpgibxCZbG+RuxuhTtkHG7zR4I3LylIIEwcKp3hmDKu/hO1m2NT5RXA==",
"version": "1.215.0",
"resolved": "https://registry.npmjs.org/supabase/-/supabase-1.215.0.tgz",
"integrity": "sha512-ITHqEnDl3F/b44AYdBUSzinqZhmGxrx205Yt2HfyaVTd5W/JppF3/naqVPIdRiUZBBLKkvfQO/wUk48W8SoVxw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {

View File

@ -0,0 +1,55 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { BusinessCard } from "@/components/BusinessCard";
import { BusinessCardProps } from "@/types/BusinessCard";
import { Separator } from "@/components/ui/separator";
import Link from "next/link";
interface BusinessSectionProps extends BusinessCardProps {
user_id: string;
}
export function BusinessSection({ businessData }: { businessData: BusinessSectionProps[] }) {
if (!businessData || businessData.length === 0) {
return (
<Card className="text-center">
<CardHeader>
<CardTitle>No Business Found</CardTitle>
</CardHeader>
<CardContent>
<p>Sorry, we could not find any businesses matching your search criteria.</p>
</CardContent>
</Card>
);
}
return (
<div className="space-y-6">
<div id="project-card">
<Card>
<CardHeader>
<CardTitle>Businesses</CardTitle>
<CardDescription>Found {businessData.length} projects!</CardDescription>
</CardHeader>
<Separator className="my-3" />
<CardContent>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{businessData.map((business) => (
<div key={business.business_id}>
<Link href={`/profile/${business.user_id}`}>
<BusinessCard
business_id={business.business_id}
business_name={business.business_name}
joined_date={business.joined_date}
location={business.location}
business_type={business.business_type}
/>
</Link>
</div>
))}
</div>
</CardContent>
</Card>
</div>
</div>
);
}

View File

@ -0,0 +1,57 @@
import React from "react";
import { ProjectCard } from "@/components/projectCard";
import { Separator } from "@/components/ui/separator";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { ProjectCardProps } from "@/types/ProjectCard";
import Link from "next/link";
export function ProjectSection({ projectsData }: { projectsData: ProjectCardProps[] | null }) {
if (!projectsData || projectsData.length === 0) {
return (
<Card className="text-center">
<CardHeader>
<CardTitle>No Project Found</CardTitle>
</CardHeader>
<CardContent>
<p>Sorry, we could not find any projects matching your search criteria.</p>
</CardContent>
</Card>
);
}
return (
<div className="space-y-6">
<div id="project-card">
<Card>
<CardHeader>
<CardTitle>Projects</CardTitle>
<CardDescription>Found {projectsData.length} projects!</CardDescription>
</CardHeader>
<Separator className="my-3" />
<CardContent>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{projectsData.map((project) => (
<div key={project.id}>
<Link href={`/deals/${project.id}`}>
<ProjectCard
name={project.project_name}
description={project.short_description}
imageUri={project.image_url}
joinDate={new Date(project.join_date).toLocaleDateString()}
location={project.location}
tags={project.tags}
minInvestment={project.min_investment}
totalInvestor={project.total_investor}
totalRaised={project.total_raise}
/>
</Link>
<Separator />
</div>
))}
</div>
</CardContent>
</Card>
</div>
</div>
);
}

View File

@ -1,89 +1,43 @@
"use client";
import React, { Suspense } from "react";
import { useSearchParams } from "next/navigation";
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
import { useQuery } from "@supabase-cache-helpers/postgrest-react-query";
import { ProjectCard } from "@/components/projectCard";
import { getBusinessByName } from "@/lib/data/businessQuery";
import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
import { BusinessSection } from "./BusinessSection";
import { ProjectSection } from "./ProjectSection";
import { getProjectCardData } from "@/lib/data/projectQuery";
import { Separator } from "@/components/ui/separator";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { getBusinessAndProject } from "@/lib/data/businessQuery";
function FindContent() {
const searchParams = useSearchParams();
const query = searchParams.get("query");
export default async function FindContent({ searchParams }: { searchParams: { query: string } }) {
const query = searchParams.query;
let supabase = createSupabaseClient();
const supabase = createSupabaseClient();
const {
data: businesses,
isLoading: isLoadingBusinesses,
error: businessError,
} = useQuery(getBusinessAndProject(supabase, { businessName: query }));
const { data: projectIds, error: projectIdsError } = await supabase
.from("project")
.select(`id`)
.ilike("project_name", `%${query}%`);
const isLoading = isLoadingBusinesses;
const error = businessError;
const { data: businessData, error: businessDataError } = await getBusinessByName(supabase, { businessName: query });
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error fetching data: {error.message}</p>;
if (businessDataError || projectIdsError) {
throw new Error(businessDataError?.message || projectIdsError?.message || "Unknown error");
}
const projectIdList: string[] = projectIds.map((item) => item.id);
const { data: projectsData, error: projectsDataError } = await getProjectCardData(supabase, projectIdList);
if (projectsDataError) {
throw new Error(projectsDataError || "Unknown error");
}
return (
<div className="container max-w-screen-xl">
<div className="mt-4">
<h1 className="text-4xl font-bold">Result</h1>
<Separator className="my-4" />
<Card className="w-full">
<CardContent className="my-2">
{businesses!.length === 0 && <p>No results found.</p>}
{businesses!.length > 0 && (
<ul>
{businesses!.map((business) => (
<li key={business.business_id}>
<Card className="w-full">
<CardHeader>
<CardTitle>{business.business_name}</CardTitle>
<CardDescription>
Joined Date: {new Date(business.joined_date).toLocaleDateString()}
</CardDescription>
</CardHeader>
<CardContent className="grid grid-cols-4 gap-4">
{business?.projects && business.projects.length > 0 ? (
business.projects.map((project) => (
<ProjectCard
key={project.id}
name={project.project_name}
description={project.project_short_description}
joinDate={project.published_time}
location={business.location}
minInvestment={project.min_investment}
totalInvestor={project.total_investment}
totalRaised={project.target_investment}
tags={project.tags?.map((tag) => String(tag.tag_value)) || []}
imageUri={project.card_image_url}
/>
))
) : (
<p>No Projects</p>
)}
</CardContent>
</Card>
</li>
))}
</ul>
)}
</CardContent>
</Card>
</div>
<div className="container max-w-screen-xl my-5 space-y-5">
<Suspense fallback={<div>Loading Business and Projects...</div>}>
<BusinessSection businessData={businessData} />
</Suspense>
<Separator className="my-3" />
<Suspense fallback={<div>Loading Projects...</div>}>
<ProjectSection projectsData={projectsData} />
</Suspense>
</div>
);
}
export default function Find() {
return (
<Suspense fallback={<p>Loading search parameters...</p>}>
<FindContent />
</Suspense>
);
}

View File

@ -1,53 +1,34 @@
import Image from "next/image";
import {
Card,
CardFooter,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Card, CardFooter, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { CalendarDaysIcon } from "lucide-react";
interface XMap {
// tagName: colorCode
[tag: string]: string;
}
interface BusinessCardProps {
name: string;
description: string;
joinDate: string;
location: string;
tags: XMap | null;
}
import { BusinessCardProps } from "@/types/BusinessCard";
import Image from "next/image";
export function BusinessCard(props: BusinessCardProps) {
return (
<Card>
<CardHeader>
<div className="h-[200px] hover:h-[100px] duration-75 pb-2">
<Card className="rounded-xl border border-gray-200 dark:border-gray-700 hover:shadow-xl transition-shadow h-full">
<CardHeader className="flex flex-row items-center gap-4 pb-4 border-b border-gray-100 dark:border-gray-800">
<div className="relative w-14 h-14 flex-shrink-0">
<Image
src={"/money.png"}
width={0}
height={0}
sizes="100vw"
style={{ width: "100%", height: "100%" }}
alt="nvidia"
src="/money.png"
alt="Business logo"
fill
className="rounded-full object-cover bg-white dark:bg-sky-900"
/>
</div>
<CardTitle>{props.name}</CardTitle>
<CardDescription>
{props.description}
<span className="flex items-center pt-2 gap-1">
<CalendarDaysIcon width={20} />
Joined {props.joinDate}
</span>
</CardDescription>
<div>
<CardTitle className="text-lg font-semibold text-gray-900 dark:text-white">{props.business_name}</CardTitle>
<CardDescription className="text-sm text-gray-600 dark:text-gray-400 mt-1">
<span className="flex items-center gap-1">
<CalendarDaysIcon width={16} />
Joined {props.joined_date}
</span>
</CardDescription>
</div>
</CardHeader>
<CardFooter className="flex-col items-start">
{props.location}
<span className="text-xs rounded-md bg-slate-200 dark:bg-slate-700 p-1">
Technology
<CardFooter className="flex flex-col gap-2 pt-4">
<span className="text-sm text-gray-700 dark:text-gray-300">{props.location}</span>
<span className="text-xs font-medium rounded-md bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 px-2 py-1">
{props.business_type}
</span>
</CardFooter>
</Card>

View File

@ -22,6 +22,18 @@ interface ProjectCardProps {
}
export function ProjectCard(props: ProjectCardProps) {
if (props.minInvestment === null) {
props.minInvestment = 0;
}
if (props.totalInvestor === null) {
props.minInvestment = 0;
}
if (props.totalRaised === null) {
props.minInvestment = 0;
}
return (
<div
className={cn(

View File

@ -18,6 +18,33 @@ export const getAllBusinesses = (client: SupabaseClient) => {
`);
};
export const getBusinessByName = (
client: SupabaseClient,
params: { businessName?: string | null; single?: boolean } = { single: false }
) => {
const query = client.from("business").select(`
business_id:id,
location,
business_name,
...business_type (
business_type_id: id,
business_type: value
),
joined_date,
user_id
`);
if (params.businessName && params.businessName.trim() !== "") {
query.ilike("business_name", `%${params.businessName}%`);
}
if (params.single) {
query.single();
}
return query;
};
export const getBusinessAndProject = (
client: SupabaseClient,
params: { businessName?: String | null; businessId?: number | null; single?: boolean } = { single: false }
@ -53,7 +80,7 @@ export const getBusinessAndProject = (
`);
if (params.businessName && params.businessName.trim() !== "") {
return query.ilike("business_name", `%${params.businessName}%`);
return query.or(`business_name.ilike.%${params.businessName}%,project_name.ilike.%${params.businessName}%`);
}
if (params.businessId) {

View File

@ -1,4 +1,5 @@
import { SupabaseClient } from "@supabase/supabase-js";
import { ProjectCardProps } from "@/types/ProjectCard";
async function getTopProjects(client: SupabaseClient, numberOfRecords: number = 4) {
try {
@ -241,6 +242,62 @@ const getProjectByUserId = (client: SupabaseClient, userId: string) => {
.eq("business.user_id", userId);
};
function getProjectByName(client: SupabaseClient, searchTerm: string) {
return client
.from("project")
.select(
`
id,
project_name,
business_id,
published_time,
project_short_description,
card_image_url,
project_investment_detail (
min_investment,
total_investment,
target_investment,
investment_deadline
),
project_tag (
tag (
id,
value
)
),
business (
location
)
`
)
.ilike("project_name", `%${searchTerm}%`);
}
const getProjectCardData = async (client: SupabaseClient, projectIds: string[]) => {
const { data, error } = await client.from("project_card").select("*").in("project_id", projectIds);
if (error) {
return { data: null, error: error.message };
}
const projectSections = data.map((project) => {
const projectSection: ProjectCardProps = {
id: project.project_id,
project_name: project.project_name,
short_description: project.short_description,
image_url: project.image_url,
join_date: project.join_date,
location: project.location,
tags: project.tags || [],
min_investment: project.min_investment,
total_investor: project.total_investor,
total_raise: project.total_raise,
};
return projectSection;
});
return { data: projectSections, error: null };
};
export {
getProjectData,
getProjectDataQuery,
@ -248,4 +305,6 @@ export {
searchProjectsQuery,
getProjectByBusinessId,
getProjectByUserId,
getProjectByName,
getProjectCardData,
};

View File

@ -0,0 +1,7 @@
export interface BusinessCardProps {
business_id: number;
business_name: string;
joined_date: string;
location: string;
business_type: string;
}

12
src/types/ProjectCard.ts Normal file
View File

@ -0,0 +1,12 @@
export interface ProjectCardProps {
id?: number;
project_name: string;
short_description: string;
image_url: string;
join_date: string;
location: string;
tags: string[] | null;
min_investment: number;
total_investor: number;
total_raise: number;
}

Binary file not shown.