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": { "node_modules/supabase": {
"version": "1.207.9", "version": "1.215.0",
"resolved": "https://registry.npmjs.org/supabase/-/supabase-1.207.9.tgz", "resolved": "https://registry.npmjs.org/supabase/-/supabase-1.215.0.tgz",
"integrity": "sha512-BJPwsAd2UBIpQawcQV3/xKHEZ8YrrkHYpgibxCZbG+RuxuhTtkHG7zR4I3LylIIEwcKp3hmDKu/hO1m2NT5RXA==", "integrity": "sha512-ITHqEnDl3F/b44AYdBUSzinqZhmGxrx205Yt2HfyaVTd5W/JppF3/naqVPIdRiUZBBLKkvfQO/wUk48W8SoVxw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "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 React, { Suspense } from "react";
import { useSearchParams } from "next/navigation"; import { getBusinessByName } from "@/lib/data/businessQuery";
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient"; import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
import { useQuery } from "@supabase-cache-helpers/postgrest-react-query"; import { BusinessSection } from "./BusinessSection";
import { ProjectCard } from "@/components/projectCard"; import { ProjectSection } from "./ProjectSection";
import { getProjectCardData } from "@/lib/data/projectQuery";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { getBusinessAndProject } from "@/lib/data/businessQuery";
function FindContent() { export default async function FindContent({ searchParams }: { searchParams: { query: string } }) {
const searchParams = useSearchParams(); const query = searchParams.query;
const query = searchParams.get("query");
let supabase = createSupabaseClient(); const supabase = createSupabaseClient();
const { const { data: projectIds, error: projectIdsError } = await supabase
data: businesses, .from("project")
isLoading: isLoadingBusinesses, .select(`id`)
error: businessError, .ilike("project_name", `%${query}%`);
} = useQuery(getBusinessAndProject(supabase, { businessName: query }));
const isLoading = isLoadingBusinesses; const { data: businessData, error: businessDataError } = await getBusinessByName(supabase, { businessName: query });
const error = businessError;
if (isLoading) return <p>Loading...</p>; if (businessDataError || projectIdsError) {
if (error) return <p>Error fetching data: {error.message}</p>; 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 ( return (
<div className="container max-w-screen-xl"> <div className="container max-w-screen-xl my-5 space-y-5">
<div className="mt-4"> <Suspense fallback={<div>Loading Business and Projects...</div>}>
<h1 className="text-4xl font-bold">Result</h1> <BusinessSection businessData={businessData} />
</Suspense>
<Separator className="my-4" /> <Separator className="my-3" />
<Suspense fallback={<div>Loading Projects...</div>}>
<Card className="w-full"> <ProjectSection projectsData={projectsData} />
<CardContent className="my-2"> </Suspense>
{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> </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"; import { CalendarDaysIcon } from "lucide-react";
import { BusinessCardProps } from "@/types/BusinessCard";
interface XMap { import Image from "next/image";
// tagName: colorCode
[tag: string]: string;
}
interface BusinessCardProps {
name: string;
description: string;
joinDate: string;
location: string;
tags: XMap | null;
}
export function BusinessCard(props: BusinessCardProps) { export function BusinessCard(props: BusinessCardProps) {
return ( return (
<Card> <Card className="rounded-xl border border-gray-200 dark:border-gray-700 hover:shadow-xl transition-shadow h-full">
<CardHeader> <CardHeader className="flex flex-row items-center gap-4 pb-4 border-b border-gray-100 dark:border-gray-800">
<div className="h-[200px] hover:h-[100px] duration-75 pb-2"> <div className="relative w-14 h-14 flex-shrink-0">
<Image <Image
src={"/money.png"} src="/money.png"
width={0} alt="Business logo"
height={0} fill
sizes="100vw" className="rounded-full object-cover bg-white dark:bg-sky-900"
style={{ width: "100%", height: "100%" }}
alt="nvidia"
/> />
</div> </div>
<CardTitle>{props.name}</CardTitle> <div>
<CardDescription> <CardTitle className="text-lg font-semibold text-gray-900 dark:text-white">{props.business_name}</CardTitle>
{props.description} <CardDescription className="text-sm text-gray-600 dark:text-gray-400 mt-1">
<span className="flex items-center pt-2 gap-1"> <span className="flex items-center gap-1">
<CalendarDaysIcon width={20} /> <CalendarDaysIcon width={16} />
Joined {props.joinDate} Joined {props.joined_date}
</span> </span>
</CardDescription> </CardDescription>
</div>
</CardHeader> </CardHeader>
<CardFooter className="flex-col items-start"> <CardFooter className="flex flex-col gap-2 pt-4">
{props.location} <span className="text-sm text-gray-700 dark:text-gray-300">{props.location}</span>
<span className="text-xs rounded-md bg-slate-200 dark:bg-slate-700 p-1"> <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">
Technology {props.business_type}
</span> </span>
</CardFooter> </CardFooter>
</Card> </Card>

View File

@ -22,6 +22,18 @@ interface ProjectCardProps {
} }
export function ProjectCard(props: 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 ( return (
<div <div
className={cn( 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 = ( export const getBusinessAndProject = (
client: SupabaseClient, client: SupabaseClient,
params: { businessName?: String | null; businessId?: number | null; single?: boolean } = { single: false } params: { businessName?: String | null; businessId?: number | null; single?: boolean } = { single: false }
@ -53,7 +80,7 @@ export const getBusinessAndProject = (
`); `);
if (params.businessName && params.businessName.trim() !== "") { 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) { if (params.businessId) {

View File

@ -1,4 +1,5 @@
import { SupabaseClient } from "@supabase/supabase-js"; import { SupabaseClient } from "@supabase/supabase-js";
import { ProjectCardProps } from "@/types/ProjectCard";
async function getTopProjects(client: SupabaseClient, numberOfRecords: number = 4) { async function getTopProjects(client: SupabaseClient, numberOfRecords: number = 4) {
try { try {
@ -241,6 +242,62 @@ const getProjectByUserId = (client: SupabaseClient, userId: string) => {
.eq("business.user_id", userId); .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 { export {
getProjectData, getProjectData,
getProjectDataQuery, getProjectDataQuery,
@ -248,4 +305,6 @@ export {
searchProjectsQuery, searchProjectsQuery,
getProjectByBusinessId, getProjectByBusinessId,
getProjectByUserId, 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.